CodeProject, WPF

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

C#, Lambdas / Anonomous delegates, LINQ, WPF

WPF : Dynamic Search Driven List Results (Now uses AppDomain)

A couple of posts ago I showed you all how to use CodeDom/CompilerServices and LINQ to create a dynamically created dll. A few people stated that this should have been in a new AppDomain (and it was already this way at work), so I have updated the example post to show you how to create a dynamic assembly in a new AppDomain. Have a look at the original post here : http://sachabarber.net/?p=496

WPF

WPF : Oynx demo project

I promised one of my WPF Disciples buddies (Bill Kempf) a while back that I would look into using his excellent WPF MVVM framework Oynx in a codeproject demo article. I am pleased to announce that I have now finished this article and it is available over at www.codeproject.com. Simply follow this link

http://www.codeproject.com/KB/WPF/WPFOynxApp.aspx

 

Here is a screen shot, its a 3D WPF article.

demo4

I have to say that I was pretty blown away with Oynx even though it doesn’t do everything, it does a great many good things. If you use WPF and want to write better cleaner code, you should check out the Oynx site, and my demo article.

Enjoy

WPF

WPF : Disciples Exemplar MVVM App / Reference

yesterday I posted a blog stating that the WPF Disciples would all be working on bringing their combined best practices together to form an exemplar reference application for the community. Work has started on this and there is already a fair bit of interest on this.

You can check it out over at our codeplex page : http://www.codeplex.com/mvvmref

if you plan to do any serious WPF development, this is the codeplex page for you.

 

Enjoy

Introduction

WPF : More Mediator Goodness

A while back I wrote a post about using the Mediator pattern, to allow ViewModels to communicate with each other easily and simply.

Now lately the wpf disciples have been talking about this pattern a lot. Josh Smith has written a better version that is more memory safe than my orginal version. You can read about Joshs version at his blog post. The wpf disciples have also been discussing the possibilities of released a best practice set of files when working with WPF/MVVM/Mediator, which will use bit of WPF knowledge from the whole group. This will be released over at codeplex. Watch out for that one

C#, CodeProject, LINQ, WPF

WPF : Dynamic Search Driven List Results

At work at the moment I am working on a way way cool customisable search that basically allows user to pick source entities and related entities and then pick what fields they would like to show, this is similar to Microsoft’s CRM Dynamics product, which allows very very sophisticated searches to be produces, by the use of a nice search UI.

Here is what CRM looks like. The search we actually built at work is even better than this, but for NDA reasons I can not show.

image

From our search we create a strongly typed Query object, which is sent across a WCF service boundary and when received at the other end, in converted to dynamic SQL, and is the  run against a SQL database. Yes that right we created our own LINQ effectively. Its call GNRSQL.

Anyway as a side effect from being able to search for anything from anywhere, we also needed the ability for our search results grid to dynamically adjust to any results set. To compound this our results objects are hierarchical in nature, and we are using the Infragistics XamDataGrid, which wants to show things in a hierarchical manner. Which is not what we wanted, so we needed to flatten the results from a hierarchy to a flat structure.

Your 1st thought might be, oh just send a DataTable across the WCF boundary, this is not a good idea, the serialization of DataTable(s) and WCF is very strange, and also incredibly heavy. Basically the namespaces get messed up and so do some of the internal field names, and my god the serialization is so far from pretty.

So we had to try some other things.

My next idea was to use IEnumerable and use LINQ to pluck out only those properties that we wanted to show based on the actually results obtained. Our results contained metadata that we could use to construct a LINQ query which would return a flattened IEnumerable of anonymous types, that had only the properties we wanted. Choosing of properties was driven by examining the actual results metadata. And all this has to happen at runtime. Doing this at design time is dead simple we can just do something like (assuming we have a list of results already)

var newFlattenedResults = (from x in results  select new { ID = x.IDField, DateOfOrder=x.OrderDate });

