MVVM, Significant Projects (Shareable), WPF

Cinch Code Generated Is Out

I am very pleased to announce that I have now released a code generator for my Cinch MVVM framework:

Here is a screen shot of the finished article

opened

 

The cinch code generator article is available at www.codeproject.com using the link : http://www.codeproject.com/KB/WPF/CinchCodeGen.aspx

The source code is available as part of the main Cinch download, which I realise is a big download now, but I wanted to keep it all together so it was easy to keep code generator Cinch.dll reference synced.

Anyway the download link is over at the Cinch Codeplex site : http://cinch.codeplex.com/

Enjoy, I am off for a holiday now, so I will not be answering any questions for quite some time, sorry about that.

C#, MVVM

Loading Assemblies In Separate Directories Into New AppDomain

As some of you may know I have been working on a code generator for my Cinch MVVM framework, which I am pleased to say I am nearly done with. The last stumbling block has been that I need to extract a bunch of Namespaces from Assemblies that the main code referenced, which I want to do by the use of Reflection which is very easy. But I also wanted the Assemblies that I would need to examine loaded into a new AppDomain so that I could Unload the newly created AppDomain when I had finished Reflecting out the Namespaces from the Assemblies.

After many failed attempted and messing around with

  • Asembly.ReflectionOnlyLoad
  • AppDomain.Load(Byte[] rawAssembly)
  • Fusion paths
  • Probing in my App.Config
  • AppDomain Evidence and AppDomainSetup

 

I finally found nirvana and hit the sweet spot, which is as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Globalization;
using System.Security.Policy;
using System.Reflection;
using System.Diagnostics.CodeAnalysis;
 
namespace ConsoleApplication1
{
 
    /// <summary>
    /// Loads an assembly into a new AppDomain and obtains all the
    /// namespaces in the loaded Assembly, which are returned as a 
    /// List. The new AppDomain is then Unloaded.
    /// 
    /// This class creates a new instance of a 
    /// <c>AssemblyLoader</c> class
    /// which does the actual ReflectionOnly loading 
    /// of the Assembly into
    /// the new AppDomain.
    /// </summary>
    public class SeperateAppDomainAssemblyLoader
    {
        #region Public Methods
        /// <summary>
        /// Loads an assembly into a new AppDomain and obtains all the
        /// namespaces in the loaded Assembly, which are returned as a 
        /// List. The new AppDomain is then Unloaded
        /// </summary>
        /// <param name="assemblyLocation">The Assembly file 
        /// location</param>
        /// <returns>A list of found namespaces</returns>
        public List<String> LoadAssemblies(List<FileInfo> assemblyLocations)
        {
            List<String> namespaces = new List<String>();
 
            AppDomain childDomain = BuildChildDomain(
                AppDomain.CurrentDomain);
 
            try
            {
                Type loaderType = typeof(AssemblyLoader);
                if (loaderType.Assembly != null)
                {
                    var loader = 
                        (AssemblyLoader)childDomain.
                            CreateInstanceFrom(
                            loaderType.Assembly.Location, 
                            loaderType.FullName).Unwrap();
 
                    namespaces = loader.LoadAssemblies(
                        assemblyLocations);
                }
                return namespaces;
            }
 
            finally
            {
 
                AppDomain.Unload(childDomain);
            }
        }
        #endregion
 
