WPF

Meeting the MVVM Boys From Planet Microsoft

Yesterday I was lucky enough to be at the free MVVM training courses that Karl Shifflett and Jaime Rodriguez were running in London. The boys did a fantastic job in my opinion, despite them being asked some pretty hair questions. They covered loads of stuff, of particular merit were:

  • DataContext inheritance
  • Attached behaviours
  • Design time data
  • Services
  • MVVM frameworks

 

I have been waiting to meet Karl for ages, and I was not disappointed, the k-dawg is pure coolness wrapped up in a big Hawaiin shirt, go Karl. Basically Karl is an awesome dude and he was a real pleasure to watch in action. Jaime is also very very good and showed some very cool subject matter.

So if you get a chance to see any of their free courses DO IT.

Based on what I saw I now feel I have the last pieces to a puzzle, and I will be attempting to make my very own MVVM framework, that deals with all the problems we have come up against (which have been quite varied actually). Stay tuned for that one

Advertisements
WPF

WPF : MVVM VS Project Template

This weekend I was going to finally write up all my pains and worries and problems solved in a neat MVVM mini framework, and was really looking forward to it, and then I opened my email, and there was one from Lester. It seems that Microsoft have finally created a Visual Studio template to get you up to speed when creating WPF MVVM apps. Read more about it over at Lesters Blog : http://blogs.msdn.com/llobo/archive/2009/05/01/download-m-v-vm-project-template-toolkit.aspx

I have tried this out, and if you are just getting to grips with the MVVM pattern there are a couple of good features in there, such as the overall project structure that is created, I think this is good. Here is what you get

Structure

I also like the fact that the ICommand implementation (DelegateCommand) uses WeakReferences.

   1:  #region ICommand Members
   2:   
   3:  /// <summary>
   4:  ///     ICommand.CanExecuteChanged implementation
   5:  /// </summary>
   6:  public event EventHandler CanExecuteChanged
   7:  {
   8:      add
   9:      {
  10:          if (!_isAutomaticRequeryDisabled)
  11:          {
  12:              CommandManager.RequerySuggested += value;
  13:          }
  14:          CommandManagerHelper.AddWeakReferenceHandler(
  15:              ref _canExecuteChangedHandlers, value, 2);
  16:      }
  17:      remove
  18:      {
  19:          if (!_isAutomaticRequeryDisabled)
  20:          {
  21:              CommandManager.RequerySuggested -= value;
  22:          }
  23:          CommandManagerHelper.RemoveWeakReferenceHandler(
  24:              _canExecuteChangedHandlers, value);
  25:      }
  26:  }
  27:   
  28:  bool ICommand.CanExecute(object parameter)
  29:  {
  30:      return CanExecute();
  31:  }
  32:   
  33:  void ICommand.Execute(object parameter)
  34:  {
  35:      Execute();
  36:  }
  37:   
  38:  #endregion

Notice the CommandManagerHelper class which looks like:

