Nieuws
Foto's
Artikelen
Componenten
Applicaties
Kleinkunst

.NET - Silverlight 4 design-time mogelijkheden met ViewModels

.NET 4.0 and Visual Studio 2010 offer a lot of new features. Also Silverlight 4 is a major update with many new features (webcam support, RichText control, HTML content, access to local files, drag and drop, ICommand, right clicks and mouse wheel support, COM support, clipboard, unit testing, …). I like all the new features but I was especially interested in improvements that could help my current developments which are based on a M-V-VM architecture with ViewModel properties that are bound to UI controls.

I explored the features of the improved design surface, the interactive Properties window, the new Data Sources window and the new design-time properties. In this article I will focus on the new features that improve the designing experience when using ViewModels. Along the way I will show a lot of screenshots and try to cover some other new Silverlight 4 features.


Domain entities and WCF RIA Services

I started creating a new Silverlight 4 navigation application. On server side I created an Entity Data Model based on the Northwind database. I used WCF RIA Services to create a small service to retrieve data from the Products table.

[EnableClientAccess()]
public class ProductDomainService : LinqToEntitiesDomainService<NorthwindEntities>
{
    public IQueryable<Product> GetProducts()
    {
        return this.ObjectContext.Products;
    }
}
Because I created a DomainService that queries the Products collection, the Product class has been generated in my Silverlight client application in the hidden folder Generated_Code.
[DataContract(Namespace="http://schemas.datacontract.org/2004/07/ScipBe.Demo.SL4.Web")]
public sealed partial class Product : Entity
{
 
    private Nullable<int> _categoryID;
 
    private int _productID;
 
    private string _productName;
 
     ...

    partial void OnCreated();
    partial void OnCategoryIDChanging(Nullable<int> value);
    partial void OnCategoryIDChanged();
    partial void OnProductIDChanging(int value);
    partial void OnProductIDChanged();
    partial void OnProductNameChanging(string value);
    partial void OnProductNameChanged();
    ...

    public Product()
    {
        this.OnCreated();
    }
 
    [DataMember()]
    public Nullable<int> CategoryID
    {
        get
        {
            return this._categoryID;
        }
        set
        {
            if ((this._categoryID != value))
            {
                this.ValidateProperty("CategoryID", value);
                this.OnCategoryIDChanging(value);
                this.RaiseDataMemberChanging("CategoryID");
                this._categoryID = value;
                this.RaiseDataMemberChanged("CategoryID");
                this.OnCategoryIDChanged();
            }
        }
    }
 
    ...
} 

 

Base classes and ViewModel interface and implementation

ViewModel interface

My domain entity Product is available on the client so I started with declaring an interface for my ViewModel. For this demo I added several collection types, an int, a DateTime, a generic Dictionary, a PagedCollectionView and an ICommand property.

public interface IProductViewModel
{
    IEnumerable<Product> ProductCollection1 { get; }
 
    ObservableCollection<Product> ProductCollection2 { get; }
 
    EntitySet<Product> ProductSet { get; }
 
    PagedCollectionView ProductView1 { get; }
 
    int ProductCount { get; }
 
    DateTime StartDateTime { get; }
 
    Dictionary<string, string> Dictionary { get; }
 
    ICommand RefreshCommand { get; }
}

ViewModelBase class

Secondly I had to create an implementation for this IProductViewModel interface. Therefore I needed 2 base classes in my framework. My ViewModelBase class implements the INotifyPropertyChanged interface.

public class ViewModelBase : INotifyPropertyChanged
{
    protected void RaisePropertyChanged<TViewModel>(Expression<Func<TViewModel>> property)
    {
        var expression = property.Body as MemberExpression;
        var member = expression.Member;
 
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(member.Name));
        }
    }
 
    public event PropertyChangedEventHandler PropertyChanged;
}


DelegateCommand class

The ICommand interface was already introduced in Silverlight 3 and now in Silverlight 4 a pair of properties have been added to the ButtonBase and Hyperlink classes named Command and CommandParameter. Surprisingly Silverlight 4 does not provide an ICommand implementation. You can use the DelegateCommand of PRISM or you can create your own implementation. For this demo I created my own DelegateCommand class.

public class DelegateCommand<T> : ICommand
{
    private readonly Action<T> executeAction;
 
    private readonly Func<T, bool> canExecuteAction;
 
    public DelegateCommand(Action<T> executeAction)
        : this(executeAction, null)
    {
    }
 
    public DelegateCommand(Action<T> executeAction, Func<T, bool> canExecuteAction)
    {
        this.executeAction = executeAction;
        this.canExecuteAction = canExecuteAction;
    }
 
