WPF : Blend 3 Interactions / Behaviours

As part of the new Blend3 release, there is a new Dll called “Microsoft.Expression.Interactivity.dll”. This Dll is a pretty cool thing, as it formalises a pattern that many WPF developers have probably already used, in their own manner. It basically formalises the “Attached Behaviour” pattern, which was possible prior to this Dll being available by the use of Attached DPs.

If you want to see a good example of Attached Behaviour using just DPs, have a look at Josh Smiths excellent www.codeproject.com article http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx it is very good.

As I say the “Microsoft.Expression.Interactivity.dll”, simply standardises this pattern a bit. So how does it all work:

Behaviours

Well behaviours can be added to any control quite easily, it is just a case of inheriting from the correct baseclass, and providing a couple of overrides for

  • OnAttached
  • OnDetatched

And then you simple need to attach behaviours to the UIElement(s) that you would like to use the behaviours. In XAML attaching behaviours looks like this:

   1:  <Window x:Class="BlendBehaviors.Window1"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      xmlns:interactivity="clr-namespace:Microsoft.Expression.Interactivity;assembly=Microsoft.Expression.Interactivity"
   5:      xmlns:local="clr-namespace:BlendBehaviors;assembly="  >
   6:  
   7:  <Rectangle Width="50" Height="50" Canvas.Left="10" Canvas.Top="10"
   8:          Fill="Aqua">
   9:  
  10:      <interactivity:Interaction.Behaviors>
  11:          <local:DragBehavior/>
  12:          <local:ResizeBehavior/>
  13:      </interactivity:Interaction.Behaviors>
  14:  </Rectangle>

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }Can you see that we are able to add behaviours by the use of a new property “Behaviours”. So that’s all cool so what does one of these here behaviours actually look like. Well there are a couple over at the Expression Blend gallery (http://gallery.expression.microsoft.com/site/items/behaviors), but for now lets have a look at one that I conjured up using. This one is a Resize Behaviour that when attached to an element will allow it to be resized using a ResizeAdorner which is hosted in the AdornerLayer.

ResizeBehavior

   1:  using System.Windows;
   2:  using System.Windows.Input;
   3:  using System.Windows.Media;
   4:  using Microsoft.Expression.Interactivity;
   5:  using System.Windows.Documents;
   6:  using System.Windows.Controls;
   7:  
   8:  
   9:  namespace BlendBehaviors
  10:  {
  11:  
  12:      /// <summary>
  13:      /// A simple Resizing Behavior that makes use
  14:      /// of a ResizingAdorner
  15:      /// </summary>
  16:      public class ResizeBehavior : Behavior<UIElement>
  17:      {
  18:          #region Data
  19:          private AdornerLayer adornerLayer;
  20:          private static Window parent;
  21:          private FrameworkElement fe;
  22:          private UIElement attachedElement;
  23:          #endregion
  24:  
  25:          #region Ctor
  26:          static ResizeBehavior()
  27:          {
  28:              parent = Application.Current.MainWindow;
  29:  
  30:          }
  31:          #endregion
  32:  
  33:          #region Behaviour Overrides
  34:  
  35:          protected override void OnAttached()
  36:          {
  37:              attachedElement = this.AssociatedObject;
  38:              fe = attachedElement as FrameworkElement;
  39:  
  40:              if (fe.Parent != null)
  41:              {
  42:                  (fe.Parent as FrameworkElement).Loaded += ResizeBehaviorParent_Loaded;
  43:              }
  44:          }
  45:  
  46:          protected override void OnDetaching()
  47:          {
  48:              base.OnDetaching();
  49:              if (adornerLayer != null)
  50:              {
  51:                  adornerLayer = null;
  52:              }
  53:          }
  54:          #endregion
  55:  
  56:          #region Private Methods
  57:          /// <summary>
  58:          /// Create the AdornerLayer when Parent for current Element loads
  59:          /// </summary>
  60:          private void ResizeBehaviorParent_Loaded(object sender, RoutedEventArgs e)
  61:          {
  62:              if (adornerLayer == null)
  63:                  adornerLayer = AdornerLayer.GetAdornerLayer(sender as Visual);
  64:              attachedElement.MouseEnter += AttachedElement_MouseEnter;
  65:          }
  66:  
  67:          /// <summary>
  68:          /// When mouse enters, create a new Resizing Adorner
  69:          /// </summary>
  70:          private void AttachedElement_MouseEnter(object sender, MouseEventArgs e)
  71:          {
  72:              ResizingAdorner resizingAdorner = new ResizingAdorner(sender as UIElement);
  73:              resizingAdorner.MouseLeave += ResizingAdorner_MouseLeave;
  74:              adornerLayer.Add(resizingAdorner);
  75:          }
  76:  
  77:          /// <summary>
  78:          /// On mouse leave for the Resizing Adorner, remove the Resizing Adorner
  79:          /// from the AdornerLayer
  80:          /// </summary>
  81:          private void ResizingAdorner_MouseLeave(object sender, MouseEventArgs e)
  82:          {
  83:              if (sender != null)
  84:              {
  85:                  adornerLayer.Remove(sender as ResizingAdorner);
  86:              }
  87:          }
  88:          #endregion
  89:  
  90:      }
  91:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }And the code for the ResizeAdorner  is one of the Microsoft SDK samples, which is as follows:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Text;
   4:  using System.Windows;
   5:  using System.Windows.Controls;
   6:  using System.Windows.Controls.Primitives;
   7:  using System.Windows.Documents;
   8:  using System.Windows.Input;
   9:  using System.Windows.Media;
  10:  
  11:  
  12:  ///*********************************************
  13:  ///
  14:  ///   This class was taken from the MSDN
  15:  ///   Adorners Overview samples page
  16:  /// 
  17:  ///*********************************************
  18:  
  19:  
  20:  namespace BlendBehaviors
  21:  {
  22:  
  23:      /// <summary>
  24:      /// A simple Resizing Adorner, that allows the user
  25:      /// to resize the Adorned element using 4 corner thumbs
  26:      /// </summary>
  27:      public class ResizingAdorner : Adorner
  28:      {
  29:          #region Data
  30:          // Resizing adorner uses Thumbs for visual elements.  
  31:          // The Thumbs have built-in mouse input handling.
  32:          Thumb topLeft, topRight, bottomLeft, bottomRight;
  33:  
  34:          // To store and manage the adorner's visual children.
  35:          VisualCollection visualChildren;
  36:          #endregion
  37:  
  38:          #region Ctor
  39:          // Initialize the ResizingAdorner.
  40:          public ResizingAdorner(UIElement adornedElement)
  41:              : base(adornedElement)
  42:          {
  43:              visualChildren = new VisualCollection(this);
  44:  
  45:              // Call a helper method to initialize the Thumbs
  46:              // with a customized cursors.
  47:              BuildAdornerCorner(ref topLeft, Cursors.SizeNWSE);
  48:              BuildAdornerCorner(ref topRight, Cursors.SizeNESW);
  49:              BuildAdornerCorner(ref bottomLeft, Cursors.SizeNESW);
  50:              BuildAdornerCorner(ref bottomRight, Cursors.SizeNWSE);
  51:  
  52:              // Add handlers for resizing.
  53:              bottomLeft.DragDelta += new DragDeltaEventHandler(HandleBottomLeft);
  54:              bottomRight.DragDelta += new DragDeltaEventHandler(HandleBottomRight);
  55:              topLeft.DragDelta += new DragDeltaEventHandler(HandleTopLeft);
  56:              topRight.DragDelta += new DragDeltaEventHandler(HandleTopRight);
  57:          }
  58:          #endregion
  59:  
  60:          #region Private Methods
  61:          // Handler for resizing from the bottom-right.
  62:          private void HandleBottomRight(object sender, DragDeltaEventArgs args)
  63:          {
  64:              FrameworkElement adornedElement = this.AdornedElement as FrameworkElement;
  65:              Thumb hitThumb = sender as Thumb;
  66:  
  67:              if (adornedElement == null || hitThumb == null) return;
  68:              FrameworkElement parentElement = adornedElement.Parent as FrameworkElement;
  69:  
  70:              // Ensure that the Width and Height are properly initialized after the resize.
  71:              EnforceSize(adornedElement);
  72:  
  73:              // Change the size by the amount the user drags the mouse, as long as it's larger 
  74:              // than the width or height of an adorner, respectively.
  75:              adornedElement.Width = Math.Max(adornedElement.Width + args.HorizontalChange,
  76:                  hitThumb.DesiredSize.Width);
  77:              adornedElement.Height = Math.Max(args.VerticalChange + adornedElement.Height,
  78:                  hitThumb.DesiredSize.Height);
  79:          }
  80:  
  81:          // Handler for resizing from the bottom-left.
  82:          private void HandleBottomLeft(object sender, DragDeltaEventArgs args)
  83:          {
  84:              FrameworkElement adornedElement = AdornedElement as FrameworkElement;
  85:              Thumb hitThumb = sender as Thumb;
  86:  
  87:              if (adornedElement == null || hitThumb == null) return;
  88:  
  89:              // Ensure that the Width and Height are properly initialized after the resize.
  90:              EnforceSize(adornedElement);
  91:  
  92:              // Change the size by the amount the user drags the mouse, as long as it's larger 
  93:              // than the width or height of an adorner, respectively.
  94:              adornedElement.Width = Math.Max(adornedElement.Width - args.HorizontalChange,
  95:                  hitThumb.DesiredSize.Width);
  96:  
  97:              adornedElement.Height = Math.Max(args.VerticalChange + adornedElement.Height,
  98:                  hitThumb.DesiredSize.Height);
  99:          }
 100:  
 101:          // Handler for resizing from the top-right.
 102:          private void HandleTopRight(object sender, DragDeltaEventArgs args)
 103:          {
 104:              FrameworkElement adornedElement = this.AdornedElement as FrameworkElement;
 105:              Thumb hitThumb = sender as Thumb;
 106:  
 107:              if (adornedElement == null || hitThumb == null) return;
 108:              FrameworkElement parentElement = adornedElement.Parent as FrameworkElement;
 109:  
 110:              // Ensure that the Width and Height are properly initialized after the resize.
 111:              EnforceSize(adornedElement);
 112:  
 113:              // Change the size by the amount the user drags the mouse, as long as it's larger 
 114:              // than the width or height of an adorner, respectively.
 115:              adornedElement.Width = Math.Max(adornedElement.Width + args.HorizontalChange,
 116:                  hitThumb.DesiredSize.Width);
 117:  
 118:              adornedElement.Height = Math.Max(adornedElement.Height - args.VerticalChange,
 119:                  hitThumb.DesiredSize.Height);
 120:          }
 121:  
 122:          // Handler for resizing from the top-left.
 123:          private void HandleTopLeft(object sender, DragDeltaEventArgs args)
 124:          {
 125:              FrameworkElement adornedElement = AdornedElement as FrameworkElement;
 126:              Thumb hitThumb = sender as Thumb;
 127:  
 128:              if (adornedElement == null || hitThumb == null) return;
 129:  
 130:              // Ensure that the Width and Height are properly initialized after the resize.
 131:              EnforceSize(adornedElement);
 132:  
 133:              // Change the size by the amount the user drags the mouse, as long as it's larger 
 134:              // than the width or height of an adorner, respectively.
 135:              adornedElement.Width = Math.Max(adornedElement.Width - args.HorizontalChange,
 136:                  hitThumb.DesiredSize.Width);
 137:  
 138:              adornedElement.Height = Math.Max(adornedElement.Height - args.VerticalChange,
 139:                  hitThumb.DesiredSize.Height);
 140:          }
 141:  
 142:          // Helper method to instantiate the corner Thumbs, set the Cursor property, 
 143:          // set some appearance properties, and add the elements to the visual tree.
 144:          private void BuildAdornerCorner(ref Thumb cornerThumb, Cursor customizedCursor)
 145:          {
 146:              if (cornerThumb != null) return;
 147:  
 148:              cornerThumb = new Thumb();
 149:  
 150:              // Set some arbitrary visual characteristics.
 151:              cornerThumb.Cursor = customizedCursor;
 152:              cornerThumb.Height = cornerThumb.Width = 10;
 153:              cornerThumb.Opacity = 0.40;
 154:              cornerThumb.Background = new SolidColorBrush(Colors.MediumBlue);
 155:  
 156:              visualChildren.Add(cornerThumb);
 157:          }
 158:  
 159:          // This method ensures that the Widths and Heights are initialized.  
 160:          // Sizing to content produces Width and Height values of Double.NaN.  
 161:          // Because this Adorner explicitly resizes, the Width and Height
 162:          // need to be set first.  It also sets the maximum size of the adorned element.
 163:          private void EnforceSize(FrameworkElement adornedElement)
 164:          {
 165:              if (adornedElement.Width.Equals(Double.NaN))
 166:                  adornedElement.Width = adornedElement.DesiredSize.Width;
 167:              if (adornedElement.Height.Equals(Double.NaN))
 168:                  adornedElement.Height = adornedElement.DesiredSize.Height;
 169:  
 170:              FrameworkElement parent = adornedElement.Parent as FrameworkElement;
 171:              if (parent != null)
 172:              {
 173:                  adornedElement.MaxHeight = parent.ActualHeight;
 174:                  adornedElement.MaxWidth = parent.ActualWidth;
 175:              }
 176:          }
 177:          #endregion
 178:  
 179:          #region Overrides
 180:          // Arrange the Adorners.
 181:          protected override Size ArrangeOverride(Size finalSize)
 182:          {
 183:              // desiredWidth and desiredHeight are the width and height of the element 
 184:              //that's being adorned.  
 185:              // These will be used to place the ResizingAdorner at the corners of the 
 186:              //adorned element.  
 187:              double desiredWidth = AdornedElement.DesiredSize.Width;
 188:              double desiredHeight = AdornedElement.DesiredSize.Height;
 189:              // adornerWidth & adornerHeight are used for placement as well.
 190:              double adornerWidth = this.DesiredSize.Width;
 191:              double adornerHeight = this.DesiredSize.Height;
 192:  
 193:              topLeft.Arrange(new Rect(-adornerWidth / 2, -adornerHeight / 2,
 194:                  adornerWidth, adornerHeight));
 195:  
 196:              topRight.Arrange(new Rect(desiredWidth - adornerWidth / 2,
 197:                  -adornerHeight / 2, adornerWidth, adornerHeight));
 198:  
 199:              bottomLeft.Arrange(new Rect(-adornerWidth / 2,
 200:                  desiredHeight - adornerHeight / 2, adornerWidth, adornerHeight));
 201:  
 202:              bottomRight.Arrange(new Rect(desiredWidth - adornerWidth / 2,
 203:                  desiredHeight - adornerHeight / 2, adornerWidth, adornerHeight));
 204:  
 205:              // Return the final size.
 206:              return finalSize;
 207:          }
 208:  
 209:          // Override the VisualChildrenCount and GetVisualChild properties to interface with 
 210:          // the adorner's visual collection.
 211:          protected override int VisualChildrenCount { get { return visualChildren.Count; } }
 212:          protected override Visual GetVisualChild(int index) { return visualChildren[index]; }
 213:          #endregion
 214:      }
 215:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }So when we run the attached demo app, we are able to resize any element that has this behaviour attached ( ok I am assuming the element is in a Panel etc etc, but you should get the idea)

image

DragBehavior

And here is another behaviour (from the Expression blend team), that allows an element to be dragged.

   1:  using System.Windows;
   2:  using System.Windows.Input;
   3:  using System.Windows.Media;
   4:  using Microsoft.Expression.Interactivity;
   5:  
   6:  ///*********************************************
   7:  ///
   8:  ///   This class was originally taken from the 
   9:  ///   Expression Blend team blog
  10:  /// 
  11:  ///*********************************************
  12:  
  13:  
  14:  namespace BlendBehaviors
  15:  {
  16:  
  17:      /// <summary>
  18:      /// A simple Resizing Behavior that makes use
  19:      /// of a ResizingAdorner
  20:      /// </summary>
  21:      public class DragBehavior : Behavior<UIElement>
  22:      {
  23:          #region Data
  24:          private bool isDragging = false;
  25:          private UIElement attachedElement;
  26:          private Window parent;
  27:          private Point lastPosition;
  28:          private TranslateTransform translatePosition;
  29:          #endregion
  30:  
  31:          #region Behaviour Overrides
  32:          protected override void OnAttached()
  33:          {
  34:              attachedElement = this.AssociatedObject;
  35:              parent = Application.Current.MainWindow;
  36:  
  37:              attachedElement.MouseLeftButtonDown += new MouseButtonEventHandler(MouseIsDown);
  38:              attachedElement.MouseLeftButtonUp += new MouseButtonEventHandler(MouseIsUp);
  39:              attachedElement.MouseMove += new MouseEventHandler(MouseIsMoving);
  40:          }
  41:          #endregion
  42:  
  43:          #region Private Methods
  44:          private void MouseIsMoving(object sender, MouseEventArgs e)
  45:          {
  46:              if (isDragging)
  47:              {
  48:                  Point currentPosition = e.GetPosition(parent);
  49:  
  50:                  double dX = currentPosition.X - lastPosition.X;
  51:                  double dY = currentPosition.Y - lastPosition.Y;
  52:  
  53:                  this.lastPosition = currentPosition;
  54:  
  55:                  Transform oldTransform = attachedElement.RenderTransform;
  56:                  TransformGroup rt = new TransformGroup();
  57:                  TranslateTransform newPos = new TranslateTransform();
  58:                  newPos.X = dX;
  59:                  newPos.Y = dY;
  60:  
  61:                  translatePosition = newPos;
  62:                  if (oldTransform != null)
  63:                  {
  64:                      rt.Children.Add(oldTransform);
  65:                  }
  66:                  rt.Children.Add(newPos);
  67:  
  68:                  MatrixTransform mt = new MatrixTransform();
  69:                  mt.Matrix = rt.Value;
  70:  
  71:                  if (currentPosition.X < 0 || currentPosition.Y < 0)
  72:                      return;
  73:  
  74:                  attachedElement.RenderTransform = mt;
  75:              }
  76:          }
  77:  
  78:          private void MouseIsUp(object sender, MouseButtonEventArgs e)
  79:          {
  80:              isDragging = false;
  81:  
  82:              attachedElement.ReleaseMouseCapture();
  83:          }
  84:  
  85:          private void MouseIsDown(object sender, MouseButtonEventArgs e)
  86:          {
  87:              isDragging = true;
  88:              lastPosition = e.GetPosition(parent);
  89:              attachedElement.CaptureMouse();
  90:          }
  91:          #endregion
  92:      }
  93:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

TargetedTriggerAction(s)

Another nice thing in the “Microsoft.Expression.Interactivity.dll”, is TargetedTriggerAction<T>, these are really cool, and allow an aribtary action to be performed against an Event. For example using these TargetedTriggerAction<T>, we are easily able to run a Command when a UIElement.MouseRightButtonUp occurs. Which previously was quite a task and actually almost would require some dynamically emitted assembly or IL.

Lets check these out next. Firstly in the XAML we can do something like

   1:  <Border CornerRadius="10" Background="WhiteSmoke"
   2:          Grid.Row="0" BorderBrush="Black" BorderThickness="5">
   3:  
   4:      <!-- Wire up a CommandAction that will fire an ICommand
   5:          when the event named by the EventTrigger EventName occurs-->
   6:      <interactivity:Interaction.Triggers>
   7:          <interactivity:EventTrigger EventName="MouseRightButtonUp">
   8:              <local:CommandAction Command="{Binding DemoCommand}"
   9:                           SyncOwnerIsEnabled="True" />
  10:          </interactivity:EventTrigger>
  11:      </interactivity:Interaction.Triggers>
  12:  
  13:  
  14:      <TextBlock Text="Right-click to fire demo ViewModel bound command"
  15:                 TextWrapping="Wrap" HorizontalAlignment="Center"
  16:                 VerticalAlignment="Center"/>
  17:  
  18:  
  19:  
  20:  </Border>

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }Which as you can see will fire the CommandAction when the MouseRightButtonUp RoutedEvent occurs for the Border in which the EventTrigger is declared. If we now focus our attention to the CommandAction implementation (again this is from the Expression Blend gallery http://gallery.expression.microsoft.com/site/items/behaviors) :

   1:  using System;
   2:  using System.ComponentModel;
   3:  using System.Windows;
   4:  using System.Windows.Input;
   5:  using Microsoft.Expression.Interactivity;
   6:  
   7:  
   8:  ///*********************************************
   9:  ///
  10:  ///   This class was originally taken from the 
  11:  ///   Expression Blend team blog
  12:  /// 
  13:  ///*********************************************
  14:  
  15:  namespace BlendBehaviors
  16:  {
  17:  
  18:  
  19:      /// <summary>
  20:      /// The CommandAction allows the user to route a FrameworkElement's 
  21:      /// routed event to a Command.
  22:      /// For instance this makes it possible to specify--in Xaml--that 
  23:      /// right-clicking on a Border element should execute the Application.Close 
  24:      /// command (this example may not make much sense, but it does illustrate 
  25:      /// what's possible).
  26:      /// 
  27:      /// CommandParameter and CommandTarget properties are provided for 
  28:      /// consistency with the Wpf Command pattern.
  29:      /// 
  30:      /// The action's IsEnabled property will be updated according to the 
  31:      /// Command's CanExecute value.
  32:      /// 
  33:      /// In addition a SyncOwnerIsEnabled property allows the user to specify 
  34:      /// that the owner element should be enabled/disabled whenever the action 
  35:      /// is enabled/disabled.
  36:      /// </summary>
  37:      public class CommandAction :
  38:          TargetedTriggerAction<FrameworkElement>,
  39:          ICommandSource
  40:      {
  41:          #region DPs
  42:  
  43:          #region Command DP
  44:          /// <summary>
  45:          /// The actual Command to fire when the 
  46:          /// EventTrigger occurs, thus firing this 
  47:          /// CommandAction
  48:          /// </summary>
  49:          [Category("Command Properties")]
  50:          public ICommand Command
  51:          {
  52:              get { return (ICommand)GetValue(CommandProperty); }
  53:              set { SetValue(CommandProperty, value); }
  54:          }
  55:  
  56:          public static readonly DependencyProperty CommandProperty =
  57:              DependencyProperty.Register(
  58:                  "Command", typeof(ICommand), typeof(CommandAction),
  59:                      new PropertyMetadata(
  60:                          (ICommand)null, OnCommandChanged));
  61:  
  62:          private static void OnCommandChanged(DependencyObject d,
  63:              DependencyPropertyChangedEventArgs e)
  64:          {
  65:              var action = (CommandAction)d;
  66:              action.OnCommandChanged((ICommand)e.OldValue, (ICommand)e.NewValue);
  67:          }
  68:  
  69:          #region Command implementation
  70:  
  71:          /// <summary>
  72:          /// This is a strong reference to the Command.CanExecuteChanged event handler. 
  73:          /// The commanding system uses a weak reference and if we don't enforce a 
  74:          /// strong reference then the event handler will be gc'ed.
  75:          /// </summary>
  76:          private EventHandler CanExecuteChangedHandler;
  77:  
  78:  
  79:  
  80:          private void OnCommandChanged(ICommand oldCommand, ICommand newCommand)
  81:          {
  82:              if (oldCommand != null)
  83:                  UnhookCommand(oldCommand);
  84:              if (newCommand != null)
  85:                  HookCommand(newCommand);
  86:          }
  87:  
  88:          private void UnhookCommand(ICommand command)
  89:          {
  90:              command.CanExecuteChanged -= CanExecuteChangedHandler;
  91:              UpdateCanExecute();
  92:          }
  93:  
  94:          private void HookCommand(ICommand command)
  95:          {
  96:              // Save a strong reference to the Command.CanExecuteChanged event handler. 
  97:              // The commanding system uses a weak reference and if we don't save a strong 
  98:              // reference then the event handler will be gc'ed.
  99:              CanExecuteChangedHandler = new EventHandler(OnCanExecuteChanged);
 100:              command.CanExecuteChanged += CanExecuteChangedHandler;
 101:              UpdateCanExecute();
 102:          }
 103:  
 104:          private void OnCanExecuteChanged(object sender, EventArgs e)
 105:          {
 106:              UpdateCanExecute();
 107:          }
 108:  
 109:          private void UpdateCanExecute()
 110:          {
 111:              if (Command != null)
 112:              {
 113:                  RoutedCommand command = Command as RoutedCommand;
 114:                  if (command != null)
 115:                      IsEnabled = command.CanExecute(CommandParameter, CommandTarget);
 116:                  else
 117:                      IsEnabled = Command.CanExecute(CommandParameter);
 118:                  if (Target != null && SyncOwnerIsEnabled)
 119:                      Target.IsEnabled = IsEnabled;
 120:              }
 121:          }
 122:  
 123:          #endregion
 124:  
 125:  
 126:          #endregion
 127:  
 128:          #region CommandParameter DP
 129:          /// <summary>
 130:          /// For consistency with the Wpf Command pattern
 131:          /// </summary>
 132:          [Category("Command Properties")]
 133:          public object CommandParameter
 134:          {
 135:              get { return (object)GetValue(CommandParameterProperty); }
 136:              set { SetValue(CommandParameterProperty, value); }
 137:          }
 138:  
 139:          public static readonly DependencyProperty CommandParameterProperty =
 140:              DependencyProperty.Register(
 141:                  "CommandParameter", typeof(object), typeof(CommandAction),
 142:                      new PropertyMetadata());
 143:          #endregion
 144:  
 145:          #region CommandTarget DP
 146:          /// <summary>
 147:          /// For consistency with the Wpf Command pattern
 148:          /// </summary>
 149:          [Category("Command Properties")]
 150:          public IInputElement CommandTarget
 151:          {
 152:              get { return (IInputElement)GetValue(CommandTargetProperty); }
 153:              set { SetValue(CommandTargetProperty, value); }
 154:          }
 155:  
 156:          public static readonly DependencyProperty CommandTargetProperty =
 157:              DependencyProperty.Register(
 158:                  "CommandTarget", typeof(IInputElement), typeof(CommandAction),
 159:                      new PropertyMetadata());
 160:          #endregion
 161:  
 162:          #region SyncOwnerIsEnabled DP
 163:          /// <summary>
 164:          /// Allows the user to specify that the owner element should be 
 165:          /// enabled/disabled whenever the action is enabled/disabled.
 166:          /// </summary>
 167:          [Category("Command Properties")]
 168:          public bool SyncOwnerIsEnabled
 169:          {
 170:              get { return (bool)GetValue(SyncOwnerIsEnabledProperty); }
 171:              set { SetValue(SyncOwnerIsEnabledProperty, value); }
 172:          }
 173:  
 174:          /// <summary>
 175:          /// When SyncOwnerIsEnabled is true then changing CommandAction.IsEnabled 
 176:          /// will automatically update the owner (Target) IsEnabled property.
 177:          /// </summary>
 178:          public static readonly DependencyProperty SyncOwnerIsEnabledProperty =
 179:              DependencyProperty.Register(
 180:                  "SyncOwnerIsEnabled", typeof(bool), typeof(CommandAction),
 181:                      new PropertyMetadata());
 182:          #endregion
 183:  
 184:          #endregion
 185:  
 186:          #region overrides
 187:          /// <summary>
 188:          /// Invoke is called when the EventTrigger associated with this
 189:          /// TargetedTriggerAction occurs. So we can obtain the associated 
 190:          /// ICommand and simply execute it
 191:          /// </summary>
 192:          protected override void Invoke(object o)
 193:          {
 194:              if (Command != null)
 195:              {
 196:                  var command = Command as RoutedCommand;
 197:                  if (command != null)
 198:                      command.Execute(CommandParameter, CommandTarget);
 199:                  else
 200:                      Command.Execute(CommandParameter);
 201:              }
 202:          }
 203:          #endregion
 204:      }
 205:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }It can be seen that this code is capable of executing a ICommand when the MouseRightButtonUp RoutedEvent occurs for the Border. Neat huh.

I have wired this up to a demoViewModel which has a single ICommand exposed, that is executed when this EventTrigger / CommandAction runs.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Collections.ObjectModel;
   4:  using System.IO;
   5:  using System.Linq;
   6:  using System.Text;
   7:  using System.Windows;
   8:  using System.Xml.Linq;
   9:  using System.Windows.Input;
  10:  using System.Linq.Expressions;
  11:  using System.Windows.Threading;
  12:  using System.Threading;
  13:  using System.Diagnostics;
  14:  
  15:  namespace BlendBehaviors
  16:  {
  17:  
  18:  
  19:  
  20:      /// <summary>
  21:      /// A small demo view model with a single
  22:      /// ICommand exposed, that will be executed
  23:      /// using the new Blend3 Interactivity
  24:      /// functionality, such as TargetedTriggerAction<T>
  25:      /// </summary>
  26:      public class DemoViewModel : ViewModelBase
  27:      {
  28:          #region Data
  29:          //Commands
  30:          private ICommand demoCommand = null;
  31:  
  32:          #endregion
  33:  
  34:          #region Ctor
  35:          public DemoViewModel()
  36:          {
  37:              //wire up command
  38:              demoCommand = new SimpleCommand
  39:              {
  40:                  CanExecuteDelegate = x => true,
  41:                  ExecuteDelegate = x =>
  42:                      {
  43:                          MessageBox.Show("In the ViewModel");
  44:                      }
  45:              };
  46:          }
  47:          #endregion
  48:  
  49:          #region Public Properties
  50:  
  51:          public ICommand DemoCommand
  52:          {
  53:              get { return demoCommand; }
  54:          }
  55:          #endregion
  56:      }
  57:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }Here is a screen shot (obviously the ViewModel shouldn’t show a MessageBox, this is for demonstration purposes only) :

image

Here is a small demo app with all this good stuff in it : blendbehaviors.zip

Advertisements

19 thoughts on “WPF : Blend 3 Interactions / Behaviours

  1. Thanks Sacha, great post.

  2. sacha says:

    Cheers Daniel

  3. uros says:

    Great as usual. Great info just in time 🙂

  4. Disore says:

    Think there is an error in the attached source code:

    ResizeBehaviour.cs ln 62

    if (adornerLayer != null)

    should be

    if (adornerLayer == null)

    Otherwise, as always, great article!

  5. sacha says:

    Uros thanks.

    Disore, yes I spotted that at home yesterday, must have forgotten to upload new zip/text.

    Ill fix that tonight. Well spotted.

  6. Great stuff, would be very interested in discussing a few projects with you. I’ve been on the creative side of 3D for 17 years, some dev, looking for someone for possible collaboration.

  7. sacha says:

    Joshua

    Glad you like it. Though its the Blend team that should get the credit.

    As far as extra projects go, having a full time job in London (I live 50 miles or 1 1/2 hours away) and having a passion to learn new things, leaves me no time for extra projects.

    Sorry but I must decline the offer, though thanks for asking.

  8. sacha says:

    Joshua, all fixed

  9. Marlon Grech says:

    NICE!!!!

    me like

  10. Amit says:

    Hi sacha

    As I cant seem to find a contact page here I had to leave a comment.

    I am running a .NET development blog and I very much like you writing.

    If you are interested in writing articles (with pay ofcourse) for Dev102 please contact me.

    Thanks

    Amit

  11. sacha says:

    Amit,

    Ill have a think about it

  12. geffry says:

    Hi Sacha,
    Blend 3 interactivity features are great, but I have a small question. Is it possible to attach a trigger/action to a ListBoxItem or ListViewItem without modifying the ContentTemplate?

    Thanks in advance.

  13. sacha says:

    I would have thought you could do it as above if you actually declare the ListBoxItem in XAML, but I guess you do not do that, and just Bind to some List of objects as an ItemSource right?

    Too be honest don’t know off the top of my head

  14. geffry says:

    Hi again Sacha,

    There is a common scenario. I have a ListBox, ItemsSource an ObservableCollection of Customers. I want to execute a command when double clicking on a Contact (on the ListBoxItem that is automatically generated by the ItemContainerGenerator). I want to attach a custom made trigger (DoubleClickTrigger – source here: http://blog.kirupa.com/?p=361) and use CommandAction for this trigger to invoke a command from the ViewModel. The problem is that I cannot attach this trigger using styles. Any idea? Thanks

  15. kdawg says:

    Sacha,

    Thanks for putting this together.

    I’m going to start using the Command implementation you presented here.

    Feels just like WPF.

    Cheers!

    kdawg

  16. benj says:

    Hello

    I have question about Behavior

    Like a Control ( [ToolboxBrowsable(false)] ), it is possible to hide a behavior in the asset library?

    Thanks

    Benj

  17. sacha says:

    Benj

    I am not aware of any way to hide a Behaviour

  18. benj says:

    Sacha

    Ok thanks

  19. scurnof says:

    Uncertain if it’s my internet browser since I still have zero free time to update and i’m always utilising Chrome 3 but while watching this site I seen some form of unusual characters everywhere in the page . It says it along with a number of different text right after it, similar to Warning: Not possible modify body string … and so on. Its a little bit odd because for me places such as by way of example yahoo.com and bing.com or simply my very own one insurance do show up without issues.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: