Pages

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

Friday, 25 May 2012

Editing XAML in Visual Studio

For no apparent reason VS 2010 decided to start crashing when editing XAML files. So I changed the default editor used to "Source Code (Text) Editor" which gives me syntax colouring and Intellisense but without the (crashing) Design view.
In the Solution Explorer Right-click on any XAML file and choose "Open With...".
To use the Design mode simply select the XAML file and click the "View Designer" icon in the Solution Explorer toolbar.



WPF Property Value Precedence and Style Setter

My DataTrigger / Setter was not changing the StrokeThickness property because it was exclicitly set in the Rectange tag. Property Value Precedence meant that the Setter was being overriden by the explicit property value. Moving the StrokeThickness default value into its own Setter fixed my problem.

<Rectangle
            Stroke="Black">
   <Rectangle.Style>
      <Style TargetType="{x:Type Rectangle}">
         <Setter Property="StrokeThickness"
                  Value="1" />
         <Style.Triggers>
            <DataTrigger Binding="{Binding Path=Selected}"
                           Value="True">
               <Setter Property="StrokeThickness"
                        Value="2" />
            </DataTrigger>
         </Style.Triggers>
      </Style>
   </Rectangle.Style>
</Rectangle>

Thursday, 3 May 2012

SQL Column Names

I wanted to ensure that all my DB columns had been defined consistently.

Ray gave me this simple query to run on INFORMATION_SCHEMA.COLUMNS:

SELECT
   TABLE_NAME,
   COLUMN_NAME,
   DATA_TYPE,
   CHARACTER_MAXIMUM_LENGTH,
   NUMERIC_PRECISION,
   NUMERIC_SCALE,
   CHARACTER_SET_NAME
FROM
   INFORMATION_SCHEMA.COLUMNS
ORDER BY
   COLUMN_NAME

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.

Saturday, 3 March 2012

DataType instead of DataTemplateSelector

Nearly three years ago I posted about the use of the DataTemplateSelector.
It transpires that because I was using different types of objects in my ItemsSource I could have used the DataType attribute of the DataTemplate.
Define the DataTemplates in the usual way but add the DataType attribute.
      <DataTemplate DataType="{x:Type dts:BookingLineItem}">
      <DataTemplate DataType="{x:Type dts:CCardPaymentLineItem}">
      <DataTemplate DataType="{x:Type dts:CashPaymentLineItem}">
      <DataTemplate DataType="{x:Type dts:LineItem}">
There is no need to define the DataTemplateSelector in the resources, no C# class to write and the ListBox becomes simpler:
      <ListBox Name="theSecondTillRoll"
               Height="150"
               Width="330"
               ItemsSource="{Binding SaleItems}" />
The DataTemplateSelector project is on GoogleCode and demonstrates both techniques.

Firing up the default Browser

This simple snippet shows how to display a URL in a WPF window and fire up the default browser when the link is clicked.
Starting with a TextBlock in the XAML:
      <TextBlock>
         <Hyperlink Click="Hyperlink_Click"
                    NavigateUri="http://mikestedman.blogspot.com/2012/03/wpf-string-formatting.html">Link to original WPF Waltz blog entry</Hyperlink>
      </TextBlock>
And a simple event handler in the code-behind:
      private void Hyperlink_Click(object sender, RoutedEventArgs e)
      {
         Process.Start((sender as Hyperlink).NavigateUri.ToString());
      }

Friday, 2 March 2012

WPF String Formatting

There are a number of ways to use formatted strings and we seem to use nearly all of them in our application. Because the format of the formatting string varies slightly I thought I'd document the different techniques.
Firstly we need some simple properties for the XAML Bindings. Notice that the ToString version needs neither the index (0:) or the curly braces.
public partial class MainWindow : Window
{
   public String FormattedDate
   {
      get { return String.Format("{0:dd MMM yyy}", DateTime.Now); }
   }
   public String DateToString
   {
      get { return DateTime.Now.ToString("dd MMM yyy"); }
   }
   public DateTime RawDateTime
   {
      get { return DateTime.Now; }
   }
   public MainWindow()
   {
      InitializeComponent();
      DataContext = this;
   }
}
The first two TextBlocks bind directly to the pre-formatted properties.
      <TextBlock Text="{Binding FormattedDate}" />
      <TextBlock Text="{Binding DateToString}" />
Or we can bind to the raw data and use StringFormat. We can escape the curly braces in a couple of ways.
      <TextBlock Text="{Binding Path=RawDateTime, StringFormat=\{0:dd MMM yyyy\}}" />
      <TextBlock Text="{Binding Path=RawDateTime, StringFormat={}{0:dd MMM yyyy}}" />
The leading curly braces in the second version can be replaced by constant text that is rendered as part of the formatted string. But it seems a bit "sensitive" so I don't use it.
      <TextBlock Text="{Binding Path=RawDateTime, StringFormat=Today is {0:dd MMM yyyy}}" />
Next up is a Converter for which we need an IValueConverter class:
class DateConverter : IValueConverter
{
   public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
   {
      if (value is DateTime)
      {
         return ((DateTime)value).ToString("dd MMM yyy");
      }
      return value;
   }

   public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
   {
      throw new NotImplementedException();
   }
}
And the corresponding XAML to use the Converter:
        xmlns:local="clr-namespace:WpfWaltz"
...
   <Window.Resources>
      <local:DateConverter x:Key="dateConverter"; />
   </Window.Resources>
...
      <TextBlock Text="{Binding Path=RawDateTime, Converter={StaticResource dateConverter}}" />
Finally, I had assumed that the indexer in the StringFormat was just a necessary evil since it was always 0: and hadn't considered using StringFormat with a MultiBinding like this:
      <TextBlock>
         <TextBlock.Text>
            <MultiBinding StringFormat="{}{0:dd MMM yyyy} {1:hh:mm:ss}">
               <Binding Path="RawDateTime" /> 
               <Binding Path="RawDateTime" />
            </MultiBinding>
         </TextBlock.Text>
      </TextBlock>
The StringFormatting project that has all the code can be downloaded from GoogleCode.

Wednesday, 29 February 2012

DataGrid loading message User Control

The DataGrid loading message I created was pretty straightforward and easy to replicate in other DataGrids. But that isn't the WPF way. So instead I spent many happy hours converting into a User Control.

The Specification
Whilst the dataset is being retrieved display a "loading" message.
Once the dataset load is complete if there are no suitable records to list display a "empty dataset" message.

Executive Summary
Skip the details and download the source from GoogleCode.

The Implementation
Starting with a new User Control and adding two dependency properties to store the "loading" and "empty dataset" messages.
   public partial class DataGridProgressBar : UserControl
   {
      public readonly static DependencyProperty LoadingMessageProperty =
         DependencyProperty.Register(
            "LoadingMessage",
            typeof(string),
            typeof(DataGridProgressBar));

      public readonly static DependencyProperty EmptyDatasetMessageProperty =
         DependencyProperty.Register(
            "EmptyDatasetMessage",
            typeof(string),
            typeof(DataGridProgressBar));

      public string LoadingMessage
      {
         get { return (string)GetValue(LoadingMessageProperty); }
         set { SetValue(LoadingMessageProperty, (string)value); }
      }
      public string EmptyDatasetMessage
      {
         get { return (string)GetValue(EmptyDatasetMessageProperty); }
         set { SetValue(EmptyDatasetMessageProperty, (string)value); }
      }
   }
Next the user control needs to know the state of the dataset loading and the number of records in the dataset.
So time to add another couple of dependency properties.
      public readonly static DependencyProperty RecordCountProperty = 
         DependencyProperty.Register(
            "RecordCount", 
            typeof(long), 
            typeof(DataGridProgressBar));
      
      public readonly static DependencyProperty LoadInProgressProperty = 
         DependencyProperty.Register(
            "LoadInProgress", 
            typeof(bool?), 
            typeof(DataGridProgressBar));

      public long RecordCount
      {
         get { return (long)GetValue(RecordCountProperty); }
         set { SetValue(RecordCountProperty, (long)value); }
      }

      public bool? LoadInProgress
      {
         get { return (bool?)GetValue(LoadInProgressProperty); }
         set { SetValue(LoadInProgressProperty, (bool?)value); }
      }
Next we turn our attention to the XAML for the new User Control, remembering to add a namespace reference:
<UserControl x:Class="DataGridWatermark.DataGridProgressBar"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:local="clr-namespace:DataGridWatermark">
The actual control consists of just a single, named, TextBlock:
<UserControl .......

   <TextBlock x:Name="PART_Message" />
</UserControl>
All the hard work is done in the Style which has three main parts:
1) Initial / default style
2) Changes to the style in the Data Loading state
3) Changes to the style when there is no data to be shown.

The initial style hides the TextBlock by setting its Visibility to Collapsed, a Red font and binds to the LoadingMessage property.
      <Style TargetType="{x:Type TextBlock}">
         <Setter Property="Visibility"
                 Value="Collapsed" />
         <Setter Property="Foreground"
                 Value="Red" />
         <Setter Property="Text"
                 Value="{Binding LoadingMessage}"; />
...
      </Style>
We use a DataTrigger to detect the data loading state by binding to the LoadInProgress property and changing the Visibility from Collapsed to Visible.
         <Style.Triggers>
            <DataTrigger Binding="{Binding LoadInProgress}"
                         Value="true">
               <Setter Property="Visibility"
                       Value="Visible" />
            </DataTrigger>
...
         </Style.Triggers>
The empty dataset is a little more complicated because there are two conditions that need to be satisfied before the Empty Dataset message is displayed. The two conditions are specified inside a MultiDataTrigger.
            <MultiDataTrigger>
               <MultiDataTrigger.Conditions>
                  <Condition Binding="{Binding RecordCount}"
                             Value="0" />
                  <Condition Binding="{Binding LoadInProgress}"
                             Value="false" />
               </MultiDataTrigger.Conditions>
               <Setter Property="Foreground"
                       Value="Silver" />
               <Setter Property="Text"
                       Value="{Binding EmptyDatasetMessage}" />
               <Setter Property="Visibility"
                       Value="Visible" />
            </MultiDataTrigger>
The final piece of the jigsaw is providing a DataContext for the property bindings. I chose to explicitly set the DataContext in the code-behind in the constructor.
public DataGridProgressBar()
{
   InitializeComponent();
   PART_Message.DataContext = this;
}
And finally we can add it to the original DataGrid.
<Window x:Class="DataGridWatermark.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:uc="clr-namespace:DataGridWatermark"
...
      <DataGrid>
         <DataGrid.Background>
            <VisualBrush Stretch="None">
               <VisualBrush.Visual>
                  <StackPanel>
                     <uc:DataGridProgressBar LoadingMessage="Loading..."
                                             EmptyDatasetMessage="No Records Found"
                                             RecordCount="{Binding Path=TheRecordCount}"
                                             LoadInProgress="{Binding Path=TheLoadState}" />
                  </StackPanel>
               </VisualBrush.Visual>
            </VisualBrush>
         </DataGrid.Background>
...
</Window>

Saturday, 25 February 2012

Datagrid loading message and VisualBrush

I have to load a DataGrid from a slow running DB query. To keep the UI responsive I'm running the data retrieval on a worker thread but I wanted an inobtrusive message to be displayed to the User during the loading phase. I replaced the DataGrid's Background with a TextBlock contained in VisualBrush. With the TextBlock's Text and Visibility bound to the underlying ViewModel. For those cases where there is no data to be shown in the DataGrid I show an alternate message.
<DataGrid ItemsSource="{Binding Source{StaticResource ViewModel},Path=EnrolledStudents}"
            CanUserAddRows="False"
            CanUserDeleteRows="False"
            AutoGenerateColumns="False"
            IsReadOnly="True">
   <DataGrid.Background>
      <VisualBrush Stretch="None">
         <VisualBrush.Visual>
            <StackPanel Background="White">
               <TextBlock HorizontalAlignment="Center"
                           VerticalAlignment="Center"
                           FontSize="10pt"
                           Margin="2"
                           Foreground="Red"
                           Text="{Binding Path=Strings.V_LabelLoading}"
                           Visibility="{Binding Source{StaticResource ViewModel}, Path=EnrolledStudentLoadingInProgress, Converter={StaticResource boolToCollapsedVisibilityConverter}}" />
               <TextBlock HorizontalAlignment="Center"
                           VerticalAlignment="Center"
                           FontSize="10pt"
                           Margin="2"
                           Foreground="Silver"
                           Text="{Binding Path=Strings.V_LabelNoEnrolledStudents}"
                           Visibility="{Binding Source{StaticResource ViewModel}, Path=NoEnrolledStudents, Converter={StaticResource boolToCollapsedVisibilityConverter}}" />
            </StackPanel>
         </VisualBrush.Visual>
      </VisualBrush>
   </DataGrid.Background>
   <DataGrid.Columns>