        #region Private Methods
        /// <summary>
        /// Creates a new AppDomain based on the parent AppDomains 
        /// Evidence and AppDomainSetup
        /// </summary>
        /// <param name="parentDomain">The parent AppDomain</param>
        /// <returns>A newly created AppDomain</returns>
        private AppDomain BuildChildDomain(AppDomain parentDomain)
        {
            Evidence evidence = new Evidence(parentDomain.Evidence);
            AppDomainSetup setup = parentDomain.SetupInformation;
            return AppDomain.CreateDomain("DiscoveryRegion", 
                evidence, setup);
        }
        #endregion
 
 
        /// <summary>
        /// Remotable AssemblyLoader, this class 
        /// inherits from <c>MarshalByRefObject</c> 
        /// to allow the CLR to marshall
        /// this object by reference across 
        /// AppDomain boundaries
        /// </summary>
        class AssemblyLoader : MarshalByRefObject
        {
            #region Private/Internal Methods
            /// <summary>
            /// ReflectionOnlyLoad of single Assembly based on 
            /// the assemblyPath parameter
            /// </summary>
            /// <param name="assemblyPath">The path to the Assembly</param>
            [SuppressMessage("Microsoft.Performance", 
                "CA1822:MarkMembersAsStatic")]
            internal List<String> LoadAssemblies(
                List<FileInfo> assemblyLocations)
            {
                List<String> namespaces = new List<String>();
                try
                {
                    foreach (FileInfo assemblyLocation in 
                        assemblyLocations)
                    {
                        Assembly.ReflectionOnlyLoadFrom(
                            assemblyLocation.FullName);
                    }
 
                    foreach (Assembly reflectionOnlyAssembly 
                        in AppDomain.CurrentDomain.
                            ReflectionOnlyGetAssemblies())
                    {
                        foreach (Type type in 
                            reflectionOnlyAssembly.GetTypes())
                        {
                            if (!namespaces.Contains(
                                type.Namespace))
                                namespaces.Add(
                                    type.Namespace);
                        }                   
                    }
                    return namespaces;
                }
                catch (FileNotFoundException)
                {
                    /* Continue loading assemblies even if an assembly
                     * can not be loaded in the new AppDomain. */
                    return namespaces;
                }
            }
            #endregion
        }
    }
}

.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 you can use like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
 
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            SeperateAppDomainAssemblyLoader 
                appDomainAssemblyLoader = 
                new SeperateAppDomainAssemblyLoader();
 
            List<FileInfo> assemblies = new List<FileInfo>();
            String root = @"C:UserssachaDesktopAppDomainMonkery";
            assemblies.Add(new FileInfo(root + 
                @"ConsoleApplication1ClassLibrary1binDebugClassLibrary1.dll"));
            assemblies.Add(new FileInfo(root + 
                @"ConsoleApplication1ClassLibrary2binDebugClassLibrary2.dll"));
 
            foreach (String @namespace in 
                appDomainAssemblyLoader.
                    LoadAssemblies(assemblies))
            {
                Console.WriteLine(String.Format(
                    "Namespace found : {0}", @namespace));
            }              
            Console.ReadLine();
        }
    }
}

.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 results in the following :

image

 

I think this code is pretty useful and I hope you find as much use for it as I have. It took me long enough to figure this out, and many google searches were done and much consulting of APIs/picking friends knowledge was done to bring you this code, so enjoy it. It nearly killed me getting that one to work.

As usual here is a small demo app : http://sachabarber.net/wp-content/uploads/2009/09/AppDomainMonkery.zip

C#, MVVM, WPF

Cinch : A Code Generator Is Coming

A while ago I released my Cinch MVVM Library out there into the wild, and the reception it was got has been pretty good, so I thank you all for that. When I wrote Cinch, I was aware that there was a bit of code that I was still asking people to write, al  be it they would gain a lot too, such as support for ViewModel controlled data editability/editable object support etc etc.

But there was still a bit of code that users had to write on their own, even if once they inherited from the correct Cinch base class ViewModel (there are 3 choices for that by the way). So I thought how easy it would be to create a code generator to simplify this process. I am pleased to say I am practically done with this and it should be available from the main Cinch site (which has had the source code moved to codeplex for obvious reasons) within a week.

For now here are some earlier preview screen shots:

Creating a new ViewModel

To create a ViewModel all you will need to do is pick your base class, and then pick some properties. Where if the property type you want isn’t present, just go and add it, and it will then be saved and will then become available now and in future.

image

 

Creating a new property

image

Editing the list of globally available property types

Like I said if the property you want is not, you can edit the list of available properties (which are backed up to file), and then use them straight away

image

Saving a ViewModel so you can edit and generate the code later, or edit an existing ViewModels generated code

You can save a ViewModel configuration, so you can come back and re-edit or modify the code that will get generated.

image

Generating The Code

This part is clever, the Cinch Code Generator, will actually attempt to compile your code, and providing all the Types you used are available in the assumed standard Assemblies, It will give you a green or red light. A red light may occur as you picked a property type that is not known about, that may be a custom business object class that you plan to add later. You could fix this to be part of the expected assemblies by editing the Cinch Code generator code. If I have time I may add this feature.

Anyway shown below is a red light that comes out of the compile process, but you WILL always be able to override this and choose to generate the final code anyway. It’s YOUR choice.

image

If All Goes Well, Preview The Code

If everything goes fine, 2 new tabs are shown with the generated code, I decided to split this over a g.cs which is a totally generated code file and should never be hand edited and a .cs file which you can edit. However if you run the Cinch code generator and copy the files it produces, you should be careful not to overwrite your custom part, that would be bad.

image

 

So there you go that’ a taster of what’s coming. Hope you like it. It is not far off believe me, 1 – 2 weeks tops.

C#, MVVM, WPF

Selector DoubleClick Behaviour calling ViewModel ICommand

The other day I was at work and needed to use a WPF ListView (Selector) to call a ICommand in a ViewModel. Now we want to be good and use nice design approaches, so I thought about using the attached command capabilities of my Cinch MVVM framework. But then I thought ah I only want to call the ICommand when the user actually double clicks an Item in the ListView (Selector) and not when the double click anywhere else, like say a header, which the ListView certainly has.

So without further ado I set to work and came up with the following attached Behaviour DP

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Windows;
   4:  using System.Windows.Controls.Primitives;
   5:  using System.Windows.Input;
   6:  using System.Windows.Controls;
   7:   
   8:  namespace ListViewDoubleCLick
   9:  {
  10:      /// <summary>
  11:      /// Selector MouseDoubleClick calling ViewModel ICommand  
  12:      /// </summary>
  13:      public static class SelectorDoubleClickCommandBehavior
  14:      {
  15:          #region Attached DPs
  16:          #region HandleDoubleClick
  17:   
  18:          /// <summary>
  19:          /// HandleDoubleClick Attached Dependency Property
  20:          /// </summary>
  21:          public static readonly DependencyProperty 
  22:              HandleDoubleClickProperty =
  23:              DependencyProperty.RegisterAttached(
  24:              "HandleDoubleClick", 
  25:              typeof(bool), 
  26:              typeof(SelectorDoubleClickCommandBehavior),
  27:                  new FrameworkPropertyMetadata(false,
  28:                      new PropertyChangedCallback(
  29:                          OnHandleDoubleClickChanged)));
  30:   
  31:          /// <summary>
  32:          /// Gets the HandleDoubleClick property.  
  33:          /// </summary>
  34:          public static bool GetHandleDoubleClick(DependencyObject d)
  35:          {
  36:              return (bool)d.GetValue(HandleDoubleClickProperty);
  37:          }
  38:   
  39:          /// <summary>
  40:          /// Sets the HandleDoubleClick property. 
  41:          /// </summary>
  42:          public static void SetHandleDoubleClick(DependencyObject d, 
  43:              bool value)
  44:          {
  45:              d.SetValue(HandleDoubleClickProperty, value);
  46:          }
  47:   
  48:          /// <summary>
  49:          /// Hooks up a weak event against the source Selectors 
  50:          /// MouseDoubleClick if the Selector has asked for 
  51:          /// the HandleDoubleClick to be handled
  52:          /// 
  53:          /// If the source Selector has expressed an interest 
  54:          /// in not having its MouseDoubleClick handled 
  55:          /// the internal reference
  56:          /// </summary>
  57:          private static void OnHandleDoubleClickChanged(
  58:              DependencyObject d,
  59:              DependencyPropertyChangedEventArgs e)
  60:          {
  61:              Selector selector = d as Selector;
  62:   
  63:   
  64:              if (selector != null)
  65:              {
  66:                  if ((bool)e.NewValue)
  67:                  {
  68:                      selector.MouseDoubleClick -= OnMouseDoubleClick;
  69: 
  70: 
  71: 
  72: 

73: selector.MouseDoubleClick += OnMouseDoubleClick;

  74:                  }
  75:              }
  76:          }
  77:          #endregion
  78:   
  79:          #region TheCommandToRun
  80:   
  81:          /// <summary>
  82:          /// TheCommandToRun : The actual ICommand to run
  83:          /// </summary>
  84:          public static readonly DependencyProperty TheCommandToRunProperty =
  85:              DependencyProperty.RegisterAttached(
  86:                  "TheCommandToRun",
  87:                  typeof(ICommand),
  88:                  typeof(SelectorDoubleClickCommandBehavior),
  89:                  new FrameworkPropertyMetadata((ICommand)null));
  90:   
  91:          /// <summary>
  92:          /// Gets the TheCommandToRun property.  
  93:          /// </summary>
  94:          public static ICommand GetTheCommandToRun(DependencyObject d)
  95:          {
  96:              return (ICommand)d.GetValue(TheCommandToRunProperty);
  97:          }
  98:   
  99:          /// <summary>
 100:          /// Sets the TheCommandToRun property.  
 101:          /// </summary>
 102:          public static void SetTheCommandToRun(DependencyObject d, 
 103:              ICommand value)
 104:          {
 105:              d.SetValue(TheCommandToRunProperty, value);
 106:          }
 107:          #endregion
 108:          #endregion
 109:   
 110:          #region Private Methods
 111:   
 112:   
 113:          /// <summary>
 114:          /// Handle Selector.MouseDoubleClick but will 
 115:          /// only fire the associated ViewModel command 
 116:          /// if the MouseDoubleClick occurred over an actual
 117:          /// ItemsControl item. This is nessecary as if we 
 118:          /// are using a ListView we may have clicked the 
 119:          /// headers which are not items, so do not want the
 120:          /// associated ViewModel command to be run
 121:          /// </summary>
 122:          private static void OnMouseDoubleClick(object sender, 
 123:              MouseButtonEventArgs e)
 124:          {
 125:              //Get the ItemsControl and then get the item, and 
 126:              //check there is an actual item, as if we are using 
 127:              //a ListView we may have clicked the
 128:              //headers which are not items
 129:              ItemsControl listView = sender as ItemsControl;
 130:              DependencyObject originalSender = 
 131:                  e.OriginalSource as DependencyObject;
 132:              if (listView == null || originalSender == null) return;
 133:   
 134:              DependencyObject container = 
 135:                  ItemsControl.ContainerFromElement
 136:                  (sender as ItemsControl, 
 137:                  e.OriginalSource as DependencyObject);
 138:   
 139:              if (container == null || 
 140:                  container == DependencyProperty.UnsetValue) return;
 141:   
 142:              // found a container, now find the item.
 143:              object activatedItem = 
 144:                  listView.ItemContainerGenerator.
 145:                      ItemFromContainer(container);
 146:   
 147:              if (activatedItem != null)
 148:              {
 149:                  ICommand command = 
 150:                      (ICommand)(sender as DependencyObject).
 151:                      GetValue(TheCommandToRunProperty);
 152:   
 153:                  if (command != null)
 154:                  {
 155:                      if (command.CanExecute(null))
 156:                          command.Execute(null);
 157:                  }                
 158:              }
 159:          }
 160:          #endregion
 161:      }
 162:   
 163:  }

 

