Saturday, December 26, 2009

Design Review and Delegation Vs. Inheritance

This week I want to discuss another bullet from my code review checklist

Consider delegation over inheritance.  I have had some pretty heated debates on this topic in the past and I will even take it a step further and say the rule should state that we should favor delegation over inheritance.  Let’s consider the following classic (and intuitive) person/student/teacher example.  A class diagram might look something like this

image

Person is our base class; Teacher and student derive from person.  We can say that a student Is A person and that a Teacher Is A person.

The up-side of this design is it is simple, intuitive, and it will perform well.  The down side of this approach is that there is a tight coupling between the derived classes and the base class, we are breaking encapsulation by making the functionality of the derived class dependent on the functionality in the base class, and we may even break encapsulation on our Person class by using protected or public access modifiers where we normally wouldn’t do so. 

The problem with breaking encapsulation is that making a change to one class (our base class) can cause unintended changes in other consuming classes (our derived classes).  I can’t begin to tell you how many times I have seen a seemingly small change to a base class break huge pieces of an application.

Now let’s consider using delegation rather than inheritance.  We will change our class diagram to look like the following:

image

We can say a teacher Has A person and similarly student Has A person.

The down-side of this design is that we have to write more code to create and manage the person class and thus the code will not perform as well (though in most cases I suspect the performance hit is negligible).  We also cannot use polymorphism - we cannot treat a student as a person and we cannot treat a teacher as a person.  The up-side of this design is that we can decrease coupling by defining a person interface and using the interface as the return type for our Person property rather than the concrete Person class, and our design is more robust.  When we use delegation we can have a single instance of a person act as both a student and a teacher.  Try that in C# using the inheritance design!

 

Saturday, December 12, 2009

Design Review and the DRY Principle

DRY in the DRY principle is an acronym for Don’t Repeat Yourself.  While we are going to focus on applying the principle to code, it can and should be applied to more than just code.  The can be applied to database schemas, tests, documentation, test plans, design documents, build scripts and more.  Any time we start duplicating our work we are signing up for parallel maintenance and chances are we will eventually have the items fall out of synch.

We can use several techniques to avoid duplicate code.  Some obvious things we can do include using classes and methods to organize common code.  We can write an abstract or base class to implement common behaviors for multiple derived classes. 

We can use properties to perform a validation in one place rather than performing the validation anywhere we want to use a member variable:



