Beginners Guide To Threading, C#, CodeProject, WPF

ThreadSafeObservableCollection

As part of an ongoing article I am just about to finish, I needed a thread safe ObservableCollection<T>. Now the native .NET framework doesn’t have one of those, but luckily it supplies you with the right job to create one using some of the Threading APIs.

Here is what I came up with.

 

   1:  /// <summary>
   2:  /// Provides a threadsafe ObservableCollection of T
   3:  /// </summary>
   4:  public class ThreadSafeObservableCollection<T>
   5:      : ObservableCollection<T>
   6:  {
   7:      #region Data
   8:      private Dispatcher _dispatcher;
   9:      private ReaderWriterLockSlim _lock;
  10:      #endregion
  11:  
  12:      #region Ctor
  13:      public ThreadSafeObservableCollection()
  14:      {
  15:          _dispatcher = Dispatcher.CurrentDispatcher;
  16:          _lock = new ReaderWriterLockSlim();
  17:      }
  18:      #endregion
  19:  
  20:  
  21:      #region Overrides
  22:  
  23:      /// <summary>
  24:      /// Clear all items
  25:      /// </summary>
  26:      protected override void ClearItems()
  27:      {
  28:          _dispatcher.InvokeIfRequired(() =>
  29:              {
  30:                  _lock.EnterWriteLock();
  31:                  try
  32:                  {
  33:                      base.ClearItems();
  34:                  }
  35:                  finally
  36:                  {
  37:                      _lock.ExitWriteLock();
  38:                  }
  39:              }, DispatcherPriority.DataBind);
  40:      }
  41:  
  42:      /// <summary>
  43:      /// Inserts an item
  44:      /// </summary>
  45:      protected override void InsertItem(int index, T item)
  46:      {
  47:          _dispatcher.InvokeIfRequired(() =>
  48:          {
  49:              if (index > this.Count)
  50:                  return;
  51:  
  52:              _lock.EnterWriteLock();
  53:              try
  54:              {
  55:                  base.InsertItem(index, item);
  56:              }
  57:              finally
  58:              {
  59:                  _lock.ExitWriteLock();
  60:              }
  61:          }, DispatcherPriority.DataBind);
  62:  
  63:      }
  64:  
  65:      /// <summary>
  66:      /// Moves an item
  67:      /// </summary>
  68:      protected override void MoveItem(int oldIndex, int newIndex)
  69:      {
  70:          _dispatcher.InvokeIfRequired(() =>
  71:          {
  72:              _lock.EnterReadLock();
  73:              Int32 itemCount = this.Count;
  74:              _lock.ExitReadLock();
  75:  
  76:              if (oldIndex >= itemCount |
  77:                  newIndex >= itemCount |
  78:                  oldIndex == newIndex)
  79:                  return;
  80:  
  81:              _lock.EnterWriteLock();
  82:              try
  83:              {
  84:                  base.MoveItem(oldIndex, newIndex);
  85:              }
  86:              finally
  87:              {
  88:                  _lock.ExitWriteLock();
  89:              }
  90:          }, DispatcherPriority.DataBind);
  91:  
  92:  
  93:  
  94:      }
  95:  
  96:      /// <summary>
  97:      /// Removes an item
  98:      /// </summary>
  99:      protected override void RemoveItem(int index)
 100:      {
 101:  
 102:          _dispatcher.InvokeIfRequired(() =>
 103:          {
 104:              if (index >= this.Count)
 105:                  return;
 106:  
 107:              _lock.EnterWriteLock();
 108:              try
 109:              {
 110:                  base.RemoveItem(index);
 111:              }
 112:              finally
 113:              {
 114:                  _lock.ExitWriteLock();
 115:              }
 116:          }, DispatcherPriority.DataBind);
 117:      }
 118:  
 119:      /// <summary>
 120:      /// Sets an item
 121:      /// </summary>
 122:      protected override void SetItem(int index, T item)
 123:      {
 124:          _dispatcher.InvokeIfRequired(() =>
 125:          {
 126:              _lock.EnterWriteLock();
 127:              try
 128:              {
 129:                  base.SetItem(index, item);
 130:              }
 131:              finally
 132:              {
 133:                  _lock.ExitWriteLock();
 134:              }
 135:          }, DispatcherPriority.DataBind);
 136:      }
 137:      #endregion
 138:  
 139:      #region Public Methods
 140:      /// <summary>
 141:      /// Return as a cloned copy of this Collection
 142:      /// </summary>
 143:      public T[] ToSyncArray()
 144:      {
 145:          _lock.EnterReadLock();
 146:          try
 147:          {
 148:              T[] _sync = new T[this.Count];
 149:              this.CopyTo(_sync, 0);
 150:              return _sync;
 151:          }
 152:          finally
 153:          {
 154:              _lock.ExitReadLock();
 155:          }
 156:      }
 157:      #endregion
 158:  }

.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 relies on this small extension method

 

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

   1:  /// <summary>
   2:  /// WPF Threading extension methods
   3:  /// </summary>
   4:  public static class WPFControlThreadingExtensions
   5:  {
   6:      #region Public Methods
   7:      /// <summary>
   8:      /// A simple WPF threading extension method, to invoke a delegate
   9:      /// on the correct thread if it is not currently on the correct thread
  10:      /// Which can be used with DispatcherObject types
  11:      /// </summary>
  12:      /// <param name="disp">The Dispatcher object on which to do the Invoke</param>
  13:      /// <param name="dotIt">The delegate to run</param>
  14:      /// <param name="priority">The DispatcherPriority</param>
  15:      public static void InvokeIfRequired(this Dispatcher disp,
  16:          Action dotIt, DispatcherPriority priority)
  17:      {
  18:          if (disp.Thread != Thread.CurrentThread)
  19:          {
  20:              disp.Invoke(priority, dotIt);
  21:          }
  22:          else
  23:              dotIt();
  24:      }
  25:      #endregion
  26:  }

.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; }Hope it is useful to someone. Enjoy

C#

DateTimeOffSet Struct

Today I was asked something about a DateTime manipulation in SQL server, and one of my learned collegues piped up about a DateTimeOffset Type. Now I have never used or even heard of this, so looked it up and low and behold there is now a new DateTimeOffSet type in .NET 3.5, that is supposedly better for globalization issues.

There is a nice post about this over on the BCL team blog.

http://blogs.msdn.com/bclteam/archive/2007/06/14/datetimeoffset-a-new-datetime-structure-in-net-3-5-justin-van-patten.aspx

Enjoy

WPF

WPF : View Level Validation

I have just written a new article over at www.codeproject.com which talks about how to perform cross business object validation using a new idea that I had the other day. This technique is quite different from the standard IDataErrorInfo approach. The idea is that Each View is bound to a ViewModel, where the ViewModel may hold many business objects which may need some sort of cross validation.

As such each ViewModel is validated by a independent validator. You may want to read more about it over at http://www.codeproject.com/KB/WPF/GlobalWPFValidation.aspx

 

Enjoy

WPF

A Very Proud Moment (Well for me it was anyhow)

Today at work, my colleague and I decided we needed a graph control. And after about a day we came up with the following WPF control.

ticker

I think its pretty cool.  The low and high X-Axis points scale to suit the values Min/Max and the background changes shades according to the Mean of the values.

Now I should point out that we already had a free graph that this control has more than a passing resemblance to. The original graph was done by Andre de Cavaignac when he was at Lab49.

That said, we ended up using virtually none of the original code, apart from an attached property that changes the opacity based on the Mean of the values. The reason we decided to create our own is that the original graph was quite tied into Lab49 business, which did not suit our particular requirements. So bada bing, we knocked up this graph. I was very pleased with the results and it is done in only 2 classes. One is the graph and one contains the graph, and I think it is very easy to use.

I will hopefully be posting the code up soon over at www.codeproject.com, so if this floats your WPF boat, watch our for it over there soon.

Beginners Guide To Threading, C#, CodeProject, Lambdas / Anonomous delegates, WPF

Useful WPF Threading Extension Method

If you are working with WinForms or WPF you will more than likely run into some long running operation that you would like to run in a new thread. A novice may actually try and create a new Thread, which is ok, but that means you are responsible for the entire lifecycle of your new thread. Which gets tricky.

A better approach would be to use the ThreadPool or use a BackgroundWorker component which uses the ThreadPool beneath the surface.

However, even using these approaches the cardinal rule is that the control is owned by 1 thread, the thread that created the controls. That is typically the UI thread. So when you try and update the controls from a background thread you will run into problems.

