Pages

Showing posts with label Sorting. Show all posts
Showing posts with label Sorting. Show all posts

Tuesday, 31 July 2012

WPF DataGrid Custom Column Sorting

With thanks to Aran Holland with this Stackoverflow answer.
I wanted to sort a DataGrid by StudentID. Unfortunately the StudentID is mainly numeric but with an alpha prefix - typically S12345. Paul had previously written SortableStringValue so I just needed to hook that up to the DataGrid using a custom class that implements IComparer.
In the code-behind I hooked up the DataGrid's Sorting event in the constructor.

public StudentCourseSearchView()
{
   InitializeComponent();

   SearchResults.Sorting += new DataGridSortingEventHandler(SearchResults_Sorting);
}
Then in the private SearchResults_Sorting method I determine if it is the StudentID column that is being sorted and instantiate the custom sorting class.

void SearchResults_Sorting(object sender, System.Windows.Controls.DataGridSortingEventArgs e)
{
   DataGridColumn column = e.Column;

   // I'm only interested in a custom sort for the StudentID column
   if (column.SortMemberPath != "StudentID.Number") return;

   IComparer comparer = null;

   // Prevent the built-in sort from sorting 
   e.Handled = true;

   ListSortDirection direction = (column.SortDirection != ListSortDirection.Ascending) ? ListSortDirection.Ascending : ListSortDirection.Descending;

   // Set the sort order on the column 
   column.SortDirection = direction;

   //use a ListCollectionView to do the sort. 
   ListCollectionView lcv = (ListCollectionView)CollectionViewSource.GetDefaultView(TypedViewModel.Students);

   // Instantiate the custom sort class which implements IComparer
   comparer = new StudentSearchResultStudentIdSort(direction);

   // Apply the sort 
   lcv.CustomSort = comparer;
}
Finally, the StudentSearchResultStudentIdSort class implements the Compare method using the SortableStringValue extension method.

public class StudentSearchResultStudentIdSort : IComparer
{
   ListSortDirection _direction;

   public StudentSearchResultStudentIdSort(ListSortDirection direction)
   {
      _direction = direction;
   }
   public int Compare(object x, object y)
   {
      string studentIdX = (x as StudentSearchResult).StudentID.Value;
      string studentIdY = (y as StudentSearchResult).StudentID.Value;

      if (_direction == ListSortDirection.Ascending)
      {
         return studentIdX.SortableStringValue().CompareTo(studentIdY.SortableStringValue());
      }
      else
      {
         return studentIdY.SortableStringValue().CompareTo(studentIdX.SortableStringValue());
      }
   }
}

Saturday, 10 March 2012

Simple WPF ColorPicker ComboBox

I needed a ColorPicker that could be embedded in a DataGrid, simple to operate and could save the color name in the database. A ComboBox seemed the obvious choice.
A simple list of colors is reasonably straightforward using a little Reflection:
      public List<string> SimpleColorList
      {
         get
         {
            List<string> colorList = new List<string>();
            foreach (PropertyInfo pi in typeof(Colors).GetProperties())
            {
               colorList.Add(pi.Name);
            }
            return colorList;
         }
      }
And bound it to a ComboBox. Using Mode=OneTime means the ColorList is only built once.
<ComboBox ItemsSource="{Binding Path=SimpleColorList, Mode=OneTime}"
            SelectedValue="{Binding SelectedColor}"
            Width="200" />
Adding a ItemTemplate to the ComboBox makes it a little more interesting:
<ComboBox ItemsSource="{Binding Path=SimpleColorList, Mode=OneTime}"
            SelectedValue="{Binding SelectedColor}"
            Width="200">
   <ComboBox.ItemTemplate>
      <DataTemplate>
         <StackPanel Orientation="Horizontal">
            <TextBlock Width="130"
                        Text="{Binding}" />
            <Border BorderBrush="Black"
                     BorderThickness="1"
                     CornerRadius="4"
                     Margin="0,1,0,1"
                     Background="{Binding}"
                     Width="40"
                     Height="18">
            </Border>
         </StackPanel>
      </DataTemplate>
   </ComboBox.ItemTemplate>
