Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

WPF : Dynamic Search Driven List Results

, 9 Apr 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
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

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-thumb.png

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 DynamicLINQ
  10:  {
  11:      /// <span class="code-SummaryComment"><summary></span>
  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:      /// <span class="code-SummaryComment"></summary></span>
  17:      public class DynamicCompiler
  18:      {
  19:          #region Public Methods
  20:          /// <span class="code-SummaryComment"><summary></span>
  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:          /// <span class="code-SummaryComment"></summary></span>
  25:          /// <span class="code-SummaryComment"><typeparam name=”T”>Type of T to use</typeparam></span>
  26:          /// <span class="code-SummaryComment"><param name=”code”>code to compile</param></span>
  27:          /// <span class="code-SummaryComment"><param name=”nameSpace”>namespace to use for compiled code</param></span>
  28:          /// <span class="code-SummaryComment"><param name=”classToLoad”>class name</param></span>
  29:          /// <span class="code-SummaryComment"><param name=”methodToRun”>method to invoke (optional)</param></span>
  30:          /// <span class="code-SummaryComment"><param name=”ShouldImvokeMethod”>true if you want to </span>
  31:          /// invoke the method<span class="code-SummaryComment"></param></span>
  32:          /// <span class="code-SummaryComment"><returns>Type of T to use</returns></span>
  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:  }

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-thumb1.png

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>

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:      //show some columns from Dynamically compiled LINQ
   4:      //query
   5:      IEnumerable results =
   6:          enumerableResultsCreator.ObtainFlattenedResults(lvItems,
   7:              people, WritePropertyType.Some);
   8:  
   9:  
  10:      lvItems.ItemsSource = results;
  11:  
  12:  }

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>();

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 DynamicLINQ
  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:          /// <span class="code-SummaryComment"><summary></span>
  39:          /// Writes out code for class to dynamically compile
  40:          /// <span class="code-SummaryComment"></summary></span>
  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:          /// <span class="code-SummaryComment"><summary></span>
  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:          /// <span class="code-SummaryComment"></summary></span>
  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:          /// <span class="code-SummaryComment"><summary></span>
 112:          /// Creates the new columns for the Dynamically retrieved
 113:          /// results 
 114:          /// <span class="code-SummaryComment"></summary></span>
 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:          /// <span class="code-SummaryComment"><summary></span>
 145:          /// Create the dynamic Type invoke its method
 146:          /// and get the results
 147:          /// <span class="code-SummaryComment"></summary></span>
 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:  }

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 DynamicLINQ
   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:  }

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-thumb2.png

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(“}”);

Showing Selected Columns Of Results (see we only get some of the columns)

image-thumb3.png

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;

Here is a small demo project

http://sachabarber.net/wp-content/uploads/2009/04/dynamiclinq.zip

Now I know this is a very specific business case, and not many people will do this

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sacha Barber
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)
 
- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence
 
Both of these at Sussex University UK.
 
Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
GeneralThe pictures don't seem to be loading. PinmemberAshaman9-Apr-09 11:23 
GeneralRe: The pictures don't seem to be loading. PinmvpSacha Barber9-Apr-09 23:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150327.1 | Last Updated 9 Apr 2009
Article Copyright 2009 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid