January 2011 - Posts

As I discussed in my last post, some of the Silverlight controls does not support MVVM quite well out of the box without specific customizations. The Context Menu is another control that requires customizations for enabling data binding on the menu options. There are a few things that you might want to expose as view model for a menu item, such as the Text, the associated icon or the command that needs to be executed. That view model should look like this,

public class MenuItemModel
{
    public string Name { get; set; }
    public ICommand Command { get; set; }
    public Image Icon { get; set; }
    public object CommandParameter { get; set; }
}

This is how you can modify the built-in control to support data binding on the model above,

public class CustomContextMenu : ContextMenu
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        CustomMenuItem item = new CustomMenuItem();
        
        Binding commandBinding = new Binding("Command");
        item.SetBinding(CustomMenuItem.CommandProperty, commandBinding);
 
        Binding commandParameter = new Binding("CommandParameter");
        item.SetBinding(CustomMenuItem.CommandParameterProperty, commandParameter);
 
        return item;
    }
}
 
public class CustomMenuItem : MenuItem
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        CustomMenuItem item = new CustomMenuItem();
 
        Binding commandBinding = new Binding("Command");
        item.SetBinding(CustomMenuItem.CommandProperty, commandBinding);
 
        return item;
    }
}
The change is very similar to the one I made in the TreeView for manually data binding some of the Menu item properties to the model.

Once you applied that change in the control, you can define it in your XAML like this.

<toolkit:ContextMenuService.ContextMenu>
    <e:CustomContextMenu ItemsSource="{Binding MenuItems}">
        <e:CustomContextMenu.ItemTemplate>
           <DataTemplate>
               <StackPanel Orientation="Horizontal"  >
                   <ContentPresenter Margin="0 0 4 0" Content="{Binding Icon}" />
                   <TextBlock Margin="0" Text="{Binding Name, Mode=OneWay}" FontSize="12"/>
               </StackPanel>
           </DataTemplate>
        </e:CustomContextMenu.ItemTemplate>
    </e:CustomContextMenu>
</toolkit:ContextMenuService.ContextMenu>

The property MenuItems associated to the “ItemsSource” in the parent model just returns a list of supported options (menu items) in the context menu.

this.menuItems = new MenuItemModel[]
{
    new MenuItemModel
    {
        Name = "My Command",
        Command = new RelayCommand(OnCommandClick),
        Icon = ImageLoader.GetIcon("command.png")
    }
};

The only problem I found so far with this approach is that the context menu service does not support a HierarchicalDataTemplate in case you want to have an hierarchy in the context menu (MenuItem –> Sub menu items), but I guess we can live without that.

Posted by cibrax
Filed under: ,

MVVM (Model-View-ViewModel) is the pattern that you will typically choose for building testable user interfaces either in WPF or Silverlight. This pattern basically relies on the data binding support in those two technologies for mapping an existing model class (the view model) to the different parts of the UI or view.

Unfortunately, MVVM was not threated as first citizen for some of controls released out of the box in the Silverlight runtime or the Silverlight toolkit. That means that using data binding for implementing MVVM is not always something trivial and usually requires some customization in the existing controls.

In ran into different problems myself trying to fully support data binding in controls like the tree view or the context menu or things like drag & drop.  For that reason, I decided to write this post to show how the tree view control or the tree view items can be customized to support data binding in many of its properties.

In first place, you will typically use a tree view for showing hierarchical data so the view model somehow must reflect that hierarchy. An easy way to implement hierarchy in a model is to use a base item element like this one,

public abstract class TreeItemModel
{
    public abstract IEnumerable<TreeItemModel> Children;
}

You can later derive your concrete model classes from that base class. For example,

public class CustomerModel
{
    public string FullName { get; set; }
    public string Address { get; set; }
    public IEnumerable<OrderModel> Orders { get; set; }
}
 
public class CustomerTreeItemModel : TreeItemModel
{
    public CustomerTreeItemModel(CustomerModel customer)
    {
    }
 
    public override IEnumerable<TreeItemModel>  Children
    {
        get { // Return orders }
    }
}

The Children property in the CustomerTreeItem model implementation can return for instance an ObservableCollection<TreeItemModel> with the orders, so the tree view will automatically subscribe to all the changes in the collection.

You can bind this model to the tree view control in the UI by using a Hierarchical data template.

<e:TreeView x:Name="TreeView" ItemsSource="{Binding Customers}">
   <e:TreeView.ItemTemplate>
      <sdk:HierarchicalDataTemplate ItemsSource="{Binding Children}">
     <!-- TEMPLATE -->
      </sdk:HierarchicalDataTemplate>
   </e:TreeView.ItemTemplate>
</e:TreeView>

An interesting behavior with the Children property and the Hierarchical data template is that the Children property is only invoked before the expansion, so you can use lazy load at this point (The tree view control will not expand the whole tree in the first expansion).

The problem with using MVVM in this control is that you can not bind properties in model with specific properties of the TreeView item such as IsSelected or IsExpanded. Here is where you need to customize the existing tree view control to support data binding in tree items.

public class CustomTreeView : TreeView
{
    public CustomTreeView()
    {
    }
 
    protected override DependencyObject GetContainerForItemOverride()
    {
        CustomTreeViewItem tvi = new CustomTreeViewItem();
        
        Binding expandedBinding = new Binding("IsExpanded");
        expandedBinding.Mode = BindingMode.TwoWay;
        tvi.SetBinding(CustomTreeViewItem.IsExpandedProperty, expandedBinding);
        Binding selectedBinding = new Binding("IsSelected");
        selectedBinding.Mode = BindingMode.TwoWay;
        tvi.SetBinding(CustomTreeViewItem.IsSelectedProperty, selectedBinding);
        return tvi;
    }
}
 
public class CustomTreeViewItem : TreeViewItem
{
    public CustomTreeViewItem()
    {
        
    }
 
    protected override DependencyObject GetContainerForItemOverride()
    {
        CustomTreeViewItem tvi = new CustomTreeViewItem();
        Binding expandedBinding = new Binding("IsExpanded");
        expandedBinding.Mode = BindingMode.TwoWay;
        tvi.SetBinding(CustomTreeViewItem.IsExpandedProperty, expandedBinding);
        Binding selectedBinding = new Binding("IsSelected");
        selectedBinding.Mode = BindingMode.TwoWay;
        tvi.SetBinding(CustomTreeViewItem.IsSelectedProperty, selectedBinding);
        return tvi;
    }
}

You basically need to derive the TreeView and TreeViewItem controls to manually add a binding for the properties you need. In the example above, I am adding a binding for the “IsExpanded” and “IsSelected” properties in the items. The model for the tree items now needs to be extended to support those properties as well,

public abstract class TreeItemModel : INotifyPropertyChanged
{
    bool isExpanded = false;
    bool isSelected = false;
 
    public abstract IEnumerable<TreeItemModel> Children { get; }
 
    public bool IsExpanded
    {
        get 
        { 
            return isExpanded; 
        }
        set 
        { 
            isExpanded = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("IsExpanded"));
        }
    }
 
    public bool IsSelected
    {
        get
        {
            return isSelected;
        }
        set
        {
            isSelected = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
        }
    }
 
    public event PropertyChangedEventHandler PropertyChanged;
}

However, as soon as you use this custom tree view control, you lose all the automatic styles from the built-in toolkit themes because they are tied to the control type (TreeView in this case).  The only ugly workaround I found so far for this problem is to copy the styles from the Toolkit source code and reuse them in the application.

Posted by cibrax | 3 comment(s)
Filed under: ,
More Posts