Samstag, 19. Juni 2010

Silverlight: Binding Events to ICommand interface (MVVM)

If you want to bind Silverlight controls which does not implement the ICommand interface to an ICommand method of your viewmodel, you can use System.Windows.Interactivity with the following pattern. In this sample I have implemented a SaveCommand at the LostFocus-Event of a TextBox.

XAML
   1: <TextBox 
   2:                                    Text="{Binding Name, Converter={StaticResource UpperCaseConverter}, Mode=TwoWay}" 
   3:                                    Grid.Column="0"                           
   4:                                    HorizontalAlignment="Stretch" 
   5:                                    VerticalAlignment="Center" 
   6:                                    Width="Auto" 
   7:                                    FontSize="18" 
   8:                                    Template="{StaticResource TextBoxFadeOut}">
   9:                                    <Interactivity:Interaction.Triggers>
  10:                                        <Interactivity:EventTrigger EventName="LostFocus" >
  11:                                            <Helpers:InvokeDelegateCommandAction 
  12:                                                Command="{Binding DataContext.SaveKategorieCommand, ElementName=LayoutRoot}" CommandParameter="{Binding}"/>
  13:                                        </Interactivity:EventTrigger>
  14:                                    </Interactivity:Interaction.Triggers>
  15:                                </TextBox>
Viewmodel-Command Definition
   1: public ICommand SaveKategorieCommand { get; set; }
   2:  
   3: SaveKategorieCommand = new DelegateCommand(SaveKategorie);
Viewmodel-Command Implementation

   1: private void SaveKategorie(object param)
   2:        {
   3:            if (param != null && param is AnzeigeKategorie)
   4:            {
   5:                var anz = param as AnzeigeKategorie;
   6:                if (anz.HasChanges)
   7:                {
   8:                    _domainContextSynchronizer.AddSubmitChangesToQueue();
   9:                }
  10:            }
  11:        }
Helper-Method InvokeDelegateCommandAction
   1: public sealed class InvokeDelegateCommandAction : TriggerAction<DependencyObject>
   2:     {
   3:         /// <summary>
   4:         ///
   5:         /// </summary>
   6:         public static readonly DependencyProperty CommandParameterProperty =
   7:             DependencyProperty.Register("CommandParameter", typeof(object), typeof(InvokeDelegateCommandAction), null);
   8:         
   9:         /// <summary>
  10:         ///
  11:         /// </summary>
  12:         public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
  13:             "Command", typeof(ICommand), typeof(InvokeDelegateCommandAction), null);
  14:  
  15:         /// <summary>
  16:         ///
  17:         /// </summary>
  18:         public static readonly DependencyProperty InvokeParameterProperty = DependencyProperty.Register(
  19:             "InvokeParameter", typeof(object), typeof(InvokeDelegateCommandAction), null);
  20:  
  21:         private string commandName;
  22:  
  23:         /// <summary>
  24:         ///
  25:         /// </summary>
  26:         public object InvokeParameter
  27:         {
  28:             get
  29:             {
  30:                 return this.GetValue(InvokeParameterProperty);
  31:             }
  32:             set
  33:             {
  34:                 this.SetValue(InvokeParameterProperty, value);
  35:             }
  36:         }
  37:  
  38:         /// <summary>
  39:         ///
  40:         /// </summary>
  41:         public ICommand Command
  42:         {
  43:             get
  44:             {
  45:                 return (ICommand)this.GetValue(CommandProperty);
  46:             }
  47:             set
  48:             {
  49:                 this.SetValue(CommandProperty, value);
  50:             }
  51:         }
  52:  
  53:         /// <summary>
  54:         ///
  55:         /// </summary>
  56:         public string CommandName
  57:         {
  58:             get
  59:             {
  60:                 return this.commandName;
  61:             }
  62:             set
  63:             {
  64:                 if (this.CommandName != value)
  65:                 {
  66:                     this.commandName = value;
  67:                 }
  68:             }
  69:         }
  70:  
  71:         /// <summary>
  72:         ///
  73:         /// </summary>
  74:         public object CommandParameter
  75:         {
  76:             get
  77:             {
  78:                 return this.GetValue(CommandParameterProperty);
  79:             }
  80:             set
  81:             {
  82:                 this.SetValue(CommandParameterProperty, value);
  83:             }
  84:         }
  85:  
  86:         /// <summary>
  87:         ///
  88:         /// </summary>
  89:         /// <param name="parameter"></param>
  90:         protected override void Invoke(object parameter)
  91:         {
  92:             this.InvokeParameter = parameter;
  93:  
  94:             if (this.AssociatedObject != null)
  95:             {
  96:                 ICommand command = this.ResolveCommand();
  97:                 if ((command != null) && command.CanExecute(this.CommandParameter))
  98:                 {
  99:                     command.Execute(this.CommandParameter);
 100:                 }
 101:             }
 102:         }
 103:  
 104:         private ICommand ResolveCommand()
 105:         {
 106:             ICommand command = null;
 107:             if (this.Command != null)
 108:             {
 109:                 return this.Command;
 110:             }
 111:             var frameworkElement = this.AssociatedObject as FrameworkElement;
 112:             if (frameworkElement != null)
 113:             {
 114:                 object dataContext = frameworkElement.DataContext;
 115:                 if (dataContext != null)
 116:                 {
 117:                     PropertyInfo commandPropertyInfo = dataContext
 118:                         .GetType()
 119:                         .GetProperties(BindingFlags.Public | BindingFlags.Instance)
 120:                         .FirstOrDefault(
 121:                             p =>
 122:                             typeof(ICommand).IsAssignableFrom(p.PropertyType) &&
 123:                             string.Equals(p.Name, this.CommandName, StringComparison.Ordinal)
 124:                         );
 125:  
 126:                     if (commandPropertyInfo != null)
 127:                     {
 128:                         command = (ICommand)commandPropertyInfo.GetValue(dataContext, null);
 129:                     }
 130:                 }
 131:             }
 132:             return command;
 133:         }
 134:     }

