Pages

Showing posts with label ComboBox. Show all posts
Showing posts with label ComboBox. Show all posts

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.

Tuesday, 14 July 2009

Parameter Ordering

Although the two ComboBox samples below look very similar they actually function a little differently.
The first sample sets the selected item based on the SelectedItem binding and then almost instantaneously sets the SelectedItem based on the first record in the ItemsSource binding.
<ComboBox
DisplayMemberPath="Description"
SelectedItem="{Binding Path=EditedCourse.Centre, Mode=TwoWay}"
SSForm:FormItem.LabelContent="Centre"
IsEditable="{Binding Path=EditedCourse.IsCentreEditable}">
<ComboBox.ItemsSource>
<Binding
Mode="OneWay">
<Binding.Source>
<CollectionViewSource
x:Name="PART_CentresView"
Source="{Binding Source={StaticResource ViewModel}, Path=Centres, Mode=OneWay}" />
</Binding.Source>
</Binding>
</ComboBox.ItemsSource>
</ComboBox>

The second sample sets the selected item based on the SelectedItem binding only and therefore does what I would have expected to happen from the first sample.
<ComboBox
DisplayMemberPath="Description"
SSForm:FormItem.LabelContent="Centre"
IsEditable="{Binding Path=EditedCourse.IsCentreEditable}">
<ComboBox.ItemsSource>
<Binding
Mode="OneWay">
<Binding.Source>
<CollectionViewSource
x:Name="PART_CentresView"
Source="{Binding Source={StaticResource ViewModel}, Path=Centres, Mode=OneWay}" />
</Binding.Source>
</Binding>
</ComboBox.ItemsSource>
<ComboBox.SelectedItem>
<Binding
Path="EditedCourse.Centre"
Mode="TwoWay" />
</ComboBox.SelectedItem>

</ComboBox>

The implication is that the selected item is not necessarily what you would expect it to be.

In the first sample, the final selected item is the first (default) entry in the item source collection, and in the second sample the final selected item is the item bound to in the SelectedItem property.

Sunday, 5 October 2008

WPF ComboBox Data Binding

Binding a enum property (PaymentFrequency) to a WPF ComboBox.
This property provides the ItemsSource for the combobox by building a dictionary of enum (key) and enum.ToString (value).
   public enum PaymentFrequency
   {
      DayOfPlay,
      Monthly,
      None,
      Quarterly,
      Weekly,
   }
Expose this as a Property and Dictionary for Binding purposes:
      public PaymentFrequency Frequency { get; set; }

      public IDictionary<PaymentFrequency, string> FrequencyList
      {
         get
         {
            IDictionary<PaymentFrequency, string> frequencyList = new Dictionary<PaymentFrequency, string>();
            foreach (PaymentFrequency pf in Enum.GetValues(typeof(PaymentFrequency)))
            {
               frequencyList.Add(pf, pf.ToString());
            }
            return frequencyList;
         }
      }
The corresponding xaml (not forgetting the UpdateSourceTrigger=PropertyChanged if the ComboBox is inside a DataGrid as discussed here):
      <ComboBox ItemsSource="{Binding Path=FrequencyList, Mode=OneTime}"
                SelectedValuePath="Key"
                DisplayMemberPath="Value"
                SelectedValue="{Binding Path=Frequency, UpdateSourceTrigger=PropertyChanged}">
      </ComboBox>
For Internationalisation purposes it would be better to use Resource Strings to provide the descriptions for the combobox. I used a prefix of "PF_" on all the strings to ensure they are unique:

PF_DayOfPlay Day of Play 
PF_Monthly Monthly 
PF_None  None 
PF_Quarterly Quarterly 
PF_Weekly Weekly
Then modify the FrequencyList getter:
      public PaymentFrequency Frequency { get; set; }

      public IDictionary<PaymentFrequency, string> FrequencyList
      {
         get
         {
            IDictionary<PaymentFrequency, string> frequencyList = new Dictionary<PaymentFrequency, string>();
            foreach (PaymentFrequency pf in Enum.GetValues(typeof(PaymentFrequency)))
            {
               frequencyList.Add(pf, ResourceStrings.ResourceManager.GetString("PF_" + pf.ToString()));
            }
            return frequencyList;
         }
      }
A final tweak is to sort the descriptions displayed in the combobox into alphabetic order based on the translated strings.
      public PaymentFrequency Frequency { get; set; }

      public IDictionary<PaymentFrequency, string> FrequencyList
      {
         get
         {
            IDictionary<PaymentFrequency, string> frequencyList = new Dictionary<PaymentFrequency, string>();
            foreach (PaymentFrequency pf in Enum.GetValues(typeof(PaymentFrequency)))
            {
               frequencyList.Add(pf, ResourceStrings.ResourceManager.GetString("PF_" + pf.ToString()));
            }
            return frequencyList.OrderBy(f => f.Value).ToDictionary(f => f.Key, f => f.Value);
         }
      }
The ComboboxDataBinding example project includes this code and can be downloaded from GoogleCode.