Code Snippet



  1.         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
  2.         int aComplexInteger;
  3.         public int AComplexInteger
  4.         {
  5.             get { return aComplexInteger; }
  6.             set
  7.             {
  8.                 if (value == 0)
  9.                     throw new ArgumentOutOfRangeException("AComplexInteger");
  10.                 if (value != aComplexInteger)
  11.                 {
  12.                     aComplexInteger = value;
  13.                     //Maybe raise a value changed event
  14.                 }
  15.             }
  16.         \




One of my favorite techniques is constructor chaining.  If we have multiple constructors that perform similar logic , we should use constructor chaining to avoid duplicating code:



Code Snippet



  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace CicMaster
  6. {
  7.     class BetterConstructorLogic
  8.     {
  9.         #region WhyItIsBetter
  10.         //No duplicate code, this is called constructor chaining
  11.         //step through this
  12.         #endregion
  13.         string someString = string.Empty;
  14.         int someInteger = 0;
  15.         List<int> myIntegers = new List<int>();
  16.         public BetterConstructorLogic():this("A Default Value")
  17.         {
  18.             //someString = "A Default Value";
  19.             System.Diagnostics.Debug.WriteLine("In Default Constructor");
  20.         }
  21.         public BetterConstructorLogic(string aString):this(aString,123)
  22.         {
  23.             //someInteger = 123;
  24.             System.Diagnostics.Debug.WriteLine("In one param constructor");
  25.         }
  26.         public BetterConstructorLogic(string aString, int anInteger)
  27.         {
  28.             System.Diagnostics.Debug.WriteLine("In two param constructor");
  29.             someString = aString;
  30.             someInteger = anInteger;
  31.             myIntegers.Add(someInteger);
  32.             myIntegers.Add(someInteger ^ 3);
  33.             myIntegers.Add((int)(2 * 3.14 * someInteger));
  34.         }
  35.     }
  36. }




The final technique I would like to mention is use a factory to create all but the simplest objects.  The following (admittedly nonsensical) code needs to execute maybe a half dozen lines of code to construct an Order object. 



Code Snippet



  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading;
  6. namespace BusinessLayer
  7. {
  8.     class ObjectContext
  9.     {
  10.         public ObjectContext(String username)
  11.         {
  12.             //Look up the user permissions
  13.         }
  14.         public bool IsInTranaction { get; set; }
  15.         public bool BeginTransaction()
  16.         {
  17.             //do some transaction logic
  18.             return true;
  19.         }
  20.     }
  21.     class Order
  22.     {
  23.         public Order(ObjectContext theContext)
  24.         {
  25.         }
  26.     }
  27.     class Consumer
  28.     {
  29.         public Consumer()
  30.         {
  31.             ObjectContext ctx = new ObjectContext(Thread.CurrentPrincipal.Identity.Name);
  32.             if (!ctx.IsInTranaction)
  33.             {
  34.                 if (ctx.BeginTransaction())
  35.                 {
  36.                     //...
  37.                 }
  38.             }
  39.             Order order = new Order(ctx);
  40.         }
  41.     }
  42. }




Duplicating these few lines of code in a couple places is not that difficult.  Now say the application is enhanced and grows for a few years and suddenly we see this code duplicated dozens or hundreds of times.  At some point it is likely that we want to change the construction logic, finding and changing all the code we use to create the order is difficult, time consuming, and a QA burden. 

A better approach would be to encapsulate the logic required to build a new order.  Here is an implementation using a simple factory.  It is much easier to find, change, and test this code:



Code Snippet



  1.     static class OrderFactory
  2.     {
  3.         public static Order GetOrder()
  4.         {
  5.             ObjectContext ctx = new ObjectContext(Thread.CurrentPrincipal.Identity.Name);
  6.             if (!ctx.IsInTranaction)
  7.             {
  8.                 if (ctx.BeginTransaction())
  9.                 {
  10.                     //...
  11.                 }
  12.             }
  13.             return new Order(ctx);
  14.         }
  15.     }
  16.     class DryConsumer
  17.     {
  18.         public DryConsumer()
  19.         {
  20.             Order order = OrderFactory.GetOrder();
  21.         }
  22.     \




If we recognize that we are duplicating even a few lines of code over and over we need to take a serious look at the code and figure out a way to encapsulate the logic.

 

My design review checklist

Friday, November 27, 2009

Silverlight 3, the CollectionViewSource and sorting

In my previous post we were introduced to the CollectionViewSource. We have multiple ItemSources binding to the same collection using a CollectionViewSource – very powerful.  Now what if we want to sort the items displayed in one of our grid cells?  We will focus on row one and column one for the rest of this post.

I’ve added some more default data to the Control’s constructor to make things a little more interesting and also notice I have added a few buttons to the bottom of our control:

image

The Sort by Name button adds a sort description to the CollectionViewSource

theDataContext[0].Column1.View.SortDescriptions.Add(new SortDescription("SomeName",ListSortDirection.Ascending));

This code tells the CollectionViewSource to sort the items in it’s view by the SomeName property in ascending order.  Click the button and we see the following:

image

Click the Clear Sort Descriptions button.

The Sort By Value button tells the CollectionViewSource to sort the items in the view by the SomeValue Property in ascending order.

theDataContext[0].Column1.View.SortDescriptions.Add(new SortDescription("SomeValue", ListSortDirection.Ascending));

Click the button to see the following:

image

This time our list is sorted by the SomeValue property.

Where things get interesting is that you can add multiple sort descriptions.  Try clearing the filter, then adding the name and value filter (in that order) and we see the following:

image

The collection is first sorted by name, then by value.

Clear the filter and click the Value, then name button and we see this:

image

The collection is first sorted by SomeValue, then by SomeName.

The order is important!  If you want to add a sort description to the top of the sort order, simply use the Insert method as follows:

theDataContext[0].Column1.View.SortDescriptions.Insert(0,new SortDescription("SomeValue", ListSortDirection.Ascending));

Here is the modified XAML:



Code Snippet



  1. <UserControl xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" x:Class="SilverlightApplication1.MainPage"
  2.    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  5.    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
  6.     <UserControl.Resources>
  7.         <Style TargetType="Button">
  8.             <Setter Property="FontSize" Value="9"/>
  9.         </Style>
  10.     </UserControl.Resources>
  11.   <Grid x:Name="LayoutRoot">
  12.         <ScrollViewer VerticalScrollBarVisibility="Auto">
  13.             <StackPanel Orientation="Vertical">
  14.             <data:DataGrid x:Name="TheMainDataGrid"  AutoGenerateColumns="False" ItemsSource="{Binding}" HeadersVisibility="Column">
  15.                 <data:DataGrid.Columns>
  16.                     <data:DataGridTemplateColumn Header="Column 1">
  17.                         <data:DataGridTemplateColumn.CellTemplate>
  18.                             <DataTemplate>
  19.                                 <ListBox MinHeight="15" ItemsSource="{Binding Path=Column1.View}" >
  20.                                     <ListBox.ItemTemplate>
  21.                                         <DataTemplate>
  22.                                             <StackPanel Orientation="Horizontal">
  23.                                                 <TextBlock Margin="10" Text="{Binding Path=SomeName}" />
  24.                                                 <TextBlock Margin="10" Text="{Binding SomeValue}" />
  25.                                             </StackPanel>
  26.                                         </DataTemplate>
  27.                                     </ListBox.ItemTemplate>
  28.                                 </ListBox>
  29.                             </DataTemplate>
  30.                         </data:DataGridTemplateColumn.CellTemplate>
  31.                     </data:DataGridTemplateColumn>
  32.                     <data:DataGridTemplateColumn Header="Column 2">
  33.                         <data:DataGridTemplateColumn.CellTemplate>
  34.                             <DataTemplate>
  35.                                 <ListBox MinHeight="15" ItemsSource="{Binding Path=Column2.View}" >
  36.                                     <ListBox.ItemTemplate>
  37.                                         <DataTemplate>
  38.                                             <StackPanel Orientation="Horizontal">
  39.                                                 <TextBlock Margin="10" Text="{Binding SomeName}" />
  40.                                                 <TextBlock Margin="10" Text="{Binding SomeValue}" />
  41.                                             </StackPanel>
  42.                                         </DataTemplate>
  43.                                     </ListBox.ItemTemplate>
  44.                                 </ListBox>
  45.                             </DataTemplate>
  46.                         </data:DataGridTemplateColumn.CellTemplate>
  47.                     </data:DataGridTemplateColumn>
  48.                 </data:DataGrid.Columns>
  49.             </data:DataGrid>
  50.             <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
  51.                 <TextBlock Margin="5" Text="Name:"/>
  52.                 <TextBox x:Name="tbNAme" Margin="5" Width="100"></TextBox>
  53.                 <TextBlock Margin="5" Width="100" Text="Value:"/>
  54.                 <TextBox x:Name="tbValue" Margin="5" Width="100"></TextBox>
  55.                 <Button x:Name="AddButton" Margin="5" Content="Add" Click="AddButton_Click"/>
  56.             </StackPanel>
  57.             <StackPanel HorizontalAlignment="Left">
  58.                 <Button x:Name="ChangeNameButton" Width="150" Content="Change Name" Click="ChangeNameButton_Click"/>
  59.                 <Button x:Name="ChangeValueButton" Width="150" Content="Change Value" Click="ChangeValueButton_Click"/>
  60.                 <Button x:Name="refresh" Width="150" Content="Refresh CVS" Click="refresh_Click" />
  61.                 <Button x:Name="AddNameSort" Width="150" Content="Sort by Name" Click="AddNameSort_Click"/>
  62.                 <Button x:Name="AddValueSort" Width="150" Content="Sort by Value" Click="AddValueSort_Click"/>
  63.                 <Button x:Name="ClearSort" Width="150" Content="Clear Sort Descriptions" Click="ClearSort_Click"/>
  64.             </StackPanel>
  65.         </StackPanel>
  66.             </ScrollViewer>
  67.   </Grid>
  68. </
  69. UserControl>




Here is the modified code behind:



Code Snippet



  1. using System.Collections.ObjectModel;
  2. using System.Windows.Controls;
  3. using System.Windows.Data;
  4. using System.ComponentModel;
  5. namespace SilverlightApplication1
  6. {
  7.     public partial class MainPage : UserControl
  8.     {
  9.         ObservableCollection<TestClass> theMainCollection ;
  10.         ObservableCollection<TheRowClass> theDataContext;
  11.         public MainPage()
  12.         {
  13.             InitializeComponent();
  14.             theMainCollection = new ObservableCollection<TestClass>()
  15.                 { new TestClass() {SomeName="A",SomeValue=11},
  16.                     new TestClass() {SomeName="A",SomeValue=12},
  17.                     new TestClass() {SomeName="A",SomeValue=10},
  18.                     new TestClass() {SomeName="AB",SomeValue=21},
  19.                     new TestClass() {SomeName="A",SomeValue=13},
  20.                     new TestClass() {SomeName="B",SomeValue=13},
  21.                     new TestClass() {SomeName="B",SomeValue=14},
  22.                     new TestClass() {SomeName="AB",SomeValue=11},
  23.                     new TestClass() {SomeName="BC",SomeValue=22},
  24.                     new TestClass() {SomeName="AB",SomeValue=23},
  25.                     new TestClass() {SomeName="BC",SomeValue=24}
  26.                 };
  27.           
  28.             theDataContext = new ObservableCollection<TheRowClass>();
  29.             theDataContext.Add(new TheRowClass(theMainCollection));
  30.             theDataContext.Add(new TheRowClass(theMainCollection));
  31.             TheMainDataGrid.DataContext = theDataContext;
  32.             AddFilter(theDataContext[0].Column1, 1, 1);
  33.             AddFilter(theDataContext[0].Column2, 1, 2);
  34.             AddFilter(theDataContext[1].Column1, 2, 1);
  35.             AddFilter(theDataContext[1].Column2, 2, 2);
  36.            
  37.         }
  38.         private void AddFilter(CollectionViewSource cvs, int row, int column)
  39.         {
  40.             cvs.Filter += delegate(object o, FilterEventArgs eArgs)
  41.             {
  42.                 //Simple filter saying if column = 1 look for an A in the SomeName property
  43.                 //Else look for a B.  If we are in Row 1 look for a 1 in the SomeValue property
  44.                 //Else look for a 2
  45.                 string columnFilter;
  46.                 if (row == 1)
  47.                     columnFilter = "A";
  48.                 else
  49.                     columnFilter = "B";
  50.                 if ((eArgs.Item as TestClass).SomeValue.ToString().Contains(column.ToString()) && (eArgs.Item as TestClass).SomeName.Contains(columnFilter))
  51.                     eArgs.Accepted = true;
  52.                 else
  53.                     eArgs.Accepted = false;
  54.             };
  55.         }
  56.         private void AddButton_Click(object sender, System.Windows.RoutedEventArgs e)
  57.         {
  58.             TestClass tc = new TestClass();
  59.             tc.SomeName = tbNAme.Text;
  60.             tc.SomeValue = int.Parse(tbValue.Text);
  61.             theMainCollection.Add(tc);
  62.         }
  63.         private void ChangeNameButton_Click(object sender, System.Windows.RoutedEventArgs e)
  64.         {
  65.             theMainCollection[theMainCollection.Count-1].SomeName = "B";
  66.         }
  67.         private void ChangeValueButton_Click(object sender, System.Windows.RoutedEventArgs e)
  68.         {
  69.             theMainCollection[theMainCollection.Count - 1].SomeValue = 25;
  70.         }
  71.         private void refresh_Click(object sender, System.Windows.RoutedEventArgs e)
  72.         {
  73.             theDataContext[0].Column1.View.Refresh();
  74.             theDataContext[1].Column1.View.Refresh();
  75.             theDataContext[0].Column2.View.Refresh();
  76.             theDataContext[1].Column2.View.Refresh();
  77.         }
  78.         private void AddNameSort_Click(object sender, System.Windows.RoutedEventArgs e)
  79.         {
  80.             theDataContext[0].Column1.View.SortDescriptions.Add(new SortDescription("SomeName",ListSortDirection.Ascending));
  81.         }
  82.         private void AddValueSort_Click(object sender, System.Windows.RoutedEventArgs e)
  83.         {
  84.             theDataContext[0].Column1.View.SortDescriptions.Add(new SortDescription("SomeValue", ListSortDirection.Ascending));
  85.         }
  86.         private void ClearSort_Click(object sender, System.Windows.RoutedEventArgs e)
  87.         {
  88.             theDataContext[0].Column1.View.SortDescriptions.Clear();
  89.         }
  90.     }
  91. }




See my previous post to get the supporting code.

Monday, November 23, 2009

Silverlight 3 and the CollectionViewSource

I have been working on a really cool Silverlight application for the last several weeks.  The application is a graphically intensive businesses application with some pretty challenging lists nested in a grid and some very cool drag and drop functionality.  Here is the scenario: 

When the app starts up we make a call to a WCF service and get several hundred to several thousand objects back in an observable Collection.  We slice, dice, and manipulate the ObservableCollection<T> and now the challenge is we have to present several dozen to several hundred different views into this same data.  Throwing several hundred different views of the same data at a user all at once is not particularly useful so the views are stored in cells of a scrollable grid.  As I mentioned, each cell in the grid is a list of items that needs to be generated based on some attributes of the column and row in the grid.  Conceptually I want to break my original collection into a bunch of smaller collections but I didn’t want to physically break the collection  into multiple collections because the drag and drop functionality I spoke of would force me to get into the business of removing an item from one collection and adding it to another.  Additionally once the items are placed where we want them I would have to reassemble the master collection and send it back to the server.  Using a single ObservableCollection<T>) collection is a good fit.  How do we get multiple independent views into this ObservableCollection<T>) ? 

Enter the CollectionViewSource.  The CollectionViewSource allows us to set a source property (in our case the ObservableCollection<T> we got from the WCF call) then set one or many filters and finally we bind each cell to the View property of the CollectionViewSource to allow us to get a list of objects that satisfy our filter criteria.

Let’s whip up a very simple application and demonstrate the behavior. 

Here is the screen:  We have a grid with 2 rows and 2 columns.  Each Cell represents a list of type TestClass.  TestClass has two properties, SomeName – a string property, and SomeValue an integer property.  Below the grid we have two text boxes and and add button.  This will allow us to demonstrate that we can add items to the observable collection and have the UI automatically update.  Ignore the bottom 3 buttons for the moment.

image

Rather than make a WCF call we are building the “Main” collection of objects  in the MainPage constructor:



Code Snippet



  1.             theMainCollection = new ObservableCollection<TestClass>()
  2.                 { new TestClass() {SomeName="A",SomeValue=11},
  3.                     new TestClass() {SomeName="A",SomeValue=12},
  4.                     new TestClass() {SomeName="B",SomeValue=13},
  5.                     new TestClass() {SomeName="B",SomeValue=14},
  6.                     new TestClass() {SomeName="AB",SomeValue=21},
  7.                     new TestClass() {SomeName="BC",SomeValue=22},
  8.                     new TestClass() {SomeName="AB",SomeValue=23},
  9.                     new TestClass() {SomeName="BC",SomeValue=24}
  10.                 };




Now we need to create a collection for the grid to bind to.  Remember, what we want is for each grid cell to be a view into our “main” collection.  In our Example we are using a DataGrid object to display our views and we want the DataGrid to automatically update if we add an object to or remove an object from the main collection .  What we need here is an ObeservableCollection of objects with a CollectionViewSource property for each column.  In our example we have called the object TheRowClass. TheRowClass has two properties of type CollectionViewSource; Column1 and Column2.

Here is the code I use to initialize the ObservableCollection<TheRowClass>:



Code Snippet



  1.             theDataContext = new ObservableCollection<TheRowClass>();
  2.             theDataContext.Add(new TheRowClass(theMainCollection));
  3.             theDataContext.Add(new TheRowClass(theMainCollection));




We have two items in the ObservableCollection<TheRowClass>, each item has two properties of type CollectionViewSource for a total of four CollectionViewSource objects – one for each cell.  We are passing the main collection into the constructor and using it as the Source property for all of our CollectionViewSources.  All we have left to do is add a filter to each of these collections.  The filter we are adding is very simple:

if TestClass.SomeName contains the letter A and TestClass.SomeValue contains the number 1 display the item in row 1, column 1

if TestClass.SomeName contains the letter A and TestClass.SomeValue contains the number 2 display the item in row 1, column 2

if TestClass.SomeName contains the letter B and TestClass.SomeValue contains the number 1 display the item in row 2, column 1

if TestClass.SomeName contains the letter B and TestClass.SomeValue contains the number 2 display the item in row 2, column 2

Here is the code to pull that off:



Code Snippet



  1.             AddFilter(theDataContext[0].Column1, 1, 1);
  2.             AddFilter(theDataContext[0].Column2, 1, 2);
  3.             AddFilter(theDataContext[1].Column1, 2, 1);
  4.             AddFilter(theDataContext[1].Column2, 2, 2);




And the AddFilter method:



Code Snippet



  1.         private void AddFilter(CollectionViewSource cvs, int row, int column)
  2.         {
  3.             cvs.Filter += delegate(object o, FilterEventArgs eArgs)
  4.             {
  5.                 //Simple filter saying if column = 1 look for an A in the SomeName property
  6.                 //Else look for a B.  If we are in Row 1 look for a 1 in the SomeValue property
  7.                 //Else look for a 2
  8.                 string columnFilter;
  9.                 if (row == 1)
  10.                     columnFilter = "A";
  11.                 else
  12.                     columnFilter = "B";
  13.                 if ((eArgs.Item as TestClass).SomeValue.ToString().Contains(column.ToString()) && (eArgs.Item as TestClass).SomeName.Contains(columnFilter))
  14.                     eArgs.Accepted = true;
  15.                 else
  16.                     eArgs.Accepted = false;
  17.             };
  18.         \




Run the app, notice that the item with SomeName=’AB’ and SomeValue=21 is in all of the cells, this is because that item satisfies the filter criteria for each CollectionViewSource in each cell.  Very cool. 

 

Add the letter ‘A’ to the first textbox and 111 to the second textbox and click the add button.  Notice that an item is added to column 1 and row 1 of the grid without doing anything more than making a call to Collection.Add.

image

Play around with a few more combinations and see how the grid updates.

Now let’s look at the bottom 3 buttons, the first button changes the SomeName value of the last item in the main collection to “B”.  Here is the event handler:

theMainCollection[theMainCollection.Count-1].SomeName = "B";

Again, add the letter ‘A’ to the first textbox and 111 to the second textbox and click the add button.  Now click the Change Name button.  Notice that nothing happens.  I assure The item has changed.

Now click the Change Value button.  Notice that the last item in row 1 cell 1 has changed, the value has become 25.

image

We changed the SomeValue property to 25 in the button click event handler.   Here is the event handler:

theMainCollection[theMainCollection.Count - 1].SomeValue = 25;

Why did SomeValue change while SomeName remained the same?  Take a look at the TestClass and you will notice that it implements the INotifyPropertyChanged interface and in the SomeValue setter we call NotifyPropertyChanged which raises an event.  Silverlight has a Pub/Sub eventing system that the UI uses to determine when to update databound objects.  With just a few lines of code we were able to wire TestClass into this eventing system and force the UI to update anytime our SomeValue property changes.

This is all fine and good, call that event for SomeName and we are good to go, well…almops.  We still have a problem with the UI!  The last item in cell 1 column 1 is displaying an incorrect SomeName value, and since we changed SomeValue to 25 it should no longer be visible in this cell.  What is happening is that the Filter on the collection view source is applied when an item is added to or removed from the collection but NOT when an item changes.  To manually reapply the filter call CollectionViewSource.View.Refresh (for each CollectionViewSource) and you will see the grid display correctly.  Click the Refresh CVS button to see this functionality in action:

image 

Below is all of the source code.

Click here to see how to sort the grid cells.

Here is the XAML:



Code Snippet



  1. <UserControl xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" x:Class="SilverlightApplication1.MainPage"
  2.    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  5.    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
  6.     <UserControl.Resources>
  7.     </UserControl.Resources>
  8.   <Grid x:Name="LayoutRoot">
  9.         <StackPanel Orientation="Vertical">
  10.             <data:DataGrid x:Name="TheMainDataGrid"   AutoGenerateColumns="False" ItemsSource="{Binding}" HeadersVisibility="Column">
  11.                 <data:DataGrid.Columns>
  12.                     <data:DataGridTemplateColumn Header="Column 1">
  13.                         <data:DataGridTemplateColumn.CellTemplate>
  14.                             <DataTemplate>
  15.                                 <ListBox MinHeight="15" ItemsSource="{Binding Path=Column1.View}" >
  16.                                     <ListBox.ItemTemplate>
  17.                                         <DataTemplate>
  18.                                             <StackPanel Orientation="Horizontal">
  19.                                                 <TextBlock Margin="10" Text="{Binding Path=SomeName}" />
  20.                                                 <TextBlock Margin="10" Text="{Binding SomeValue}" />
  21.                                             </StackPanel>
  22.                                         </DataTemplate>
  23.                                     </ListBox.ItemTemplate>
  24.                                 </ListBox>
  25.                             </DataTemplate>
  26.                         </data:DataGridTemplateColumn.CellTemplate>
  27.                     </data:DataGridTemplateColumn>
  28.                     <data:DataGridTemplateColumn Header="Column 2">
  29.                         <data:DataGridTemplateColumn.CellTemplate>
  30.                             <DataTemplate>
  31.                                 <ListBox MinHeight="15" ItemsSource="{Binding Path=Column2.View}" >
  32.                                     <ListBox.ItemTemplate>
  33.                                         <DataTemplate>
  34.                                             <StackPanel Orientation="Horizontal">
  35.                                                 <TextBlock Margin="10" Text="{Binding SomeName}" />
  36.                                                 <TextBlock Margin="10" Text="{Binding SomeValue}" />
  37.                                             </StackPanel>
  38.                                         </DataTemplate>
  39.                                     </ListBox.ItemTemplate>
  40.                                 </ListBox>
  41.                             </DataTemplate>
  42.                         </data:DataGridTemplateColumn.CellTemplate>
  43.                     </data:DataGridTemplateColumn>
  44.                 </data:DataGrid.Columns>
  45.             </data:DataGrid>
  46.             <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
  47.                 <TextBlock Margin="5" Text="Name:"/>
  48.                 <TextBox x:Name="tbNAme" Margin="5" Width="100"></TextBox>
  49.                 <TextBlock Margin="5" Width="100" Text="Value:"/>
  50.                 <TextBox x:Name="tbValue" Margin="5" Width="100"></TextBox>
  51.                 <Button x:Name="AddButton" Margin="5" Content="Add" Click="AddButton_Click"/>
  52.             </StackPanel>
  53.             <StackPanel HorizontalAlignment="Left">
  54.                 <Button x:Name="ChangeNameButton" Width="100" Content="Change Name" Click="ChangeNameButton_Click"/>
  55.                 <Button x:Name="ChangeValueButton" Width="100" Content="Change Value" Click="ChangeValueButton_Click"/>
  56.                 <Button x:Name="refresh" Width="100" Content="Refresh CVS" Click="refresh_Click" />
  57.             </StackPanel>
  58.         </StackPanel>
  59.   </Grid>
  60. </UserControl>




Here is the code behind:



Code Snippet



  1. using System.Collections.ObjectModel;
  2. using System.Windows.Controls;
  3. using System.Windows.Data;
  4. namespace SilverlightApplication1
  5. {
  6.     public partial class MainPage : UserControl
  7.     {
  8.         ObservableCollection<TestClass> theMainCollection ;
  9.         ObservableCollection<TheRowClass> theDataContext;
  10.         public MainPage()
  11.         {
  12.             InitializeComponent();
  13.             theMainCollection = new ObservableCollection<TestClass>()
  14.                 { new TestClass() {SomeName="A",SomeValue=11},
  15.                     new TestClass() {SomeName="A",SomeValue=12},
  16.                     new TestClass() {SomeName="B",SomeValue=13},
  17.                     new TestClass() {SomeName="B",SomeValue=14},
  18.                     new TestClass() {SomeName="AB",SomeValue=21},
  19.                     new TestClass() {SomeName="BC",SomeValue=22},
  20.                     new TestClass() {SomeName="AB",SomeValue=23},
  21.                     new TestClass() {SomeName="BC",SomeValue=24}
  22.                 };
  23.           
  24.             theDataContext = new ObservableCollection<TheRowClass>();
  25.             theDataContext.Add(new TheRowClass(theMainCollection));
  26.             theDataContext.Add(new TheRowClass(theMainCollection));
  27.             TheMainDataGrid.DataContext = theDataContext;
  28.             AddFilter(theDataContext[0].Column1, 1, 1);
  29.             AddFilter(theDataContext[0].Column2, 1, 2);
  30.             AddFilter(theDataContext[1].Column1, 2, 1);
  31.             AddFilter(theDataContext[1].Column2, 2, 2);
  32.             
  33.         }
  34.         private void AddFilter(CollectionViewSource cvs, int row, int column)
  35.         {
  36.             cvs.Filter += delegate(object o, FilterEventArgs eArgs)
  37.             {
  38.                 //Simple filter saying if column = 1 look for an A in the SomeName property
  39.                 //Else look for a B.  If we are in Row 1 look for a 1 in the SomeValue property
  40.                 //Else look for a 2
  41.                 string columnFilter;
  42.                 if (column == 1)
  43.                     columnFilter = "A";
  44.                 else
  45.                     columnFilter = "B";
  46.                 if ((eArgs.Item as TestClass).SomeValue.ToString().Contains(column.ToString()) && (eArgs.Item as TestClass).SomeName.Contains(columnFilter))
  47.                     eArgs.Accepted = true;
  48.                 else
  49.                     eArgs.Accepted = false;
  50.             };
  51.         }
  52.         private void AddButton_Click(object sender, System.Windows.RoutedEventArgs e)
  53.         {
  54.             TestClass tc = new TestClass();
  55.             tc.SomeName = tbNAme.Text;
  56.             tc.SomeValue = int.Parse(tbValue.Text);
  57.             theMainCollection.Add(tc);
  58.         }
  59.         private void ChangeNameButton_Click(object sender, System.Windows.RoutedEventArgs e)
  60.         {
  61.             theMainCollection[theMainCollection.Count-1].SomeName = "B";
  62.         }
  63.         private void ChangeValueButton_Click(object sender, System.Windows.RoutedEventArgs e)
  64.         {
  65.             theMainCollection[theMainCollection.Count - 1].SomeValue = 25;
  66.         }
  67.         private void refresh_Click(object sender, System.Windows.RoutedEventArgs e)
  68.         {
  69.             theDataContext[0].Column1.View.Refresh();
  70.             theDataContext[1].Column1.View.Refresh();
  71.             theDataContext[0].Column2.View.Refresh();
  72.             theDataContext[1].Column2.View.Refresh();
  73.         }
  74.     }
  75. }




Here is the TestClass Code:



Code Snippet



  1. using System;
  2. using System.ComponentModel;
  3. namespace SilverlightApplication1
  4. {
  5.     public class TestClass:INotifyPropertyChanged
  6.     {
  7.         public String SomeName { get; set; }
  8.         private int _someValue;
  9.         public int SomeValue
  10.         {
  11.             get
  12.             {
  13.                 return _someValue;
  14.             }
  15.             set
  16.             {
  17.                 if (value != this._someValue)
  18.                 {
  19.                     this._someValue = value;
  20.                     NotifyPropertyChanged("SomeValue");
  21.                 }
  22.             }
  23.         }
  24.         #region INotifyPropertyChanged Members
  25.         public event PropertyChangedEventHandler PropertyChanged;
  26.         #endregion
  27.         private void NotifyPropertyChanged(String info)
  28.         {
  29.             if (PropertyChanged != null)
  30.             {
  31.                 PropertyChanged(this, new PropertyChangedEventArgs(info));
  32.             }
  33.         }
  34.     }
  35. }




Here is TheRowClass:



Code Snippet



  1. using System.Windows.Data;
  2. using System.Collections;
  3. namespace SilverlightApplication1
  4. {
  5.     public class TheRowClass
  6.     {
  7.         public TheRowClass(IEnumerable source)
  8.         {
  9.             Column1 = new CollectionViewSource();
  10.             Column1.Source = source;
  11.             Column2 = new CollectionViewSource();
  12.             Column2.Source = source;
  13.         }
  14.         public CollectionViewSource Column1 { get; set; }
  15.         public CollectionViewSource Column2 { get; set; }
  16.     }
  17. }