    public event EventHandler CanExecuteChanged;
 
    public bool CanExecute(object parameter)
    {
        if (canExecuteAction != null)
        {
            return (canExecuteAction((T)parameter));
        }
 
        return executeAction != null;
    }
 
    public void Execute(object parameter)
    {
        if (executeAction != null)
        {
            executeAction((T)parameter);
        }
    }
}


ProductViewModel class

Now I can fully implement my ProductViewModel class. I’m using WCF RIA DomainService to retrieve the product collection from the Northwind database.

public class ProductViewModel : ViewModelBase, IProductViewModel
{
    private IEnumerable<Product> productCollection1;
 
    public IEnumerable<Product> ProductCollection1
    {
        get
        {
            return productCollection1;
        }
 
        private set
        {
            productCollection1 = value;
            RaisePropertyChanged(() => ProductCollection1);
        }
    }

    ...
 
    public ICommand RefreshCommand { get; private set; }
 
    private ProductDomainContext context;
 
    public ProductViewModel()
    {
        RefreshCommand = new DelegateCommand<object>(Refresh);
 
        context = new ProductDomainContext();
 
        productView1 = new PagedCollectionView(context.Products);
        productView1.SortDescriptions.Add(
            new SortDescription("UnitPrice", ListSortDirection.Descending));
    }
 
    public void Refresh(object dummy)
    {
        context.Load(context.GetProductsQuery()).Completed += GetProductsCompleted;
    }
 
    private void GetProductsCompleted(object sender, EventArgs e)
    {
        var loadOperation = (LoadOperation)sender;
        if (loadOperation.IsComplete && !loadOperation.IsCanceled && !loadOperation.HasError)
        {
            ProductSet = context.Products;
            ProductCollection1 = context.Products;
            ProductCollection2 = new ObservableCollection<Product>(context.Products);
            ProductCount = context.Products.Count;
        }
    }
}

 

XAML designer

Visual Studio 2010 offers a powerful design surface for Silverlight 3 and 4 that works the same as the WPF designer. Now you can drag controls from the Toolbox and drop them on the design surface. Automatically the control will be added to the XAML code and all default properties will be set. The designer displays various types of handles, adorners and snaplines to assist the developer in positioning and resizing controls and supplying visual feedback. By clicking on the rails on the top or left of a grid new grid columns and rows can be created. Additionally small popup buttons will help you specifying the Height and Width.

Silverlight 4 designer

Moving and resizing controls can be done easily with the mouse. The ColumnSpan and RowSpan properties will automatically change. Aligning and stretching controls can be done by clicking on the small circle. Once this alignment adorner is a small triangle the Width/Height and VerticalAlignment/HorizontalAligment properties will be removed and the control will be stretched.

Selecting multiple controls can be done by holding the CTRL key and pressing CTRL+A will select all controls in the selected container.

Silverlight 4 designer

In this demo application I added 5 TextBlocks in row 1 and two ListBoxes and 3 DataGrids in row 2.

 

Design-time properties

DesignWidth and DesignHeight

In Silverlight 3 some design-time properties were introduced. The "d" alias refers to these design-time properties which will be ignored in run-time.  DesignWidth and DesignHeight can be used to specify the width and height of controls in design-time.

<navigation:Page 
    x:Class="ScipBe.Demo.SL4.Views.ProductPage"
    mc:Ignorable="d"
    d:DesignWidth="800" d:DesignHeight="200">

DataContext and DesignInstance

Another new and very interesting property is DataContext. When using the M-V-VM pattern ViewModel classes will be created in run-time and will then be linked to a View (Page/UserControl/…). When using PRISM or another framework this will probably be done using an IoC container. This makes it difficult to use the new Properties window in design-time. Fortunately this can be solved by this new design-time DataContext property and the DesignInstance option. This design-time DataContext can be used independent of the run-time DataContext property.

<navigation:Page 
    x:Class="ScipBe.Demo.SL4.Views.ProductPage"
    mc:Ignorable="d"
    d:DesignWidth="800" d:DesignHeight="200"
    d:DataContext="{d:DesignInstance local:ProductViewModel}">

 

Properties window and databinding

Now we can start using the new Properties window and setting up bindings between our controls and the properties of our ProductViewModel.

TextBox

When you select the first TextBox you will see that you can use the Properties window to modify all its properties. Silverlight developers are used to create XAML code manually but from now on we can do everything with the design surface and the Properties window. Several property types will show nice editors like the Font Editor, Data Binding Builder, Resource Picker, Brush Editor, ...

Silverlight Properties window

Creating bindings can also be done easily. Select the Text property and click on the Apply DataBinding menu to open the dialog. You will see that the DataContext has already been set and it refers to the design-time DataContext which is our ProductViewModel class. Now we can select one of its properties, e.g. ProductCount. Cool, isn’t it?

Silverlight Properties window

Silverlight Properties window

You can also select another Source or specify Converters or Validation options.

Silverlight Properties window

<TextBlock 
    Grid.Row="1"            
    Grid.Column="0" 
    Margin="10" Text="{Binding Path=ProductCount}">
</TextBlock>

Button and Command

In Silverlight 4 the Button class has 2 new properties Command and CommandParameter. Now you can bind the Command property of the Button control to the RefreshCommand property of our ProductViewModel.

Silverlight Properties window

<Button
    Grid.Row="0"
    Grid.Column="0"
    Grid.ColumnSpan="5"
    Content="Refresh" 
    Command="{Binding Path=RefreshCommand}">
</Button>

ListBox

Next we will try to create bindings with collections. Therefore we create a binding to ProductCollection1 for the ItemSource property of the first ListBox. In the property DisplayMemberPath we can enter the string "ProductName".

Silverlight Properties window

Silverlight Properties window

DataGrid

Now we will try the same with a DataGrid. First create a binding to ProductCollection1 for the ItemSource property and then open the Columns Collection Editor dialog. This nice dialog allows us to Add/Edit columns and change their properties. Notice that you can select each property of our Product class. This is because the ItemSource has been set to a collection of Products.

Silverlight DataGrid Collection Editor

In the same dialog you can specify the Width of each column. Silverlight 4 adds several new features to the DataGrid control like the ability to allow columns to share the remaining width of a DataGrid (*). As you can see in the screenshot several other new options have been introduced.

Silverlight DataGrid Collection Editor

In the current beta there is a bug in this dialog which throws “Value does not fall within expected range” exceptions. The generated XAML will not work so you have to clean it up manually.

<data:DataGrid.Columns>
    <data:DataGridTextColumn Binding="{Binding Path=ProductName}" CanUserReorder="True" CanUserResize="True" 
      CanUserSort="True" CellStyle="{x:Null}" ClipboardContentBinding="{x:Null}" DisplayIndex="-1" 
      DragIndicatorStyle="{x:Null}" EditingElementStyle="{x:Null}" ElementStyle="{x:Null}" FontSize="NaN" 
      FontStyle="Normal" FontWeight="Normal" Foreground="{x:Null}" HeaderStyle="{x:Null}" 
      IsReadOnly="False" MaxWidth="Infinity" MinWidth="0" SortMemberPath="{x:Null}" Visibility="Visible" Width="100" />
    <data:DataGridTextColumn Binding="{Binding Path=UnitPrice}" CanUserReorder="True" CanUserResize="True"
      CanUserSort="True" CellStyle="{x:Null}" ClipboardContentBinding="{x:Null}" DisplayIndex="-1" 
      DragIndicatorStyle="{x:Null}" EditingElementStyle="{x:Null}" ElementStyle="{x:Null}" FontSize="NaN" 
      FontStyle="Normal" FontWeight="Normal" Foreground="{x:Null}" Header="{x:Null}" HeaderStyle="{x:Null}" 
      IsReadOnly="False" MaxWidth="Infinity" MinWidth="0" SortMemberPath="{x:Null}" Visibility="Visible" Width="*" />
</data:DataGrid.Columns>
<data:DataGrid.Columns>
    <data:DataGridTextColumn Binding="{Binding Path=ProductName}" Header="Name"/>
    <data:DataGridTextColumn Binding="{Binding Path=UnitPrice}" Header="Price"/>
</data:DataGrid.Columns>

Bindings to IEnumerable or Observable collections or to PagedCollectionView properties all work the same way.

 

Showing data in Design-time

The Properties window helps us setting up bindings but we are not able to see the final result in design-time because no data is shown. Fortunately this can also be solved with another design-time property that allow us to create an instance of the ViewModel in design-time.

Design-time ViewModel

Because our current ProductViewModel calls WCF RIA DomainServices to retrieve data, this ViewModel cannot be used in design-time. To allow you to take advantage of this feature, you should create a small design-time/mocked ViewModel with the same interface. The property getters should return some hardcoded data. Not all properties can be mocked, so the following example only shows the properties and collections which can be set in the design-time ViewModel. If you want to create design-time ViewModels then you should decide to use properties which can be mocked.