.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; }

   1:  /// <summary>
   2:  ///     This class contains methods for the CommandManager that help avoid 
   3:  ///    memory leaks by using weak references.
   4:  /// </summary>
   5:  internal class CommandManagerHelper
   6:  {
   7:      internal static void CallWeakReferenceHandlers(List<WeakReference> handlers)
   8:      {
   9:          if (handlers != null)
  10:          {
  11:              // Take a snapshot of the handlers before we call out to them since the handlers
  12:              // could cause the array to me modified while we are reading it.
  13:   
  14:              EventHandler[] callees = new EventHandler[handlers.Count];
  15:              int count = 0;
  16:   
  17:              for (int i = handlers.Count - 1; i >= 0; i--)
  18:              {
  19:                  WeakReference reference = handlers[i];
  20:                  EventHandler handler = reference.Target as EventHandler;
  21:                  if (handler == null)
  22:                  {
  23:                      // Clean up old handlers that have been collected
  24:                      handlers.RemoveAt(i);
  25:                  }
  26:                  else
  27:                  {
  28:                      callees[count] = handler;
  29:                      count++;
  30:                  }
  31:              }
  32:   
  33:              // Call the handlers that we snapshotted
  34:              for (int i = 0; i < count; i++)
  35:              {
  36:                  EventHandler handler = callees[i];
  37:                  handler(null, EventArgs.Empty);
  38:              }
  39:          }
  40:      }
  41:   
  42:      internal static void AddHandlersToRequerySuggested(
  43:         List<WeakReference> handlers)
  44:      {
  45:          if (handlers != null)
  46:          {
  47:              foreach (WeakReference handlerRef in handlers)
  48:              {
  49:                  EventHandler handler = handlerRef.Target as EventHandler;
  50:                  if (handler != null)
  51:                  {
  52:                      CommandManager.RequerySuggested += handler;
  53:                  }
  54:              }
  55:          }
  56:      }
  57:   
  58:      internal static void RemoveHandlersFromRequerySuggested(
  59:         List<WeakReference> handlers)
  60:      {
  61:          if (handlers != null)
  62:          {
  63:              foreach (WeakReference handlerRef in handlers)
  64:              {
  65:                  EventHandler handler = handlerRef.Target as EventHandler;
  66:                  if (handler != null)
  67:                  {
  68:                      CommandManager.RequerySuggested -= handler;
  69:                  }
  70:              }
  71:          }
  72:      }
  73:   
  74:      internal static void AddWeakReferenceHandler(
  75:        ref List<WeakReference> handlers, EventHandler handler)
  76:      {
  77:          AddWeakReferenceHandler(ref handlers, handler, -1);
  78:      }
  79:   
  80:      internal static void AddWeakReferenceHandler(
  81:        ref List<WeakReference> handlers, EventHandler handler, 
  82:        int defaultListSize)
  83:      {
  84:          if (handlers == null)
  85:          {
  86:              handlers = (defaultListSize > 0 ? new 
                   List<WeakReference>(defaultListSize) : new List<WeakReference>());
  87:          }
  88:   
  89:          handlers.Add(new WeakReference(handler));
  90:      }
  91:   
  92:      internal static void RemoveWeakReferenceHandler(
  93:        List<WeakReference> handlers, EventHandler handler)
  94:      {
  95:          if (handlers != null)
  96:          {
  97:              for (int i = handlers.Count - 1; i >= 0; i--)
  98:              {
  99:                  WeakReference reference = handlers[i];
 100:                  EventHandler existingHandler = reference.Target as EventHandler;
 101:                  if ((existingHandler == null) || (existingHandler == handler))
 102:                  {
 103:                      // Clean up old handlers that have been collected
 104:                      // in addition to the handler that is to be removed.
 105:                      handlers.RemoveAt(i);
 106:                  }
 107:              }
 108:          }
 109:      }
 110:  }

.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; }

 

I think I could be tempted to use this from now on. And one more thing I like is the CommandReference class which allows a Key to be associated with an ICommand in XAML like

   1:  <Window x:Class="WpfModelViewApplication1.Views.MainView"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      xmlns:c="clr-namespace:WpfModelViewApplication1.Commands"
   5:      Title="Main Window" Height="400" Width="800">
   6:      
   7:      <Window.Resources>
   8:          <!-- Allows a KeyBinding to be associated with a 
   9:          command defined in the View Model  -->
  10:          <c:CommandReference x:Key="ExitCommandReference" 
  11:                              Command="{Binding ExitCommand}" />
  12:      </Window.Resources>
  13:     
  14:      <Window.InputBindings>
  15:          <KeyBinding Key="X" Modifiers="Control" 
  16:                      Command="{StaticResource ExitCommandReference}" /> 
  17:      </Window.InputBindings>
  18:      
  19:      <DockPanel>
  20:          <Menu DockPanel.Dock="Top">
  21:              <MenuItem Header="_File">
  22:                  <MenuItem Command="{Binding ExitCommand}" Header="E_xit" 
  23:                            InputGestureText="Ctrl-X" />
  24:              </MenuItem>
  25:          </Menu>
  26:      
  27:          <Grid>
  28:              <!-- Add additional content here -->
  29:          </Grid>
  30:      </DockPanel>
  31:  </Window>