Where we would use this in XAML as follows:

   1:  <Window x:Class="ListViewDoubleCLick.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:ListViewDoubleCLick"
   5:      xmlns:interactivity="clr-       namespace:Microsoft.Expression.Interactivity;
   6:         assembly=Microsoft.Expression.Interactivity" 
   7:      Title="Window1" Height="600" Width="800" 
   8:      WindowStartupLocation="CenterScreen">
   9:      <Grid>
  10:   
  11:          <TabControl>
  12:              <TabItem Header="Attached DP Approach">
  13:                  <ListView ItemsSource="{Binding People}"
  14:                    IsSynchronizedWithCurrentItem="True"
  15:                    local:NaiveSelectorDoubleClickCommandBehavior.HandleDoubleClick="true"
  16:                    local:NaiveSelectorDoubleClickCommandBehavior.TheCommandToRun=
  17:                            "{Binding Path=DoItCommand}" >
  18:   
  19:                      <ListView.View>
  20:                          <GridView>
  21:                              <GridViewColumn Header="FirstName"
  22:                                      DisplayMemberBinding="{Binding FirstName}"
  23:                                      Width="80" />
  24:                              <GridViewColumn Header="LastName" 
  25:                                      DisplayMemberBinding="{Binding LastName}"  
  26:                                      Width="80"/>
  27:                          </GridView>
  28:                      </ListView.View>
  29:                  </ListView>
  30:              </TabItem>
  31:          </TabControl>
  32:      </Grid>
  33:   
  34:  </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; }

 

Or we could get really fancy and use the Blend 3 Microsoft.Expression.Interactivity.Dll which would look something like this :

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Windows;
   4:  using System.Windows.Controls.Primitives;
   5:  using System.Windows.Input;
   6:  using System.Windows.Controls;
   7:  using Microsoft.Expression.Interactivity;
   8:  using System.ComponentModel;
   9:   
  10:  namespace ListViewDoubleCLick
  11:  {
  12:      /// <summary>
  13:      /// Selector MouseDoubleClick calling ViewModel ICommand Behavior
  14:      /// using Blend3 Microsoft.Expression.Interactivity Dll
  15:      /// </summary>
  16:      public class InteractionsSelectorDoubleClickCommandAction :
  17:          TargetedTriggerAction<FrameworkElement>,
  18:          ICommandSource
  19:      {
  20:          #region DPs
  21:   
  22:          #region Command DP
  23:          /// <summary>
  24:          /// The actual Command to fire when the 
  25:          /// EventTrigger occurs, thus firing this 
  26:          /// InteractionsSelectorDoubleClickCommandAction
  27:          /// </summary>
  28:          [Category("Command Properties")]
  29:          public ICommand Command
  30:          {
  31:              get { return (ICommand)GetValue(CommandProperty); }
  32:              set { SetValue(CommandProperty, value); }
  33:          }
  34:   
  35:          public static readonly DependencyProperty CommandProperty =
  36:              DependencyProperty.Register(
  37:                  "Command", typeof(ICommand),
  38:                      typeof(InteractionsSelectorDoubleClickCommandAction),
  39:                      new PropertyMetadata(
  40:                          (ICommand)null, OnCommandChanged));
  41:   
  42:          private static void OnCommandChanged(DependencyObject d,
  43:              DependencyPropertyChangedEventArgs e)
  44:          {
  45:              var action = 
  46:                  (InteractionsSelectorDoubleClickCommandAction)d;
  47:              action.OnCommandChanged((ICommand)e.OldValue, 
  48:                  (ICommand)e.NewValue);
  49:          }
  50:   
  51:          #region Command implementation
  52:   
  53:          /// <summary>
  54:          /// This is a strong reference to the Command.
  55:          /// CanExecuteChanged event handler. 
  56:          /// The commanding system uses a weak 
  57:          /// reference and if we don't enforce a 
  58:          /// strong reference then the event 
  59:          /// handler will be gc'ed.
  60:          /// </summary>
  61:          private EventHandler CanExecuteChangedHandler;
  62:   
  63:   
  64:   
  65:          private void OnCommandChanged(ICommand oldCommand, 
  66:              ICommand newCommand)
  67:          {
  68:              if (oldCommand != null)
  69:                  UnhookCommand(oldCommand);
  70:              if (newCommand != null)
  71:                  HookCommand(newCommand);
  72:          }
  73:   
  74:          private void UnhookCommand(ICommand command)
  75:          {
  76:              command.CanExecuteChanged -= 
  77:                  CanExecuteChangedHandler;
  78:              UpdateCanExecute();
  79:          }
  80:   
  81:          private void HookCommand(ICommand command)
  82:          {
  83:              // Save a strong reference to the 
  84:              // Command.CanExecuteChanged event handler. 
  85:              // The commanding system uses a weak 
  86:              // reference and if we don't save a strong 
  87:              // reference then the event handler will be gc'ed.
  88:              CanExecuteChangedHandler = 
  89:                  new EventHandler(OnCanExecuteChanged);
  90:              command.CanExecuteChanged 
  91:                  += CanExecuteChangedHandler;
  92:              UpdateCanExecute();
  93:          }
  94:   
  95:          private void OnCanExecuteChanged(object sender, 
  96:              EventArgs e)
  97:          {
  98:              UpdateCanExecute();
  99:          }
 100:   
 101:          private void UpdateCanExecute()
 102:          {
 103:              if (Command != null)
 104:              {
 105:                  RoutedCommand command = 
 106:                       Command as RoutedCommand;
 107:                  if (command != null)
 108:                      IsEnabled = 
 109:                          command.CanExecute(
 110:                           CommandParameter, CommandTarget);
 111:                  else
 112:                      IsEnabled = 
 113:                          Command.CanExecute(CommandParameter);
 114:                  if (Target != null && SyncOwnerIsEnabled)
 115:                      Target.IsEnabled = IsEnabled;
 116:              }
 117:          }
 118:   
 119:          #endregion
 120:   
 121:   
 122:          #endregion
 123:   
 124:          #region CommandParameter DP
 125:          /// <summary>
 126:          /// For consistency with the Wpf Command pattern
 127:          /// </summary>
 128:          [Category("Command Properties")]
 129:          public object CommandParameter
 130:          {
 131:              get { return (object)GetValue(
 132:                  CommandParameterProperty); }
 133:              set { SetValue(CommandParameterProperty, value); }
 134:          }
 135:   
 136:          public static readonly DependencyProperty 
 137:              CommandParameterProperty =
 138:              DependencyProperty.Register(
 139:                  "CommandParameter", typeof(object), 
 140:                      typeof(InteractionsSelectorDoubleClickCommandAction),
 141:                          new PropertyMetadata());
 142:          #endregion
 143:   
 144:          #region CommandTarget DP
 145:          /// <summary>
 146:          /// For consistency with the Wpf Command pattern
 147:          /// </summary>
 148:          [Category("Command Properties")]
 149:          public IInputElement CommandTarget
 150:          {
 151:              get { return (IInputElement)GetValue(
 152:                  CommandTargetProperty); }
 153:              set { SetValue(CommandTargetProperty, value); }
 154:          }
 155:   
 156:          public static readonly DependencyProperty 
 157:              CommandTargetProperty =
 158:              DependencyProperty.Register(
 159:                  "CommandTarget", typeof(IInputElement), 
 160:                      typeof(InteractionsSelectorDoubleClickCommandAction),
 161:                          new PropertyMetadata());
 162:          #endregion
 163:   
 164:          #region SyncOwnerIsEnabled DP
 165:          /// <summary>
 166:          /// Allows the user to specify that the 
 167:          /// owner element should be 
 168:          /// enabled/disabled whenever the 
 169:          /// action is enabled/disabled.
 170:          /// </summary>
 171:          [Category("Command Properties")]
 172:          public bool SyncOwnerIsEnabled
 173:          {
 174:              get { return (bool)GetValue(SyncOwnerIsEnabledProperty); }
 175:              set { SetValue(SyncOwnerIsEnabledProperty, value); }
 176:          }
 177:   
 178:          /// <summary>
 179:          /// When SyncOwnerIsEnabled is true 
 180:          /// then changing 
 181:          /// InteractionsSelectorDoubleClickCommandAction.
 182:          /// IsEnabled 
 183:          /// will automatically update the owner 
 184:          /// (Target) IsEnabled property.
 185:          /// </summary>
 186:          public static readonly DependencyProperty 
 187:              SyncOwnerIsEnabledProperty =
 188:              DependencyProperty.Register(
 189:                  "SyncOwnerIsEnabled", typeof(bool), 
 190:                      typeof(InteractionsSelectorDoubleClickCommandAction),
 191:                      new PropertyMetadata());
 192:          #endregion
 193:   
 194:          #endregion
 195:   
 196:          #region Overrides
 197:          /// <summary>
 198:          /// On attached hook up our own MouseDoubleClick so we
 199:          /// can check we actually double click an item
 200:          /// </summary>
 201:          protected override void OnAttached()
 202:          {
 203:              base.OnAttached();
 204:              Selector s = this.AssociatedObject as Selector;
 205:              if (s != null)
 206:              {
 207:                  s.MouseDoubleClick += OnMouseDoubleClick;
 208:              }
 209:          }
 210:   
 211:          /// <summary>
 212:          /// On attached unhook the previously 
 213:          /// hooked MouseDoubleClick handler
 214:          /// </summary>
 215:          protected override void OnDetaching()
 216:          {
 217:              base.OnDetaching();
 218:              Selector s = this.AssociatedObject as Selector;
 219:              if (s != null)
 220:              {
 221:                  s.MouseDoubleClick -= OnMouseDoubleClick;
 222:              }
 223:          }
 224:   
 225:          //Must at least implement abstract member invoke
 226:          protected override void Invoke(object parameter)
 227:          {
 228:              //The logic for this is done in the OnMouseDoubleClick
 229:              //as we only wanto fire command if we are actually on an
 230:              //Item in the Selector. If the Selector is a ListView we
 231:              //may have headers so will not want to fire associated
 232:              //Command when a header is double clicked
 233:          }
 234:          #endregion
 235:   
 236:          #region Private Methods
 237:   
 238:          /// <summary>
 239:          /// Handle Selector.MouseDoubleClick but will 
 240:          /// only fire the associated ViewModel command 
 241:          /// if the MouseDoubleClick occurred over an actual
 242:          /// ItemsControl item. This is nessecary as if we 
 243:          /// are using a ListView we may have clicked the 
 244:          /// headers which are not items, so do not want the
 245:          /// associated ViewModel command to be run
 246:          /// </summary>
 247:          private static void OnMouseDoubleClick(object sender,
 248:              MouseButtonEventArgs e)
 249:          {
 250:              //Get the ItemsControl and then get the item, and 
 251:              //check there is an actual item, as if we are using 
 252:              //a ListView we may have clicked the
 253:              //headers which are not items
 254:              ItemsControl listView = sender as ItemsControl;
 255:              DependencyObject originalSender =
 256:                  e.OriginalSource as DependencyObject;
 257:              if (listView == null || originalSender == null) return;
 258:   
 259:              DependencyObject container =
 260:                  ItemsControl.ContainerFromElement
 261:                  (sender as ItemsControl,
 262:                  e.OriginalSource as DependencyObject);
 263:   
 264:              if (container == null ||
 265:                  container == DependencyProperty.UnsetValue) return;
 266:   
 267:              // found a container, now find the item.
 268:              object activatedItem =
 269:                  listView.ItemContainerGenerator.
 270:                      ItemFromContainer(container);
 271:   
 272:              if (activatedItem != null)
 273:              {
 274:                  ICommand command =
 275:                      (ICommand)(sender as DependencyObject).
 276:                      GetValue(TheCommandToRunProperty);
 277:   
 278:                  if (command != null)
 279:                  {
 280:                      if (command.CanExecute(null))
 281:                          command.Execute(null);
 282:                  }
 283:              }
 284:          }
 285:   
 286:          #endregion
 287:      }
 288:   
 289:  }