</ComboBox>
The straight alphabetic sorted list is fine but I wanted to sort by "color", in general terms from lighter to darker. I found some code on the web that would convert the RGB into the HSL color space. (I'm afraid I can't find the code again so my apologies to its author for the lack of attribution).
   public static class HslValueConverter
   {
      /// <summary>
      /// Converts a WPF RGB color to an HSL color
      /// </summary>
      /// <param name="rgbColor">The RGB color to convert.</param>
      /// <returns>An HSL color object equivalent to the RGB color object passed in.</returns>
      public static HslColor RgbToHsl(string name, Color rgbColor)
      {
         // Initialize result
         var hslColor = new HslColor();
         hslColor.Name = name;

         // Convert RGB values to percentages
         double r = (double)rgbColor.R / 255;
         var g = (double)rgbColor.G / 255;
         var b = (double)rgbColor.B / 255;
         var a = (double)rgbColor.A / 255;

         // Find min and max RGB values
         var min = Math.Min(r, Math.Min(g, b));
         var max = Math.Max(r, Math.Max(g, b));
         var delta = max - min;

         /* If max and min are equal, that means we are dealing with 
          * a shade of gray. So we set H and S to zero, and L to either
          * max or min (it doesn't matter which), and  then we exit. */

         //Special case: Gray
         if (max == min)
         {
            hslColor.Hue = 0;
            hslColor.Saturation = 0;
            hslColor.Lightness = max;
            return hslColor;
         }

         /* If we get to this point, we know we don't have a shade of gray. */

         // Set L
         hslColor.Lightness = (min   max) / 2;

         // Set S
         if (hslColor.Lightness < 0.5)
         {
            hslColor.Saturation = delta / (max   min);
         }
         else
         {
            hslColor.Saturation = delta / (2.0 - max - min);
         }

         // Set H
         if (r == max) hslColor.Hue = (g - b) / delta;
         if (g == max) hslColor.Hue = 2.0   (b - r) / delta;
         if (b == max) hslColor.Hue = 4.0   (r - g) / delta;
         hslColor.Hue *= 60;
         if (hslColor.Hue < 0) hslColor.Hue  = 360;

         // Set A
         hslColor.Alpha = a;

         // Set return value
         return hslColor;

      }
   }
   public struct HslColor
   {
      public string Name { get; set; }
      public double Alpha;
      public double Hue;
      public double Lightness;
      public double Saturation;
   }
Then I changed the property (Note: the property name has changed to SortedColorList, so the ComboBox Binding will have to be altered) "getter" to sort by Saturation, Hue and Lightness. I also decided to exclude the Transparent color.
      public List<string> SortedColourList
      {
         get
         {
            List<HslColor> colorList = new List<HslColor>();
            foreach (PropertyInfo pi in typeof(Colors).GetProperties())
            {
               Color color = (Color)pi.GetValue(null, null);
               // Only select non-Transparent colors
               if (color.A != 0) colorList.Add(HslValueConverter.RgbToHsl(pi.Name, color));
            }
            return colorList.OrderBy(c => c.Saturation)
                         .OrderBy(c => c.Hue)
                         .OrderByDescending(c => c.Lightness)
                         .Select(c => c.Name).ToList<string>();
         }
      }
The SimpleColorPicker project is available on GoogleCode.

Thursday, 2 February 2012

SortableStringValue

We have a number of different alphanumeric codes in our application that occasionally need to be sorted. Using the standard sorting mechanism we typically end up with a list that looks like this:
A1
A10
A2
A20
A3
A4

Paul has written an extension method that allows us to sort into alpha then numeric order, like this:
A1
A2
A3
A4
A10
A20

The extension method is used like this:
    var result = list.OrderBy(l => l.SortableStringValue())


public static class Extensions
{
   public static String SortableStringValue(this String text)
   {
      StringBuilder  textBuilder;
      StringBuilder  numberBuilder;
      textBuilder = new StringBuilder();
      numberBuilder = new StringBuilder();
      //Look at each char in the string
      foreach(char value in text)
      {
         switch (value)
         {
            //If its a number add it to the number builder
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
               numberBuilder.Append(value);
               break;
            //Else add it to the text builder
            default:             
               //Before we add the text, format and add any number we may have
               if (numberBuilder.Length > 0)
               {
                  textBuilder.Append(numberBuilder.ToString().PadLeft(16,'0'));
                  numberBuilder.Clear();
               }
               textBuilder.Append(value);
                  
               break;
         }
      }

      //Check to see if we have any numbers left in the builder
      if (numberBuilder.Length > 0)
      {
         textBuilder.Append(numberBuilder.ToString().PadLeft(16,'0'));
      }
      //Return the string value (The replace will allow negative number to be sorted)
      return textBuilder.ToString().Replace("-0","-00");
   }
}