Wednesday, 15 February 2012

SplitCSV an extension method to read CSV files

Hard on the heels on Paul's SortableStringValue I thought I would convert 'h.brouwer's Regex CSV splitter into an extension method. I've also added a couple of options to bring it in line with the standard String.Split() method.
public static class Extensions
{
   public static string[] SplitCsv(this string line)
   {
      return SplitCsv(line, int.MaxValue, StringSplitOptions.None);
   }
   public static string[] SplitCsv(this string line, int count)
   {
      return SplitCsv(line, count, StringSplitOptions.None);
   }
   public static string[] SplitCsv(this string line, StringSplitOptions options)
   {
      return SplitCsv(line, int.MaxValue, options);
   }
   public static string[] SplitCsv(this string line, int count, StringSplitOptions options)
   {
      return (from Match m in Regex.Matches(line,
      @"(((?<x>(?=[,\r\n] ))|""(?<x>([^""]|"""") )""|(?<x>[^,\r\n] )),?)",
      RegexOptions.ExplicitCapture)
              select m.Groups[1].Value).Where(i => (options == StringSplitOptions.None) || (options == StringSplitOptions.RemoveEmptyEntries && i != "")).Take(count).ToArray();
   }
}
It has four overloaded methods:
  • SplitCsv()
    All substrings are returned
  • SplitCsv(int count)
    To limit the maximum number of substrings to return
  • SplitCsv(StringSplitOptions options)
    Use StringSplitOptions.RemoveEmptyEntries to omit empty array elements from the array returned, or StringSplitOptions.None to include empty array elements in the array returned.
  • SplitCsv(int count, StringSplitOptions options)
Used like this:

string[] fields = line.SplitCsv();
string[] fields = line.SplitCsv(10);
string[] fields = line.SplitCsv(StringSplitOptions.RemoveEmptyEntries);
string[] fields = line.SplitCsv(10, StringSplitOptions.RemoveEmptyEntries);

Reading a CSV text file

It should be easy to read a CSV file as exported by Excel. But the quotes around any field that includes an embedded comma makes it trickier. I found this Regex nugget from an anonymous poster known only as 'h. brouwer'.
string[] SplitCsvLine(string line)
{
   return (from Match m in Regex.Matches(line,
   @"(((?<x>(?=[,\r\n] ))|""(?<x>([^""]|"""") )""|(?<x>[^,\r\n] )),?)",
   RegexOptions.ExplicitCapture)
           select m.Groups[1].Value).ToArray();
}

Reading a text file with TextReader

Why can't I remember the syntax for TextReader and TextWriter? They're simple enough. My only excuse is that I only use them about once a year. Or it might be because when I type
TextRead readFile = new
Intellisense abandons me.
TextReader readFile = new StreamReader(@"Book.csv");
string line;
while (true)
{
   line = readFile.ReadLine();
   if (line == null) break;
   // Process the line here
}
readFile.Close();
TextWriter writeFile = new StreamReader("Book.csv");
writeFile.WriteLine("abc");
writeFile.Close();

Wednesday, 8 February 2012

ItemsControl horizontal content layout

ItemsControl lays out its content vertically by default. To arrange it horizontally we set the Orientation of the StackPanel in the ItemsPanelTemplate. In XAML like this:
<ItemsControl>
   <ItemsControl.ItemsPanel>
      <ItemsPanelTemplate>
         <StackPanel Orientation="Horizontal"/>
      </ItemsPanelTemplate>
   </ItemsControl.ItemsPanel>
</ItemsControl>
And in C#, working from the deepest levels outward, like this:
FrameworkElementFactory factoryPanel =
   new FrameworkElementFactory(typeof(StackPanel));
factoryPanel.SetValue(
   StackPanel.OrientationProperty,
   Orientation.Horizontal);

ItemsPanelTemplate template = new ItemsPanelTemplate();
template.VisualTree = factoryPanel;

ItemsControl itemsControl = new ItemsControl();
itemsControl.ItemsPanel = template;

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, 1 February 2012

Document Header

Sandstorm project only: Document Header is a component of the framework and is bound to the DocumentHeader property of AbstractDocumentViewModel.
It takes a simple string but if the string is empty the DocumentHeader is Hidden.

Drawing a semi-circle using Ellipse and Clipping

I needed to draw a filled semi-circle and Clip seems to do the job nicely. The RectangleGeometry describes the part of the clipped image that you want to be able to see.
<Ellipse
   Width="20"
   Height="20"
   Canvas.Left="20"
   Canvas.Top="0"
   Stretch="Fill" 
   Stroke="Black" 
   Fill="Red">
   <Ellipse.Clip>
      <RectangleGeometry Rect="0,10,20,20"/>
   </Ellipse.Clip>
</Ellipse>
And here is a fuller example with all four semi-circle rotations:
<Canvas VerticalAlignment="Center">
   <Ellipse
      Width="20"
      Height="20"
      Canvas.Left="20"
      Canvas.Top="0"
      Stretch="Fill" 
      Stroke="Black" 
      Fill="Red">
      <Ellipse.Clip>
         <RectangleGeometry Rect="0,10,20,20"/>
      </Ellipse.Clip>
   </Ellipse>
   <Ellipse
      Width="20"
      Height="20"
      Canvas.Left="60"
      Canvas.Top="0"
      Stretch="Fill" 
      Stroke="Black" 
      Fill="Red">
      <Ellipse.Clip>
         <RectangleGeometry Rect="0,0,10,20"/>
      </Ellipse.Clip>
   </Ellipse>
   <Ellipse
      Width="20"
      Height="20"
      Canvas.Left="100"
      Canvas.Top="0"
      Stretch="Fill" 
      Stroke="Black" 
      Fill="Red">
      <Ellipse.Clip>
         <RectangleGeometry Rect="10,0,10,20"/>
      </Ellipse.Clip>
   </Ellipse>
   <Ellipse
      Width="20"
      Height="20"
      Canvas.Left="140"
      Canvas.Top="0"
      Stretch="Fill" 
      Stroke="Black" 
      Fill="Red">
      <Ellipse.Clip>
         <RectangleGeometry Rect="0,10,20,20"/>
      </Ellipse.Clip>
   </Ellipse>
</Canvas>
Which gives this result:

Tuesday, 31 January 2012

DataGridTextColumn Text Alignment (improved)

From Martin: I’ve been using this technique DataGridTextColumn Text Alignment, but I found it had 2 weird effects which I ran by Katharine yesterday.

1. If you clicked on the row just to the left of the value, it wasn’t registering and selecting the row
2. When the row was selected, the value was highlighted weirdly in a dark blue “selected” box

Apparently, that approach was overwriting the usual cell style template and making the textblock behave a little strangely.
With the ElementStyle change she suggested, the 2 issues above went away, so it seems to be the right way to do it.

Courtesy of Katharine, this is the correct way:
<DataGridTextColumn Header="Blah" Binding="{Binding Blah}">
   <DataGridTextColumn.ElementStyle>
      <Style TargetType="{x:Type TextBlock}">
         <Setter Property="TextAlignment" Value="Right" />
      </Style>
   </DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
Alternatively the style can be set up as StaticResource if it needs to be referenced by several DataGrid columns.

<Style x:Key="rightAlignedColumn" TargetType="{x:Type TextBlock}">
   <Setter Property="TextAlignment" Value="Right" />
</Style>

...

<DataGridTextColumn Header="Blah"
                    Binding="{Binding Blah}"
                    ElementStyle="{StaticResource rightAlignedColumn}">
</DataGridTextColumn>