.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 we could use from XAML as follows:

   1:  <Window x:Class="ListViewDoubleCLick.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:ListViewDoubleCLick"
   5:      xmlns:interactivity="clr-namespace:Microsoft.Expression.Interactivity;assembly=Microsoft.Expression.Interactivity" 
   6:      Title="Window1" Height="600" Width="800" 
   7:      WindowStartupLocation="CenterScreen">
   8:      <Grid>
   9:   
  10:          <TabControl>
  11:               <TabItem Header="Using Blend3 Interactivity Dll" >
  12:                  <ListView ItemsSource="{Binding People}" 
  13:                    IsSynchronizedWithCurrentItem="True">
  14:   
  15:                      <interactivity:Interaction.Triggers>
  16:                          <interactivity:EventTrigger EventName="MouseDoubleClick">
  17:                              <local:InteractionsSelectorDoubleClickCommandAction 
  18:                                  Command="{Binding DoItCommand}" 
  19:                                  SyncOwnerIsEnabled="True" />
  20:                          </interactivity:EventTrigger>
  21:                      </interactivity:Interaction.Triggers>
  22:   
  23:                      <ListView.View>
  24:                          <GridView>
  25:                              <GridViewColumn Header="FirstName"
  26:                                      DisplayMemberBinding="{Binding FirstName}"
  27:                                      Width="80" />
  28:                              <GridViewColumn Header="LastName" 
  29:                                      DisplayMemberBinding="{Binding LastName}"  
  30:                                      Width="80"/>
  31:                          </GridView>
  32:                      </ListView.View>
  33:                  </ListView>
  34:              </TabItem>
  35:          </TabControl>
  36:          
  37:   
  38:   
  39:      </Grid>
  40:   
  41:  </Window>
 
As usual here is a small demo project:  

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

http://sachabarber.net/wp-content/uploads/2009/09/listviewdoubleclick.zip