public class DesignProductViewModel : ViewModelBase, IProductViewModel
{
    public IEnumerable<Product> ProductCollection1
    {
        get
        {
            var products = new Collection<Product>();
            products.Add(new Product() { ProductID = 1, ProductName = "A", UnitPrice = 10 });
            products.Add(new Product() { ProductID = 2, ProductName = "B", UnitPrice = 7 });
            products.Add(new Product() { ProductID = 3, ProductName = "C", UnitPrice = 109 });
            return products;
        }
    }
 
    public ObservableCollection<Product> ProductCollection2
    {
        get
        {
            var products = new ObservableCollection<Product>(ProductCollection1);
            return products;
        }
    }
 
    public PagedCollectionView ProductView1
    {
        get
        {
            var view = new PagedCollectionView(ProductCollection1);
            view.SortDescriptions.Add(new SortDescription("UnitPrice", ListSortDirection.Descending));
            return view;
        }
    }
 
 
    public int ProductCount
    {
        get
        {
            return 123;
        }
    }
 
    public DateTime StartDateTime
    {
        get
        {
            return DateTime.Now;
        }
    }
 
    public Dictionary<string, string> Dictionary
    {
        get
        {
            var dictionary = new Dictionary<string, string>();
            dictionary.Add("A", "First string in dictionary");
            dictionary.Add("B", "Second string in dictionary");
            return dictionary;
        }
    }
 
    public ICommand RefreshCommand { get; private set; }
}

IsDesignTimeCreatable

Now we need to change the design-time DataContext and set the property IsDesignTimeCreatable to true.

<navigation:Page 
    x:Class="ScipBe.Demo.SL4.Views.ProductPage"
    mc:Ignorable="d"
    d:DesignWidth="800" d:DesignHeight="200"
    d:DataContext="{d:DesignInstance local:DesignProductViewModel, IsDesignTimeCreatable=True}">

The cool thing about this option is that you will immediately see your fixed data in the designer.

Silverlight Design-time Data


Other Silverlight 4 DataBinding improvements

Silverlight also offers some other handy improvements that also work fine in design-time.

String indexers

Silverlight 4 introduces binding support for string indexers. For example, it is possible to bind an element from a dictionary by specifying its key. In current beta the Properties window does not support specifying this index. So you can create the binding to the Dictionary with the Properties window but the index itself should be set manually in the XAML code.

<TextBlock 
    Grid.Row="1"            
    Grid.Column="3" 
    Margin="10" Text="{Binding Path=Dictionary[B]}">
</TextBlock>

Dependency Objects

Silverlight 4 also introduces the ability to bind properties on a DependencyObject and not just on FrameworkElements. Now we can finally use bindings in the Run inline flow element of the TextBlock control. The current beta sometimes throws "Invalid XAML" errors when using Run in a DataTemplate but I assume this will be solved in the final version.

Binding with DataGridColumn properties like Header, Visibility, ... and binding with the ValueConverter parameter still can't be done. I really hope this will be implemented in the final version because these features are missing since Silverlight 2.

Silverlight Design-time Data

StringFormat & TextTrimming

Bindings now support StringFormat like WPF does and TextTrimming properties have been introduced.

Silverlight Design-time Data

 

Data Sources window

Visual Studio 2010 also introduces a new Data Sources window. This window shows all the available WCF RIA DomainContexts and their EntitySets. You can customize a DataGrid or DataForm and drag and drop it on the design surface.

Silverlight Data Sources Window

It becomes more interesting when you add your own Data Source. In the wizard I will choose my DesignProductViewModel class.

Silverlight Data Sources Window

The Data Sources window will now display all the properties of my ViewModel and they can be customized and dragged to the design surface.

Silverlight Data Sources Window

This looks very cool but unfortunately in the current beta this window sometimes throws exceptions or hides the newly added Data Sources. Also the generated XAML code is not correct. Visual Studio will add CollectionViewSources as Resource elements. You should manually remove these Resource elements, remove the (run-time) DataContext and change the ItemSource databinding of the DataGrid. The current features of this Data Sources window are not really helpful (yet) when binding ViewModel properties.

Silverlight Data Sources Window

 

Hopefully the above walkthrough provides a good overview of the new design-time features which are useful when developing Silverlight applications which implement the M-V-VM pattern. I hope you are as enthusiastic as I am. The current beta does not always refresh correctly and sometimes exceptions or invalid XAML errors are shown. I’m sure that these issues will be solved in the final version. If you have any remarks or suggestions, please let me know.