But how could you create something that could be used in this manner, and tailored to suit the properties returned by the search, but done at runtime. The rest of this post will show you how.

Dynamic Assemblies

.NET comes equipped with the ability to produce dynamic assemblies on the fly at runtime. So lets start there and have a look at a helper class for making this process easy.

   1:  using System.Reflection;
   2:  using System.CodeDom.Compiler;
   3:  using Microsoft.CSharp;
   4:  
   5:  using System;
   6:  using System.Collections.Generic;
   7:  using System.Text;
   8:  
   9:  namespace Dyno
  10:  {
  11:      /// <summary>
  12:      /// Utlilty class. Compliles and runs assemblies 
  13:      /// on the fly, with the option to invoke a method 
  14:      /// and return the results from the chosen
  15:      /// method
  16:      /// </summary>
  17:      public class DynamicCompiler
  18:      {
  19:          #region Public Methods
  20:          /// <summary>
  21:          /// Compiles the code and either returns the compiled code as a Type
  22:          /// or creates compiled code as a Type, and invokes the created Types
  23:          /// method. Where the method is picked by the user
  24:          /// </summary>
  25:          /// <typeparam name="T">Type of T to use</typeparam>
  26:          /// <param name="code">code to compile</param>
  27:          /// <param name="nameSpace">namespace to use for compiled code</param>
  28:          /// <param name="classToLoad">class name</param>
  29:          /// <param name="methodToRun">method to invoke (optional)</param>
  30:          /// <param name="ShouldImvokeMethod">true if you want to 
  31:          /// invoke the method</param>
  32:          /// <returns>Type of T to use</returns>
  33:          public T ComplileAndRun<T>(String code, String nameSpace,
  34:              String classToLoad, string methodToRun, Boolean ShouldImvokeMethod)
  35:          {
  36:              try
  37:              {
  38:                  String lcCode = code;
  39:  
  40:                  var provider = new CSharpCodeProvider(
  41:                          new Dictionary<String, String>()
  42:                              { { "CompilerVersion", "v3.5" } });
  43:  
  44:                  CompilerParameters parameters = new CompilerParameters();
  45:  
  46:                  // Start by adding any referenced assemblies
  47:                  parameters.ReferencedAssemblies.Add("System.dll");
  48:                  parameters.ReferencedAssemblies.Add(
  49:                      typeof(Demo.Data.Person).Assembly.Location);
  50:                  parameters.ReferencedAssemblies.Add(
  51:                      typeof(System.Linq.Enumerable).Assembly.Location);
  52:  
  53:  
  54:                  // Load the resulting assembly into memory
  55:                  parameters.GenerateInMemory = true;
  56:                  // Now compile the whole thing 
  57:                  //Must create a fully functional assembly as the code string
  58:                  CompilerResults compiledCode =
  59:                      provider.CompileAssemblyFromSource(parameters, lcCode);
  60:  
  61:                  if (compiledCode.Errors.HasErrors)
  62:                  {
  63:                      String errorMsg = String.Empty;
  64:                      errorMsg = compiledCode.Errors.Count.ToString() +
  65:                                 " n Dynamically generated code threw an error. n Errors:";
  66:  
  67:                      for (int x = 0; x < compiledCode.Errors.Count; x++)
  68:                      {
  69:                          errorMsg = errorMsg + "rnLine: " +
  70:                                     compiledCode.Errors[x].Line.ToString() + " - " +
  71:                                     compiledCode.Errors[x].ErrorText;
  72:                      }
  73:  
  74:                      throw new Exception(errorMsg);
  75:                  }
  76:  
  77:                  Assembly assembly = compiledCode.CompiledAssembly;
  78:  
  79:                  // Retrieve an obj ref – generic type only
  80:                  object instance = assembly.CreateInstance(
  81:                      nameSpace + "." + classToLoad);
  82:  
  83:                  //invoke the method if needs be, and get results 
  84:                  //back from method invocation
  85:                  if (ShouldImvokeMethod)
  86:                  {
  87:                      if (instance == null)
  88:                          return default(T);
  89:  
  90:                      T result = (T)instance.GetType().InvokeMember(
  91:                             methodToRun, BindingFlags.InvokeMethod,
  92:                             null, instance, new object[0]);
  93:  
  94:                      return result;
  95:                  }
  96:                  else
  97:                  {
  98:                      return (T)instance;
  99:                  }
 100:              }
 101:              catch (Exception ex)
 102:              {
 103:                  Console.WriteLine(String.Format(
 104:                      "An exception occurred {0}", ex.Message));
 105:                  return default(T);
 106:              }
 107:          }
 108:          #endregion
 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; }So using this helper class we can construct an in memory assembly and call a method within an object within it, or simply return the newly created object within the dynamic assembly.

So lets continue our journey, and look at a small XAML app, where there is a ListView that starts with the following columns

image

Where the XAML for this screen looks like this

   1:  <Window x:Class="DynamicLINQ.Window1"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:          WindowStartupLocation="CenterScreen"
   5:      Title="Window1" Height="300" Width="700">
   6:      <DockPanel LastChildFill="True">
   7:          <StackPanel DockPanel.Dock="Top" Orientation="Horizontal"
   8:                      HorizontalAlignment="Stretch" Background="CornflowerBlue">
   9:              <Button x:Name="ShowAll" Content="Show All" Margin="5"
  10:                      Click="ShowAll_Click"/>
  11:              <Button x:Name="ShowSome" Content="Show Some Columns" Margin="5"
  12:                      Click="ShowSome_Click"/>
  13:          </StackPanel>
  14:  
  15:  
  16:          <ListView x:Name="lvItems" >
  17:              <ListView.View>
  18:                  <GridView>
  19:                      <GridViewColumn Header="Age"
  20:                                      DisplayMemberBinding="{Binding Age}" />
  21:                      <GridViewColumn Header="FirstName"
  22:                                      DisplayMemberBinding="{Binding FirstName}" />
  23:                      <GridViewColumn Header="MiddleName"
  24:                                      DisplayMemberBinding="{Binding MiddleName}" />
  25:                      <GridViewColumn Header="LastName"
  26:                                      DisplayMemberBinding="{Binding LastName}" />
  27:                      <GridViewColumn Header="LastName"
  28:                                      DisplayMemberBinding="{Binding LastName}" />
  29:                      <GridViewColumn Header="ID"
  30:                                      DisplayMemberBinding="{Binding ID}" Width="230" />
  31:                      <GridViewColumn Header="DOB"
  32:                                      DisplayMemberBinding="{Binding Dob}" Width="130" />
  33:                  </GridView>
  34:              </ListView.View>
  35:          </ListView>
  36:  
  37:  
  38:      </DockPanel>
  39:  </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; }You can see that the initial columns for the displayed results are static at this point, and are showing the results of being bound to a List<Person> objects, where a Person looks like this

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.ComponentModel;
   6:  
   7:  namespace Demo.Data
   8:  {
   9:      public class Person : INotifyPropertyChanged
  10:      {
  11:          #region Data
  12:          public Int32 age;
  13:          public String firstName;
  14:          public String middleName;
  15:          public String lastName;
  16:          public AdditionalData personData;
  17:          #endregion
  18:  
  19:          #region Public Properties
  20:          public Int32 Age
  21:          {
  22:              get { return age; }
  23:              set
  24:              {
  25:                  age = value;
  26:                  NotifyPropertyChanged("Age");
  27:              }
  28:          }
  29:  
  30:          public String FirstName
  31:          {
  32:              get { return firstName; }
  33:              set
  34:              {
  35:                  firstName = value;
  36:                  NotifyPropertyChanged("FirstName");
  37:              }
  38:          }
  39:  
  40:          public String MiddleName
  41:          {
  42:              get { return middleName; }
  43:              set
  44:              {
  45:                  middleName = value;
  46:                  NotifyPropertyChanged("MiddleName");
  47:              }
  48:          }
  49:  
  50:          public String LastName
  51:          {
  52:              get { return lastName; }
  53:              set
  54:              {
  55:                  lastName = value;
  56:                  NotifyPropertyChanged("LastName");
  57:              }
  58:          }
  59:  
  60:          public AdditionalData PersonData
  61:          {
  62:              get { return personData; }
  63:              set
  64:              {
  65:                  personData = value;
  66:                  NotifyPropertyChanged("PersonData");
  67:              }
  68:          }
  69:          #endregion
  70:  
  71:          #region INotifyPropertyChanged region
  72:          public event PropertyChangedEventHandler PropertyChanged;
  73:  
  74:          private void NotifyPropertyChanged(String info)
  75:          {
  76:              if (PropertyChanged != null)
  77:              {
  78:                  PropertyChanged(this, new PropertyChangedEventArgs(info));
  79:              }
  80:          }
  81:          #endregion
  82:      }
  83:  
  84:      public class AdditionalData : INotifyPropertyChanged
  85:      {
  86:          #region Data
  87:          private Guid id;
  88:          private DateTime dob;
  89:          #endregion
  90:  
  91:          #region Public Properties
  92:          public Guid ID
  93:          {
  94:              get { return id; }
  95:              set
  96:              {
  97:                  id = value;
  98:                  NotifyPropertyChanged("Id");
  99:              }
 100:          }
 101:  
 102:          public DateTime Dob
 103:          {
 104:              get { return dob; }
 105:              set
 106:              {
 107:                  dob = value;
 108:                  NotifyPropertyChanged("Dob");
 109:              }
 110:          }
 111:          #endregion
 112:  
 113:          #region INotifyPropertyChanged region
 114:          public event PropertyChangedEventHandler PropertyChanged;
 115:  
 116:          private void NotifyPropertyChanged(String info)
 117:          {
 118:              if (PropertyChanged != null)
 119:              {
 120:                  PropertyChanged(this, new PropertyChangedEventArgs(info));
 121:              }
 122:          }
 123:          #endregion
 124:      }
 125:  
 126:  }

So now let us see what would happen if we simulated some other search results coming back, from somewhere else, that should alter what is shown in the search results ListView.


   1:  private void ShowSome_Click(object sender, RoutedEventArgs e)
   2:  {
   3:      DataLoader dl = new DataLoader();
   4:  
   5:      //show some columns from Dynamically compiled LINQ
   6:      //query
   7:      IEnumerable results =
   8:          dl.ObtainFlattenedResults(lvItems,
   9:              people, WritePropertyType.Some);
  10:  
  11:  
  12:      lvItems.ItemsSource = results;
  13:  
  14:  }

.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; }Where people is simply a list of Person objects set up with some Person objects in it (obviously I am faking the search results part, for the sake of this article, the Person objects, would actually be some search results or something coming from the results of a search, but for this example it doesn’t matter, as I am just trying to show you how to dynamically work with LINQ at runtime)

   1:  private List<Person> people = new List<Person>();

.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; }You can see from the above code that we use a method called ObtainFlattenedResults() which is available in the following helper class

   1:  using System;
   2:  using System.Collections;
   3:  using System.Collections.Generic;
   4:  using System.Linq;
   5:  using System.Reflection;
   6:  using System.Text;
   7:  
   8:  using Demo.Data;
   9:  using System.Windows.Controls;
  10:  using System.Windows.Data;
  11:  
  12:  
  13:  namespace Dyno
  14:  {
  15:  
  16:      public enum WritePropertyType { All = 1, Some };
  17:  
  18:      public class EnumerableResultsCreator
  19:      {
  20:          #region Data
  21:          private readonly String generatedNamespace = "DynamicLINQ";
  22:          private readonly String generatedClassName = "ResultsWriter";
  23:          private readonly String generatedMethod = "GetResults";
  24:          private List<Person> originalResults = null;
  25:          private WritePropertyType currentPropertyType =
  26:              WritePropertyType.All;
  27:          #endregion
  28:  
  29:          #region Ctor
  30:          public EnumerableResultsCreator()
  31:          {
  32:  
  33:          }
  34:          #endregion
  35:  
  36:          #region Private Methods
  37:  
  38:          /// <summary>
  39:          /// Writes out code for class to dynamically compile
  40:          /// </summary>
  41:          private String WriteDynamicClass(WritePropertyType currentPropertyType)
  42:          {
  43:              StringBuilder code = new StringBuilder();
  44:              code.AppendLine(" using System; ");
  45:              code.AppendLine(" using System.Collections.Generic;");
  46:              code.AppendLine(" using System.Collections.ObjectModel;");
  47:              code.AppendLine(" using System.Linq;");
  48:              code.AppendLine(" using System.Text;");
  49:              //Where the DataLayer objects live
  50:              code.AppendLine(" using Demo.Data; ");
  51:              code.AppendFormat(" namespace {0}", generatedNamespace);
  52:              code.AppendLine();
  53:              code.AppendLine(" {");
  54:              code.AppendFormat(" public class {0}", generatedClassName);
  55:              code.AppendLine();
  56:              code.AppendLine(" {");
  57:              code.AppendFormat("{0}",
  58:                  this.WriteCodeProperties(currentPropertyType));
  59:              code.AppendLine();
  60:              code.Append(" }");
  61:              code.AppendLine();
  62:              code.Append(" }");
  63:              code.AppendLine();
  64:              return code.ToString();
  65:          }
  66:  
  67:  
  68:          /// <summary>
  69:          /// Either writes a LINQ query string that returns an anomomous
  70:          /// set of object with ALL properties, or just some of the properties
  71:          /// </summary>
  72:          private String WriteCodeProperties(WritePropertyType currentPropertyType)
  73:          {
  74:              StringBuilder builder = new StringBuilder();
  75:              builder.AppendFormat(
  76:                  "n public Object {0}(List<Person> results)",
  77:                      generatedMethod);
  78:              builder.AppendLine("{");
  79:  
  80:  
  81:              switch (currentPropertyType)
  82:              {
  83:                  case WritePropertyType.All:
  84:                      //OK this is a static query that could have been done in  standard LINQ
  85:                      //but this techniqe can be used to create dyanamically created
  86:                      //LINQ queries at runtime
  87:                      builder.AppendLine("var x =(from r in results select new { ");
  88:                      builder.AppendLine("         Age = r.Age,FirstName = r.FirstName,");
  89:                      builder.AppendLine("         MiddleName=r.MiddleName, LastName = r.LastName,");
  90:                      builder.AppendLine("         ID = r.PersonData.ID, Dob = r.PersonData.Dob });");
  91:                      builder.AppendLine("return x;");
  92:                      builder.AppendLine("}");
  93:                      break;
  94:                  case WritePropertyType.Some:
  95:                      //OK this is a static query that could have been done in  standard LINQ
  96:                      //but this techniqe can be used to create dyanamically created
  97:                      //LINQ queries at runtime
  98:                      builder.AppendLine("var x =(from r in results select new { ");
  99:                      builder.AppendLine("         Age = r.Age,FirstName = r.FirstName,");
 100:                      builder.AppendLine("         LastName = r.LastName});");
 101:                      builder.AppendLine("return x;");
 102:                      builder.AppendLine("}");
 103:                      break;
 104:              }
 105:  
 106:  
 107:              return builder.ToString();
 108:  
 109:          }
 110:  
 111:          /// <summary>
 112:          /// Creates the new columns for the Dynamically retrieved
 113:          /// results 
 114:          /// </summary>
 115:          private void CreateListViewColumns(IEnumerable result, ListView lv)
 116:          {
 117:              GridView gv = new GridView();
 118:  
 119:              IEnumerator e = result.GetEnumerator();
 120:              while (e.MoveNext())
 121:              {
 122:                  Type t = e.Current.GetType();
 123:                  PropertyInfo[] infos = t.GetProperties();
 124:  
 125:                  foreach (var item in infos)
 126:                  {
 127:                      GridViewColumn column = new GridViewColumn();
 128:                      column.Header = item.Name;
 129:                      Binding binding = new Binding(item.Name);
 130:  
 131:                      column.DisplayMemberBinding = binding;
 132:                      gv.Columns.Add(column);
 133:                  }
 134:                  break;
 135:              }
 136:  
 137:              lv.View = gv;
 138:          }
 139:  
 140:  
 141:          #endregion
 142:  
 143:          #region Public Method
 144:          /// <summary>
 145:          /// Create the dynamic Type invoke its method
 146:          /// and get the results
 147:          /// </summary>
 148:          public IEnumerable ObtainFlattenedResults(
 149:              ListView lv,
 150:              List<Person> originalResults,
 151:              WritePropertyType currentPropertyType)
 152:          {
 153:              if (originalResults.Count == 0)
 154:                  return null;
 155:  
 156:              this.originalResults = originalResults;
 157:  
 158:              //Create the DynamicCompiler
 159:              DynamicCompiler compiler = new DynamicCompiler();
 160:  
 161:              //Get the newly compiled object
 162:              Object dynaClass = compiler.ComplileAndRun<Object>(
 163:                  this.WriteDynamicClass(currentPropertyType),
 164:                  generatedNamespace, generatedClassName, "", false);
 165:  
 166:              //invoke its method to get the result
 167:              IEnumerable result =
 168:                  (IEnumerable)dynaClass.GetType().InvokeMember(
 169:                          generatedMethod, BindingFlags.InvokeMethod,
 170:                          null, dynaClass, new object[1]
 171:                              { this.originalResults });
 172:  
 173:              //create new ListView columns
 174:              CreateListViewColumns(result, lv);
 175:  
 176:  
 177:              return result;
 178:  
 179:          }
 180:          #endregion
 181:  
 182:      }
 183:  }