Wednesday, 15 July 2009

Sorting Arrays

Martin pointed me at this interesting blog regarding Array.Sort Sorting Arrays

I used Array.Sort previously in this post Sorting and De-Duping Arrays but didn't investigate further at that time.

A quick look at Array.Sort(...) shows that it has 17 overloads and lots of things to try out at some point in the future.

Thursday, 29 January 2009

Sorting and De-duping arrays

I seem to be obsessed with sorting based on the number of posts I've made on the topic.
Sorting a simple string[] is easy using:

string[] recordIds = { "q", "z", "a", "b", "q" };
 
Array.Sort(recordIds);

To stop a third party component from failing we had to ensure that our string[] array contained only unique strings. Sorting and de-duping using LINQ could be done in a single line:

string[] recordIds = { "q", "z", "a", "b", "q" };
 
var sortedUniqueIds = (from id in recordIds orderby id select id).Distinct();
 
int i=0;
foreach (string id in sortedUniqueIds)
{
   recordIds[i++] = id;
}

Microsoft have an excellent LINQ samples page: 101 LINQ Samples

Tuesday, 27 January 2009

Sorting Generic Collections - Redux

Using LINQ it turns out to be quite easy to sort a generic Dictionary by either Key or Value.

In these examples a list of sorted keys is returned in the 'keys' variable.

Alternately the LINQ statement can used directly in a foreach loop.

Dictionary<string, string> dict = new Dictionary<string, string>();
...


// Sort by Key
var keys = from key in dict.Keys orderby key ascending select key;


// Sort by Value
var keys = from key in dict.Keys orderby dict[key] ascending select key;


// Sort within foreach(...) by Key
foreach(string aKey in (from key in dict.Keys orderby key ascending select key)){
...
}

Monday, 6 October 2008

Sorting a collection using Linq and 'SortExpression' string

Bruce suggested that we use LINQ to sort collections. I found the following code on Miron Abrahason's blog but haven't had the chance to try it out yet.

Already happened to you that you had a collection of object from type 'X' with some properties, and you had to sort it one time by property 'ID', and another time by property 'Name' ? You wished that you can sort it by just using a 'Sort Expression' ? If still not, I'm sure this moment will arrive sooner or later. Let me save you some time and an headache.

This is how it can be done:

public static IEnumerable Sort(this IEnumerable source, string sortExpression)
{
string[] sortParts = sortExpression.Split(' ');
var param = Expression.Parameter(typeof(T), string.Empty);
try
{
var property = Expression.Property(param, sortParts[0]);
var sortLambda = Expression.Lambda>(Expression.Convert(property, typeof(object)), param);
if (sortParts.Length > 1 && sortParts[1].Equals("desc", StringComparison.OrdinalIgnoreCase))
{
return source.AsQueryable().OrderByDescending(sortLambda);
}
return source.AsQueryable().OrderBy(sortLambda);
}
catch (ArgumentException)
{
return source;
}
}

Just drop it in a static class, and you will be able to sort any collection that implement the interface IEnumerable.

Lets say you have a class 'User':
public class User
{
    public int ID { get; set; }
    public string Name { get; set; }
}

and a List collection: users. You can sort it however you want:
IEnumerable sortedUsersIEnumerable = users.Sort("ID desc");

Or
List sortedUsersList = users.Sort("Name").ToList();

Sunday, 5 October 2008

Sorting Generic Collections

You can simplify the sorting by using the anonymous delegate as shown below and you don't need to implement IComparable interface:

(Note: For a simpler technique using LINQ see: Sorting Generic Collections - Redux)
List persons = new List();
persons.Add( new Person("Tom",30) );
persons.Add(new Person("Harry", 55));

// sort in ascending order
persons.Sort( delegate(Person person0, Person person1)
{
return
person0.FirstName.CompareTo(person1.FirstName);
} );


// sort in descending order
persons.Sort( delegate(Person person0, Person person1)
{
return
person1.FirstName.CompareTo(person0.FirstName);
} );