Blend Behaviors FTW

Update: As promised, here’s the code read below for usage.
AgilityCore.zip

Beyond SketchFlow (THE official coolest thing since sliced bread), Blend 3 has some other great features for improving the designer-developer workflow. Something I like a lot  (and most of my fellow WPF Disciples) are Behaviors.

If you recall, I spent a couple of posts a few years back talking about using attached properties to enable adding functionality to controls without code. Blend Behaviors take the pattern and wrap it in a simple API making it much easier to get down to the business of hacking WPF.

Even more interesting than the standard attached behaviors is the new Trigger system implemented using the pattern. Including the capability of creating custom Trigger Actions. In the initial release of WPF, Trigger Actions were effectively sealed because they are abstract and the function that inheritors need to override is marked internal. This effectively stood in the way of providing a solution to this problem in the MSDN forums.

The new Behavior-based trigger system (which works in Silverlight and WPF) opens the door for custom Trigger Actions. Now that we have custom Trigger Actions, I’m able to create an ExecuteCommandAction that allows you to invoke a Command from any trigger. The code is simple as is its usage.

First we define the trigger action

   1:  using System.Windows;
   2:  using System.Windows.Input;
   3:  using Microsoft.Expression.Interactivity;
   4:  using System.ComponentModel;
   5:   
   6:  namespace AzureCoding.Agility.Core.Behaviors
   7:  {
   8:      public class ExecuteCommandAction : TriggerAction<FrameworkElement>
   9:      {
  10:   
  11:          [Category("ExecuteCommandAction Properties"),
  12:          Description("The name of the Command that will be executed by this Trigger Action")]
  13:          public string TargetCommand
  14:          {
  15:              get { return (string)GetValue(TargetCommandProperty); }
  16:              set { SetValue(TargetCommandProperty, value); }
  17:          }
  18:   
  19:          public static readonly DependencyProperty TargetCommandProperty =
  20:              DependencyProperty.Register("Target Command",
  21:                                          typeof(string),
  22:                                          typeof(ExecuteCommandAction),
  23:                                          new PropertyMetadata
  24:                                              (TargetCommandChanged));
  25:   
  26:          private static void TargetCommandChanged(DependencyObject d,
  27:              DependencyPropertyChangedEventArgs e)
  28:          {
  29:   
  30:          }
  31:   
  32:          protected override void Invoke(object parameter)
  33:          {
  34:              var path = TargetCommand;
  35:              var dc = AssociatedObject.DataContext;
  36:              var targetCommand = 
  37:                  dc.GetType().GetProperty(path).GetValue(dc, null) as ICommand;
  38:   
  39:              if (targetCommand != null && targetCommand.CanExecute(parameter))
  40:              {
  41:                  targetCommand.Execute(parameter);
  42:              }
  43:          }
  44:      }
  45:  }

 

The new Behaviors framework is defined in the Microsoft.Expression.Interactivity namespace (in the assembly by the same name). We inherit from TriggerAction, a descendant of the base behavior class and declare a dependency property for the name of the command we want invoked (lines 11 – 30). Finally, we override the Invoke method from the TriggerAction class. Unfortunately, it appears that Binding does not work for TriggerActions and I haven’t heard if this will be addressed in the final version of the behaviors framework. At least for now we have to get a handle to the target command manually. Once that’s done, we just check that it can execute and then ask it to do so. The code uses the convention that the command resides on the Data Context of the object to which your trigger is attached. I also didn’t bother putting in robust error checking (so if the property doesn’t exist, you’ll get an exception).

image 
Once we’ve built the library and referenced it in a Blend 3 project, we can see our new behavior in the asset library

custombehaviors
Adding it to a target is as simple as dragging it to the objects and timeline panel. From there we’re able to edit the properties using the property editor.

So how do we use it? Well The convention of looking for the Command on the attached control’s data context works very well with the MVVM pattern. So let’s make a simple login control and matching View Model to drive it. Here’s the XAML for the Control

<UserControl
<!-- A bunch of xmlns definitions—> 
> <UserControl.DataContext> <AgVM:LoginViewModel/> </UserControl.DataContext> <Grid x:Name="LayoutRoot" Background="White"> <!-- A bunch of layout definitions--> <Button Margin="0,0,5,0" VerticalAlignment="Bottom" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="2" Content="Login"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <Ag:ExecuteCommandAction TargetCommand="LoginCommand"/> </i:EventTrigger> </i:Interaction.Triggers> </Button>
<!—More UI code--> </Grid> </UserControl>

There is no custom code-behind for the control. The ViewModel is instantiated in the XAML and all the elements are bound to properties on the ViewModel using data binding. Here is our ViewModel (or the significant parts thereof):

    public class LoginViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public LoginViewModel()
        {
            PropertyChanged += (blank, blank1) => { };
            LoginCommand = new DelegatingCommand
                               {
                                   CanExecuteRequested = param => true,
                                   ExecuteRequested = param => ShowLogin()
                               };
        }

        private void ShowLogin()
        {
            MessageBox.Show(String.Format("Login by {0} successful", _UserName));
        }

Here I’m using the DelegatingCommand to expose an ICommand as a property on my ViewModel. I’ve also exposed a UserName and Password field. Running the application gives us this.

runningthecode

So now we have trigger driven command execution with zero code behind. I’ll upload the code later.

Published Monday, April 06, 2009 3:12 PM by Mike Brown

Comments

# re: Blend Behaviors FTW

Cool stuff.  Is there a reason we're specifying the command as a string and doing reflection, though?  Why not specify as an ICommand and use the binding infrastructure?

Wednesday, April 08, 2009 8:17 AM by wekempf

# re: Blend Behaviors FTW

The new TriggerActions can't use binding unfortunately. I would have just gone that route myself. That was the first path I followed originally.

This could be cleaned up a bit by using the targeted trigger action though.

Thursday, May 21, 2009 12:46 PM by Mike Brown

# re: Blend Behaviors FTW

Can we have the code please?

Wednesday, July 29, 2009 2:42 AM by Trevoure

# re: Blend Behaviors FTW

Code is linked above...right at the top of the post

Wednesday, September 30, 2009 5:53 PM by Mike Brown

# TriggerAction driven command execution with zero code behind &laquo; vincenthome&#8217;s Tech Clips

Pingback from  TriggerAction driven command execution with zero code behind « vincenthome’s Tech Clips

Leave a Comment

(required) 
(required) 
(optional)
(required)