But as for the rest, it is nothing that the WPF Disciples have not been doing for ages. What I am really disappointed by, is the lack of thought about services such : OpenFile/MessageBox/OpenDialog etc etc.

These are very very important, and far harder to get right that the basics.

In closing, I would say this is a step in the right direction, but some work still needs to be done to bridge the gap. I am not saying a full blown CAL/Prism template is required, but thought needs to be given to services. This is what is most likely to trips newbie WPFers up, I mean I have been working with WPF a while now, and I only just get it. What hope has a newbie got.

Looks like I will be writing my stuff up after all. Yipee

C#, CodeProject, WPF

WPF : Attached Commands

In my last post I showed you how to execute ViewModel ICommand(s) from FrameworkElement using the new Blend 3 Behaviours Dll. Well this time I want to show you how to run a ViewModel ICommand from any FrameworkElement without the use of the Blend 3 Behaviours Dll.

I should state that this post was inspired by a good friend of mine Marlon Grechs AttachedCommandBehavior V2 aka ACB blog post. I loved and have used Marlons idea, but he uses dynamically created IL, to create the correct type of RoutedEvent handler that matches the event you are trying to use to trigger the ICommand. It is way cool, but I couldn’t help but think there was an easier way. I like simple things, they suit me, as I am a simple man.

So what I thought was why not just use an Attached DP or 2, and a bit of Reflection, and Event hooking/Binding and ala-kazam, job done.

Here is how, the ViewModel looks like this

   1:  /// <summary>
   2:  /// A small demo view model with a single
   3:  /// ICommand exposed, that will be executed
   4:  /// using the new Blend3 Interactivity
   5:  /// functionality, such as TargetedTriggerAction<T>
   6:  /// </summary>
   7:  public class DemoViewModel : ViewModelBase
   8:  {
   9:      #region Data
  10:      //Commands
  11:      private ICommand demoCommand = null;
  12:  
  13:      #endregion
  14:  
  15:      #region Ctor
  16:      public DemoViewModel()
  17:      {
  18:          //wire up command
  19:          demoCommand = new SimpleCommand
  20:          {
  21:              CanExecuteDelegate = x => true,
  22:              ExecuteDelegate = x =>
  23:                  {
  24:                      MessageBox.Show("In the ViewModel");
  25:                  }
  26:          };
  27:      }
  28:      #endregion
  29:  
  30:      #region Public Properties
  31:  
  32:      public ICommand DemoCommand
  33:      {
  34:          get { return demoCommand; }
  35:      }
  36:      #endregion
  37:  }

I am actually using Marlon Grechs  SimpleCommand but if you prefer you can use Prisms DelegateCommand, or Josh Smiths RelayCommand, they all work.