This code demonstrates the problem with cross thread calls :

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Windows;
   6:  using System.Windows.Controls;
   7:  using System.Windows.Data;
   8:  using System.Windows.Documents;
   9:  using System.Windows.Input;
  10:  using System.Windows.Media;
  11:  using System.Windows.Media.Imaging;
  12:  using System.Windows.Navigation;
  13:  using System.Windows.Shapes;
  14:  using System.ComponentModel;
  15:  using System.Windows.Threading;
  16:  
  17:  namespace BackgroundThread
  18:  {
  19:  
  20:  
  21:      public partial class Window1 : Window
  22:      {
  23:          private Int32 currentCount = 0;
  24:          private Int32 maxCount = 500;
  25:          private float factor = 0;
  26:  
  27:          public Window1()
  28:          {
  29:              InitializeComponent();
  30:  
  31:          }
  32:  
  33:          private void btnGo_Click(object sender, RoutedEventArgs e)
  34:          {
  35:              factor = (float)100 / maxCount;
  36:  
  37:              BackgroundWorker bgWorker = new BackgroundWorker();
  38:              bgWorker.WorkerReportsProgress = true;
  39:              bgWorker.WorkerSupportsCancellation = false;
  40:  
  41:              //DoWork
  42:              bgWorker.DoWork += (s2, e2) =>
  43:              {
  44:                  for (currentCount = 0;
  45:                      currentCount < maxCount; currentCount++)
  46:                  {
  47:                      lstItems.Items.Add(
  48:                          String.Format("Count {0}", currentCount));
  49:                  }
  50:              };
  51:  
  52:              //ProgressChanged
  53:              bgWorker.ProgressChanged += (s3, e3) =>
  54:              {
  55:                  pgbar.Value = e3.ProgressPercentage;
  56:              };
  57:  
  58:              bgWorker.RunWorkerAsync();
  59:  
  60:          }
  61:      }
  62:  }

.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 when run will result in the following:

crossThread

.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 how can we fix this, well we could use the Dispatcher.Invoke around the offending items, but perhaps a more elegant solution may be to use a extension method.

   1:  public static class WPFThreadingExtensions
   2:  {
   3:      /// <summary>
   4:      /// Simple helper extension method to marshall to correct
   5:      /// thread if its required
   6:      /// </summary>
   7:      /// <param name="control">The source control</param>
   8:      /// <param name="methodcall">The method to call</param>
   9:      /// <param name="priorityForCall">The thread priority</param>
  10:      public static void InvokeIfRequired(
  11:          this DispatcherObject control,
  12:          Action methodcall,
  13:          DispatcherPriority priorityForCall)
  14:      {
  15:          //see if we need to Invoke call to Dispatcher thread
  16:          if (control.Dispatcher.Thread != Thread.CurrentThread)
  17:              control.Dispatcher.Invoke(priorityForCall, methodcall);
  18:          else
  19:              methodcall();
  20:      }
  21:  }

.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 can then use in our code as simply as follows :

   1:      factor = (float)100 / maxCount;
   2:  
   3:      BackgroundWorker bgWorker = new BackgroundWorker();
   4:      bgWorker.WorkerReportsProgress = true;
   5:      bgWorker.WorkerSupportsCancellation = false;
   6:  
   7:      //DoWork
   8:      bgWorker.DoWork += (s2, e2) =>
   9:      {
  10:          for (currentCount = 0;
  11:              currentCount < maxCount; currentCount++)
  12:          {
  13:  
  14:              this.InvokeIfRequired(() =>
  15:              {
  16:                  lstItems.Items.Add(
  17:                      String.Format("Count {0}", currentCount));
  18:              },
  19:                  DispatcherPriority.Background);
  20:  
  21:              bgWorker.ReportProgress((int)(factor * (currentCount + 1)));
  22:  
  23:          }
  24:      };
  25:  
  26:      //ProgressChanged
  27:      bgWorker.ProgressChanged += (s3, e3) =>
  28:      {
  29:          this.InvokeIfRequired(() =>
  30:          {
  31:              pgbar.Value = e3.ProgressPercentage;
  32:          },
  33:          DispatcherPriority.Background);
  34:  
  35:  
  36:      };
  37:  
  38:      bgWorker.RunWorkerAsync();
  39:  
  40:  }

.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 when run allows cross threaded calls to be marshaled to the correct Dispatcher object.

better

Hope this helps.
.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; }

C#, LINQ

A simple SGML Parser and Visitor pattern loveliness

I am in the middle of so many article ideas right now that I am finding it hard to find enough time to look at them all, and some of them are pretty big articles. So what I have to do when this happens, is break them down into smaller areas, and look and see if any of the bigger articles would become clearer if I wrote it in smaller parts.

I am currently working on one that has to do with extending LINQ and the whole System.Linq.Expressions namespace idea in general. Now I don’t want to give too much away about what I will be doing, but one of the fundamental parts of how the whole System.Linq.Expressions namespace works, is by the use of the "Visitor Pattern".

If you have not used this pattern before, it is a structural pattern that can be used keep the algorithm separate from the object structure. I have used this pattern on a few different occasions and I for one really like it, though I do feel the standard visitor pattern could be improved a little.

I have written a small article talking about the standard a possibly improved visitor pattern over at www.codeproject.com, you can use the following link to read more.

http://www.codeproject.com/KB/recipes/ReflectVisitor.aspx

Enjoy

C#, WPF

Restyling WPF ListView Header

Of late I have been working with Net Advantage for WPF by Infragistics, but today we didn’t really need the fully functionality of a DataGrid and needed a rather lighter weight component (basically simply sorting list, no paging, no grouping…just a list), so I turned my attention back to the inbuilt WPF controls (there is a WPF Datagrid that was released out of bands within the WPF Toolkit should you want a free WPF grid), where I needed a ListView.

Now I like the ListView but I have always stuggled a little bit with getting it to look how I wanted visually, one area where I seem to always have issues is changed the header (where the column names are shown).

I had initially looked into doing this with Blend following the instructions here http://www.designerwpf.com/2008/01/16/styling-the-listview-column-header/ which although very accurate, result in about 300 lines of code, most of which I didn’t want to change. Basically all I was after was a new Colour in the header section.

Now if you use Blend and are happy with what it produces that’s all cool, I am not against that, It’s just I feel that the hand written approach is a little nicer on the XAML content. You see what happens when you work with Expression Blend in design mode, and start editing control templates, you will get the entire set of XAML to make the control from scratch, but you may only actually need to style/template 1 particular control.

In order to restyle the ListView header, I had to do the following:

 

   1:  <LinearGradientBrush  x:Key="BlueRinseBrush" 
   2:                        EndPoint="0.5,1" StartPoint="0.5,0">
   3:      <GradientStop Color="#FF223B84" Offset="1"/>
   4:      <GradientStop Color="#FF57A0F4" Offset="0.5"/>
   5:      <GradientStop Color="#FF4B94EC" Offset="0.5"/>
   6:  </LinearGradientBrush>
   7:   
   8:   
   9:  <Style x:Key="GridViewColumnHeaderGripper" 
  10:         TargetType="Thumb">
  11:      <Setter Property="Width" Value="18"/>
  12:      <Setter Property="Background" Value="White"/>
  13:      <Setter Property="Template">
  14:          <Setter.Value>
  15:              <ControlTemplate TargetType="{x:Type Thumb}">
  16:                  <Border Padding="{TemplateBinding Padding}" 
  17:                          Background="Transparent">
  18:                      <Rectangle HorizontalAlignment="Center" 
  19:                                 Width="3"
  20:                          Fill="{TemplateBinding Background}"/>
  21:                  </Border>
  22:              </ControlTemplate>
  23:          </Setter.Value>
  24:      </Setter>
  25:  </Style>
  26:   
  27:  <Style x:Key="GridViewColumnHeaderStyle"  
  28:         TargetType="GridViewColumnHeader">
  29:      <Setter Property="HorizontalContentAlignment" 
  30:              Value="Center"/>
  31:      <Setter Property="VerticalContentAlignment" 
  32:              Value="Center"/>
  33:      <Setter Property="Background" 
  34:              Value="{StaticResource BlueRinseBrush}"/>
  35:      <Setter Property="Foreground" 
  36:              Value="{DynamicResource 
  37:                  {x:Static SystemColors.ControlTextBrushKey}}"/>
  38:      <Setter Property="Template">
  39:          <Setter.Value>
  40:           <ControlTemplate 
  41:              TargetType="GridViewColumnHeader">
  42:                  <Grid>
  43:                      <Border Name="HeaderBorder"
  44:                              BorderThickness="0"
  45:                              BorderBrush="{StaticResource BlueRinseBrush}"
  46:                              Background="{StaticResource BlueRinseBrush}"
  47:                              Padding="2,0,2,0">
  48:                          <ContentPresenter Name="HeaderContent"
  49:                          TextElement.Foreground="White"
  50:                          Margin="0,0,0,1"
  51:                          VerticalAlignment="{TemplateBinding 
  52:                          VerticalContentAlignment}"
  53:                          HorizontalAlignment="{TemplateBinding 
  54:                          HorizontalContentAlignment}"
  55:                          RecognizesAccessKey="True"
  56:                          SnapsToDevicePixels=
  57:                          "{TemplateBinding SnapsToDevicePixels}"/>
  58:                      </Border>
  59:                      <Thumb x:Name="PART_HeaderGripper"
  60:                          HorizontalAlignment="Right"
  61:                          Margin="0,0,-9,0"
  62:                          Style="{StaticResource 
  63:                          GridViewColumnHeaderGripper}"/>
  64:                  </Grid>
  65:                  <ControlTemplate.Triggers>
  66:                      <Trigger Property="IsMouseOver" Value="true">
  67:                          <Setter TargetName="HeaderBorder" 
  68:                            Property="Background" Value="Yellow"/>
  69:                          <Setter TargetName="HeaderContent" 
  70:                            Property="TextElement.Foreground" 
  71:                            Value="Black"/>
  72:                      </Trigger>
  73:                  </ControlTemplate.Triggers>
  74:              </ControlTemplate>
  75:          </Setter.Value>
  76:      </Setter>
  77:  </Style>

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

 

Now the important part here is the approach that I took….which I believe is the key to working successfully with WPF. Basically I would recommend you should always consult the existing control templates (the default look and feel, basically) which you can find at http://msdn.microsoft.com/en-us/library/aa970773(VS.85).aspx and only style the parts you need to change.

 

Anyway the results of the code above give me what I was after, a custom header area:

LV

And here is a small demo project : listview.zip

MVP

Codeproject MVP Status For 2009

I just go a new email from Chris Maunder the chap that started and runs www.codeproject.com, and I have received an MVP award from it, for the articles I wrote in 2008.

I love codeproject, and I find writing articles to be very personally rewarding as they give me something to focus/learn from. The benifts that come with being a MVP both for Microsoft and codeproject are great, and I would encourage anyone of you to have a go at contributing something to the community.

C#, CodeProject, WPF

Generic Support In XAML

I have worked with XAML/WPF for a while now, but it wasn’t until recently I thought about generic support in XAML. I had no idea if it was possible, so I decided to have a quick look into this.

Luckily the WPF team thought about this and do actually enable WPF developers the correct mechanisms to fully support generics from within XAML.

The first thing you need to know is that a typical WPF window is made up of 2 partial classes, the code behind and the XAML, then at compilation time a combined file known as the generated file is formed with the combination of the code behind file and the XAML file.

build

So in order to correctly support generics for a file that is made up of both code behind and XAML we MUST ensure that both parts of the file are using generic type arguments. This should become clearer as we move on to see a small example.

In order to demonstrate the support for generics in XAML let us consider a small example, which requires the following arrangement :

  1. We have a common window base class that is generic
  2. We inherit from the common window base class, but we then need to provide generic arguments within the code behind and the XAML in order to make it a generic type.

That’s basically what we are trying to do. In order to demonstrate this, I have constructed a small demo app that carries out the following:

I have an abstract base class called LoggingStrategy which looks like this

   1:  using System;
   2:  
   3:  namespace GenericViews
   4:  {
   5:      public abstract class LoggingStrategy
   6:      {
   7:          public abstract void LogText(string textToLog);
   8:      }
   9:  }

.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; } I then have a specific class that overrides the LogText(…) method, this is as follows:

   1:  using System;
   2:  using System.Windows;
   3:  
   4:  namespace GenericViews
   5:  {
   6:      public class MessageBoxLogger : LoggingStrategy
   7:      {
   8:          public override void LogText(string textToLog)
   9:          {
  10:              MessageBox.Show(textToLog);
  11:          }
  12:      }
  13:  }

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

Then within the main App class that you get if you start a new WPF application in VS2008, I have created a simple Dictionary lookup for a Type to get the correct logging strategy to use. The idea being that the Type will come from a generic argument provided by the view, as we will see in just a minute.

For now all you need to know is that there is a lookup against a Type that returns a LoggingStrategy inherited object.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Windows;
   4:  
   5:  namespace GenericViews
   6:  {
   7:      public partial class App : Application
   8:      {
   9:          public static Dictionary<Type, LoggingStrategy> Loggers =
  10:              new Dictionary<Type, LoggingStrategy>();
  11:  
  12:  
  13:          static App()
  14:          {
  15:              Loggers.Add(typeof(MessageBoxLogger), new MessageBoxLogger());
  16:          }
  17:      }
  18:  }

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

So we have pretty much covered all the demo stuff, now its just a question of dealing with the actual generic stuff. In the attached demo I have provided a small base class for views, and the base class is generic, and will lookup the correct LoggingStrategy inherited object, from the Dictionary stored in the static App Dictionary.

Here is the base class for the views.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Windows;
   4:  
   5:  namespace GenericViews
   6:  {
   7:      public class ViewBase<T> : Window where T : LoggingStrategy
   8:      {
   9:          public LoggingStrategy CurrentLogger { get; private set; }
  10:  
  11:          public ViewBase()
  12:          {
  13:              CurrentLogger = App.Loggers[typeof(T)];
  14:          }
  15:      }
  16:  }

.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 base class expects a generic argument T in order to work. T must inherit from LoggingStrategy. What then happens is generic argument is used to obtain the correct LoggingStrategy inherited object from the Dictionary stored in the static App Dictionary.

Ok we are nearly there, all we now have to do is make a custom window which inherits from the ViewBase<T>, and passes a generic argument .

Let’s start with the easy part, the code behind. This part is dead easy, we simply use standard generic stuff, as we would any other generic class:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Windows;
   4:  
   5:  
   6:  namespace GenericViews
   7:  {
   8:  
   9:      public partial class MessageLoggingWindow : ViewBase<MessageBoxLogger>
  10:      {
  11:          public MessageLoggingWindow() : base()
  12:          {
  13:              InitializeComponent();
  14:          }
  15:  
  16:          private void Button_Click(object sender, RoutedEventArgs e)
  17:          {
  18:              base.CurrentLogger.LogText(
  19:                  String.Format("Sometext which was logged at {0}",
  20:                  DateTime.Now.ToShortTimeString()));
  21:          }
  22:      }
  23:  }

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

Now that the easy part is done, why don’t we focus our efforts on the XAML part.

   1:  <local:ViewBase
   2:      x:Class="GenericViews.MessageLoggingWindow"
   3:      x:TypeArguments="local:MessageBoxLogger"
   4:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   5:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   6:      xmlns:local="clr-namespace:GenericViews">
   7:  ...
   8:  ...
   9:  </local:ViewBase>

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

Now, what is going on here exactly. Well a couple of things actually, let’s go through them

  1. x:Class : that’s the name of the class, this matches the code behind namespace and class name
  2. xmlns:local : This is a namespace alias that allows use to use local: in the XAML to point to classes in the GenericViews namespace
  3. <local:ViewBase…/> : Means we are using GenericViews.ViewBase<T> as the base class for this new Window, but wait what about the generic argument. Well that’s step 4
  4. x:TypeArguments : This is where the generic value gets plugged in. So we now have a window declared in XAML which is really GenericViews.ViewBase<MessageBoxLogger>

That’s it…..I know my example is not exactly real world, but that was not the point of all this, I simply wanted to show you how to create generics in XAML, and to that end this post should do just that.

Here is the small demo app genericviews.zip, that creates a generic window with a MessageBoxLogger, and when run looks like this:

image

Enjoy

NOTE:

A reader of this blog also pointed me at another entry by Mike Hillberg, which covers a lot of other generic support in XAML, have a look here for that, its quite cool

http://blogs.msdn.com/mikehillberg/archive/2006/10/06/LimitedGenericsSupportInXaml.aspx