.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 what this class does, is construct a new code file, that is compiled using the DynamicCompiler helper class that I showed you earlier. What we then do is get an actual instance of an object back from the dynamic compilation process, which we can then use to invoke a method on.

Here is what the compiled code will look like

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Collections.ObjectModel;
   4:  using System.Linq;
   5:  using System.Text;
   6:  using Demo.Data;
   7:  namespace DynoGen
   8:  {
   9:      public class ResultsWriter
  10:      {
  11:  
  12:          public Object GetResults(List<Person> results)
  13:          {
  14:              var x = (from r in results
  15:                       select new
  16:                       {
  17:                           Age = r.Age,
  18:                           FirstName = r.FirstName,
  19:                           LastName = r.LastName
  20:                       });
  21:              return x;
  22:          }
  23:  
  24:      }
  25:  }

But Beware

As we are effectivly creating a new dynamically created Dll in memory in our current AppDomain this could lead to the current AppDomain getting bigger and bigger. So it is a good idea to do all this in a new AppDomain, but how do we do that?

Well that is actually pretty simple, we can do this

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  
   6:  using Demo.Data;
   7:  using System.Windows.Controls;
   8:  using System.Collections;
   9:  using System.IO;
  10:  using System.Runtime.Remoting;
  11:  using System.Reflection;
  12:  
  13:  namespace DynamicLINQ
  14:  {
  15:      /// <summary>
  16:      /// Obtains dynamic results using a seperate AppDomain that is 
  17:      /// unloaded when the work is finished with the Dyanamic Assembly
  18:      /// </summary>
  19:      public class DataLoader
  20:      {
  21:          public IEnumerable ObtainFlattenedResults(
  22:              ListView lv,
  23:              List<Person> originalResults,
  24:              WritePropertyType currentPropertyType)
  25:          {
  26:  
  27:              //Create new app domain            
  28:              AppDomain domain = AppDomain.CreateDomain("ResultsDomain");
  29:              IEnumerable results = null;
  30:              Object instance = null;
  31:  
  32:  
  33:              try
  34:              {
  35:  
  36:                  //load search assembly
  37:                  domain.Load(File.ReadAllBytes(String.Format(@"{0}{1}",
  38:                      Environment.CurrentDirectory,
  39:                       "Dyno.dll")));
  40:  
  41:                  //Get the EnumerableResultsCreator instance
  42:                  ObjectHandle instanceHandle = domain.CreateInstance("Dyno",
  43:                                      String.Format("{0}.{1}"
  44:                                      , "Dyno"
  45:                                      , "EnumerableResultsCreator"));
  46:  
  47:                  if (instanceHandle != null)
  48:                      instance = instanceHandle.Unwrap();
  49:  
  50:                  //call method to get IEnumerable results
  51:                  if (instance != null)
  52:                      results = instance.GetType().InvokeMember
  53:                          ("ObtainFlattenedResults",
  54:                                  BindingFlags.InvokeMethod,
  55:                                  null,
  56:                                  instance,
  57:                                  new object[3] {
  58:                                      lv
  59:                                      , originalResults
  60:                                      , currentPropertyType }
  61:                          ) as IEnumerable;
  62:              }
  63:              catch (Exception e)
  64:              {
  65:                  Console.WriteLine("Problem constructing the " +
  66:                      "EnumerableResultsCreator instance");
  67:              }
  68:              finally
  69:              {
  70:                  AppDomain.Unload(domain);
  71:              }
  72:              return results;
  73:          }
  74:      }
  75:  }