So next all I do is create some attached behaviour using the magic of Attached DPs. Here is the full code.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Windows;
   6:  using System.Windows.Input;
   7:  using System.Reflection;
   8:  using System.Windows.Media;
   9:  
  10:  namespace AttachedCommands
  11:  {
  12:      public static class CommandBehavior
  13:      {
  14:          #region TheCommandToRun
  15:  
  16:          /// <summary>
  17:          /// TheCommandToRun : The actual ICommand to run
  18:          /// </summary>
  19:          public static readonly DependencyProperty TheCommandToRunProperty =
  20:              DependencyProperty.RegisterAttached("TheCommandToRun",
  21:                  typeof(ICommand),
  22:                  typeof(CommandBehavior),
  23:                  new FrameworkPropertyMetadata((ICommand)null));
  24:  
  25:          /// <summary>
  26:          /// Gets the TheCommandToRun property.  
  27:          /// </summary>
  28:          public static ICommand GetTheCommandToRun(DependencyObject d)
  29:          {
  30:              return (ICommand)d.GetValue(TheCommandToRunProperty);
  31:          }
  32:  
  33:          /// <summary>
  34:          /// Sets the TheCommandToRun property.  
  35:          /// </summary>
  36:          public static void SetTheCommandToRun(DependencyObject d, ICommand value)
  37:          {
  38:              d.SetValue(TheCommandToRunProperty, value);
  39:          }
  40:          #endregion
  41:  
  42:          #region RoutedEventName
  43:  
  44:          /// <summary>
  45:          /// RoutedEventName : The event that should actually execute the
  46:          /// ICommand
  47:          /// </summary>
  48:          public static readonly DependencyProperty RoutedEventNameProperty =
  49:              DependencyProperty.RegisterAttached("RoutedEventName", typeof(String),
  50:              typeof(CommandBehavior),
  51:                  new FrameworkPropertyMetadata((String)String.Empty,
  52:                      new PropertyChangedCallback(OnRoutedEventNameChanged)));
  53:  
  54:          /// <summary>
  55:          /// Gets the RoutedEventName property.  
  56:          /// </summary>
  57:          public static String GetRoutedEventName(DependencyObject d)
  58:          {
  59:              return (String)d.GetValue(RoutedEventNameProperty);
  60:          }
  61:  
  62:          /// <summary>
  63:          /// Sets the RoutedEventName property.  
  64:          /// </summary>
  65:          public static void SetRoutedEventName(DependencyObject d, String value)
  66:          {
  67:              d.SetValue(RoutedEventNameProperty, value);
  68:          }
  69:  
  70:          /// <summary>
  71:          /// Hooks up a Dynamically created EventHandler (by using the 
  72:          /// <see cref="EventHooker">EventHooker</see> class) that when
  73:          /// run will run the associated ICommand
  74:          /// </summary>
  75:          private static void OnRoutedEventNameChanged(DependencyObject d,
  76:              DependencyPropertyChangedEventArgs e)
  77:          {
  78:              String routedEvent = (String)e.NewValue;
  79:  
  80:              //If the RoutedEvent string is not null, create a new
  81:              //dynamically created EventHandler that when run will execute
  82:              //the actual bound ICommand instance (usually in the ViewModel)
  83:              if (!String.IsNullOrEmpty(routedEvent))
  84:              {
  85:                  EventHooker eventHooker = new EventHooker();
  86:                  eventHooker.ObjectWithAttachedCommand = d;
  87:  
  88:                  EventInfo eventInfo = d.GetType().GetEvent(routedEvent,
  89:                      BindingFlags.Public | BindingFlags.Instance);
  90:  
  91:                  //Hook up Dynamically created event handler
  92:                  if (eventInfo != null)
  93:                  {
  94:                      eventInfo.AddEventHandler(d,
  95:                          eventHooker.GetNewEventHandlerToRunCommand(eventInfo));
  96:                  }
  97:              }
  98:          }
  99:          #endregion
 100:      }
 101:  
 102:      /// <summary>
 103:      /// Contains the event that is hooked into the source RoutedEvent
 104:      /// that was specified to run the ICommand
 105:      /// </summary>
 106:      sealed class EventHooker
 107:      {
 108:          #region Public Methods/Properties
 109:          /// <summary>
 110:          /// The DependencyObject, that holds a binding to the actual
 111:          /// ICommand to execute
 112:          /// </summary>
 113:          public DependencyObject ObjectWithAttachedCommand { get; set; }
 114:  
 115:          /// <summary>
 116:          /// Creates a Dynamic EventHandler that will be run the ICommand
 117:          /// when the user specified RoutedEvent fires
 118:          /// </summary>
 119:          /// <param name="eventInfo">The specified RoutedEvent EventInfo</param>
 120:          /// <returns>An Delegate that points to a new EventHandler
 121:          /// that will be run the ICommand</returns>
 122:          public Delegate GetNewEventHandlerToRunCommand(EventInfo eventInfo)
 123:          {
 124:              Delegate del = null;
 125:  
 126:              if (eventInfo == null)
 127:                  throw new ArgumentNullException("eventInfo");
 128:  
 129:              if (eventInfo.EventHandlerType == null)
 130:                  throw new ArgumentException("EventHandlerType is null");
 131:  
 132:              if (del == null)
 133:                  del = Delegate.CreateDelegate(eventInfo.EventHandlerType, this,
 134:                        GetType().GetMethod("OnEventRaised",
 135:                          BindingFlags.NonPublic |
 136:                          BindingFlags.Instance));
 137:  
 138:              return del;
 139:          }
 140:          #endregion
 141:  
 142:          #region Private Methods
 143:  
 144:          /// <summary>
 145:          /// Runs the ICommand when the requested RoutedEvent fires
 146:          /// </summary>
 147:          private void OnEventRaised(object sender, EventArgs e)
 148:          {
 149:              ICommand command = (ICommand)(sender as DependencyObject).
 150:                  GetValue(CommandBehavior.TheCommandToRunProperty);
 151:  
 152:              if (command != null)
 153:              {
 154:                  command.Execute(null);
 155:              }
 156:          }
 157:          #endregion
 158:      }
 159:  
 160:  }

.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; }NOTE :

If you look at the ICommand interface you will notice that the Execute method look like this

void Execute(
    Object parameter
)

I have not catered for the ICommand parameter in my implementation, but if you want to do that it should be simply a case of yet another attached property, which you could then feed into the EventHooker class shown above, and then pass to the command when executing. So instead of command.Execute(null); you could do something like command.Execute(commandParameter);

Basically I do not use the command parameter that often, so did not include it, but you could, it would be trivial.

The last thing to do is to show you how to use this in a View, here is how.

   1:  <Window x:Class="AttachedCommands.Window1"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      xmlns:local="clr-namespace:AttachedCommands"
   5:      Title="Window1" Height="300" Width="300">
   6:      <Grid>
   7:  
   8:          <Grid.RowDefinitions>
   9:              <RowDefinition Height="*"/>
  10:              <RowDefinition Height="*"/>
  11:          </Grid.RowDefinitions>
  12:  
  13:          <!-- Fires DemoCommand ICommand when MouseDown RoutedEvent occurs -->
  14:          <Grid Grid.Row="0"
  15:                  local:CommandBehavior.RoutedEventName="MouseDown"
  16:                  local:CommandBehavior.TheCommandToRun=
  17:                      "{Binding Path=DemoCommand}"
  18:                  Background="Orange">
  19:              <Label VerticalAlignment="Center" HorizontalAlignment="Center"
  20:                     Content="MouseDown to raise Command"/>
  21:  
  22:          </Grid>
  23:  
  24:          <!-- Fires  DemoCommand ICommand when MouseWheel RoutedEvent occurs -->
  25:          <Grid Grid.Row="1"
  26:                  local:CommandBehavior.RoutedEventName="MouseWheel"
  27:                  local:CommandBehavior.TheCommandToRun=
  28:                      "{Binding Path=DemoCommand}"
  29:                  Background="Pink">
  30:  
  31:              <Label VerticalAlignment="Center" HorizontalAlignment="Center"
  32:                     Content="MouseWheel to raise Command"/>
  33:  
  34:  
  35:          </Grid>
  36:      </Grid>
  37:  
  38:  </Window>

.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 here is a small screen shot of it running:

ABC

And as always here is a small demo project: attachedcommands.zip

Enjoy

.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; }