At this way you can bind each event of any Silverlight control to a ICommand.

Samstag, 29. Mai 2010

DataTemplate Selector with Silverlight

With Windows Presentation Foundation (WPF) it is very easy to switch your datatemplate in dependency of for example the type of data you want to bind. But Silverlight does not support this “Out-of-box”. So we have investigated some time in our current project to get a similar mechanism. The idea for my solution is, that we have a area in our form which should contains dynamic objects.One of the conditions for the solution was that it should support the common MVVM pattern.

The key point of my solution is to use a DependencyProperty and bind this to our viewmodel instance.

Step 1: Write a user control for the dynamic area which binds the view model
   1: <UserControl x:Class="SDX.FinancialPlanning.Views.Uebersicht.EinzelItemView"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
   6:     xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
   7:              xmlns:Detail1="clr-namespace:SDX.FinancialPlanning.ViewModels.Detail" 
   8:              xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:CommonHelpers="clr-namespace:SDX.FinancialPlanning.CommonHelpers" 
   9:              xmlns:Detail="clr-namespace:SDX.FinancialPlanning.Views.Detail" 
  10:              mc:Ignorable="d"
  11:     d:DesignHeight="300" d:DesignWidth="400">
  12:     
  13:     <Detail:DetailsViewSelector Content="{Binding SelectedEntryDetails}"  TemplateType="{Binding SelectedEntryDetails}"  >
  14:         <Detail:DetailsViewSelector.Resources>
  15:            <DataTemplate
  16:             x:Key="DetailWertpapierView"
  17:                         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  18:                         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  19:                         xmlns:Control="clr-namespace:SDX.FinancialPlanning.Views.Detail">
  20:                 <Control:DetailWertpapierView />
  21:             </DataTemplate>
  22:             <DataTemplate x:Key="EmptyTemplate">
  23:             </DataTemplate>
  24:         </Detail:DetailsViewSelector.Resources>
  25:         
  26:        
  27:     </Detail:DetailsViewSelector>
  28: </UserControl>

The DetailsViewSelector have two bindings

  • Content: Binds the view model to the choosen control
  • TemplateType: Binds also the view model to select the view by naming comventions

In my sample I habe only one DataTemplate and a empty Data Template if no corresponding objects could be bound.

Step 2: Implement the Detail View Selector
   1: <ContentPresenter x:Class="SDX.FinancialPlanning.Views.Detail.DetailsViewSelector"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
   6:                   Content="{Binding}">
   7:     <ContentPresenter.Resources>
   8:     </ContentPresenter.Resources>
   9: </ContentPresenter>

Code for the Control:

   1: public partial class DetailsViewSelector : ContentPresenter
   2:    {
   3:  
   4:        public static readonly DependencyProperty TemplateTypeProperty =
   5:        DependencyProperty.Register("TemplateType", typeof(DetailViewModelBase),
   6:        typeof(DetailsViewSelector), new PropertyMetadata(new DetailWertpapierViewModel()));
   7:  
   8:        public DetailViewModelBase TemplateType
   9:        {
  10:            get { return (DetailViewModelBase)GetValue(TemplateTypeProperty); }
  11:            set { SetValue(TemplateTypeProperty, value); }
  12:        }
  13:  
  14:        public DetailsViewSelector()
  15:        {
  16:            Loaded += new RoutedEventHandler(OnLoaded);
  17:        }
  18:  
  19:        private void OnLoaded(object sender, RoutedEventArgs e)
  20:        {
  21:            DetailViewModelBase templateType = TemplateType;
  22:            string viewName = templateType.GetType().FullName.Replace("Model", "");
  23:            Type viewType = Type.GetType(viewName);
  24:            if (viewType == null)
  25:            {
  26:                ContentTemplate = (DataTemplate) Resources["EmptyTemplate"];
  27:            }
  28:            else
  29:            {
  30:                DataTemplate dataTemplate = (DataTemplate)Resources[viewType.Name];
  31:                ContentTemplate = dataTemplate;
  32:            }
  33:        }
  34:    }

To have a very flexible mechanism the DetailsViewSelector works with naming conventions to choice the View for the given view model.