.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; }This takes care of managing a new AppDomain and gets the results from the dynamic assembly, and then unloads the new AppDomain. This ensures the primary applications AppDomain doesn’t get any unwanted code in it.

So you can see that we are creating an actual class that accepts a List<Person> (the fake search results) and uses some LINQ to grab out ONLY the properties we want to use all rolled up in an anonymous which is selected as a single flattened search result for the search results grid. Remember this is all happening at RUNTIME, so we can do what we like, we can manipulate the results as much as we like, as the code we compile is just a String.

The other thing that happens is that the WPF ListView columns are altered to only show the necessary  columns based on the newly flattened search results.

Some grid works nicer with anonymous types, though so far the WPF Toolkit DataGrid failed, and so does the ListView, that is why we need to do the trick with re-creating the columns based on the new results. At work we are using the Infragistics xamDataGrid, and it works straight out of the can with anonymous types, we do not have to mess about with columns at all, it just knows what to do.

Here is the results

Showing ALL Columns Of Results

image

Which is done using the following logic

   1:                  case WritePropertyType.All:
   2:                      //OK this is a static query that could have been done in  standard LINQ
   3:                      //but this techniqe can be used to create dyanamically created
   4:                      //LINQ queries at runtime
   5:                      builder.AppendLine("var x =(from r in results select new { ");
   6:                      builder.AppendLine("         Age = r.Age,FirstName = r.FirstName,");
   7:                      builder.AppendLine("         MiddleName=r.MiddleName, LastName = r.LastName,");
   8:                      builder.AppendLine("         ID = r.PersonData.ID, Dob = r.PersonData.Dob });");
   9:                      builder.AppendLine("return x;");
  10:                      builder.AppendLine("}");

.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; }Showing Selected Columns Of Results (see we only get some of the columns)

image

Which is done using the following logic

   1:                  case WritePropertyType.Some:
   2:                      //OK this is a static query that could have been done in  standard LINQ
   3:                      //but this techniqe can be used to create dyanamically created
   4:                      //LINQ queries at runtime
   5:                      builder.AppendLine("var x =(from r in results select new { ");
   6:                      builder.AppendLine("         Age = r.Age,FirstName = r.FirstName,");
   7:                      builder.AppendLine("         LastName = r.LastName});");
   8:                      builder.AppendLine("return x;");
   9:                      builder.AppendLine("}");
  10:                      break;

.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 small demo project

dynamiclinq1.zip

Now I know this is a very specific business case, and not many people will do this exactly, but it did lead to some very interesting code, that I thought I could share with you, it does show you how to work with dynamically compiled assemblies.

IMPORTANT NOTE

For this example I could have used the Dynamic LINQ library Dll, which is discussed over at Scott Guthrie’s blog.

http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

However this technique is a powerful one, and could be used for a great many use cases, where you need to run some dynamic code that you only know about at runtime. It just happens to be I chose to use LINQ as the demonstration tool, at work we actually use the Dynamic LINQ library Dll, but the rest is the same.

C#, Current Work, LINQ, SQL, WCF, WPF

Who Wants Me To Write A Book

Recently someone left me the following message on my new article (http://www.codeproject.com/KB/smart/GeoPlaces.aspx)

Hi Sacha,
I’m sure this has been said before but you really should write a book imho.
I’d buy it …
I’m not going to vote a 5 because I’d rather vote with my cash frankly!!
Oh, alright then … Wink
<stick>
Gold Star
</stick>
Seriously, write a book, but don’t do the predictable thing of picking a single technology. I like your whole approach and passion. This article would make a killer chapter in said book for instance …
Best Regards,

Jammer : 05/04/2009

Now this is not the 1st time someone has suggested that I write a book, I was just wondering how many people would actually like me to write a book. If you would could you leave a comment here, so that I can tell who would buy the book, and how many folk think it would be a good idea.

The sort of thing that I would try and cover is what I normally cover, which would be how to work with a mixture of technologies all used together, So this may include WCF/WPF/WF/LINQ/Custom controls/ASP .NET Silverlight all working together. Typically this would be to solve some problem that I make up, where the problem show cases how to use the technology really.

Let me know thanks

PS : I am running out of time to say thanks to all of you that have encouraged me to do this. I am going to look into it. I just wanted to say a big thanks to you all.

WPF

Free WPF / MVVM Training

Jaime Rodriguez and Karl Shifflett recently sent me an email stating that they would be in London to teach how to use WPF to create Line of Business applications…for free!

Karl and Jaime are both fellow wpfdisciples, both of whom I am lucky enough to know. These 2 guys really know WPF and what it takes to build business apps, so I’m sure their training sessions will be packed with useful, important, interesting information. You can read more about this exciting opportunity on Jaime’s blog:

Announcing the “WPF for LOB” Training Tour

Lately the wpfdisciples, has attracted some very influential members such as Scott Guthrie, and other core WPF members of the Redmond camp. So you can see the calibre of the people involved, and can guess the mark that these training days will be pitched at.

Also myself and another bunch of the WPF Disciples will be there, such as

  • Karl and Jaime (obviously)
  • Marlon Grech (kid genius)
  • Pete O’Hanlon

I’ve been told that Mike Taulty will also be there, and that guy is the bomb. Considering that their previous training sessions were sold out, be quick, sign up.

Introduction

GeoPlaces article

I have just released a new article over at http://www.codeproject.com whch uses the following technologies

WCF : RESTful WCF (PUT/GET)

WCF : Hosting RESTful WCF service

ADO .NET ENtity Framework Models

WPF : MVVM

WPF : Mediator pattern

WPF : Embedded Virtual Earth 3D

I am way proud of this article it has worked out very well, and you can read more about it and please vote and leave a message if you like it, I would be most grateful for your votes

GeoPlaces codeproject Url:

http://www.codeproject.com/KB/smart/GeoPlaces.aspx

Here is a screen shot of part of the app running

geo1.png