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.

Kommentare:

  1. I really like what you've done here, but can't get it to work. Could you post the project source to download?

    AntwortenLöschen
  2. Ok, I figured it out.
    1) Need to reference System.Windows.Interactivity (http://expressionblend.codeplex.com/)
    2) Need a basic DelegateCommand
    public class DelegateCommand : ICommand
    {
    public event EventHandler CanExecuteChanged;
    public Func<object, bool> CanExecuteFunction;
    public Action<object> ExecuteAction;

    public bool CanExecute(object parameter)
    {
    if (CanExecuteFunction == null) return true;

    return CanExecuteFunction(parameter);
    }

    public void Execute(object parameter)
    {
    if (ExecuteAction == null) return;

    ExecuteAction(parameter);
    }

    }
    p.s. I didn't implement the constructor and just used parametrised construction for DelegateCommand

    AntwortenLöschen
  3. Thanks for the post, but this is not working for me. One different thing I did was assigning Datacontext to view dynamically.
    Can you please help me how to bind command when viewmodel is assigned dynamically?

    AntwortenLöschen