One day at work I had to build a combo box that would let the user select a background colour. Fair enough I thought, and went ahead and built a basic 5 colour combo, manually putting in the items. Job done. Next day I had another look, and felt that although it worked, it wasn't really that great, and hey if I can use the Brushes enumeration surely I can easily build a combo box with all the colours right? Except... Brushes isn't an enumeration! Wow I had never noticed that before! It is just a class with a ton of static properties, each one being a differnt brush! I'm sure most of you are super sharp and already knew that but it took me by suprise!
So then I got thinking, how could I take these static properties, and basically cram them into a combo box, using as little code as possible? I already knew that I would need the ObjectDataProvider to get what I thought I would need had Brushes been an enumeration, but had no idea how I could use this to get static properties of a class. And how could I cram this into the combo box in a way that would be visually pleasing, using as little C# as possible?
Here's what happened...
Firstly I started a new WPF app project, I called mine ColourComboBoxExample. Next I created my combo box in XAML, and figured that I would need an item template to display the colours - after all any user would want to see the colour they are picking, and since each Brush is already named, then we may as well show that too. So heres the code I came up with for the combo box...
<ComboBox Name="brushSelectionBox" IsEditable="False">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Border Height="25" Width="25" Margin="0,3,0,3" BorderThickness="1" BorderBrush="Black"/>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Margin="10,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Starting at the top, I set the "IsEditable" property of the combo box to false, as we want it locked so that people don't start typing in it. Moving on to the acutal item template, I opted for a simple stack panel, which contains a border to show off the selected colour, and a text block that will display the colours name.
Next, it was important to decide just what data we need to select the brush. The actual data we need is the properties that the Brushes class contains, as these are the bruhses themselves. In C# we could do this to list the parmeters...
// itterate all properties of the bruhses type
foreach (PropertyInfo propertyInfo in typeof(Brushes).GetProperties())
{
// display name of info
Console.WriteLine(propertyInfo.Name);
}
Or we could get they type of the the Bruhses class via the Type.GetType(String type) method which is closer to the equivelent of what we would do in XAML...
// get type of bruhses
Type bruhsesType = Type.GetType("System.Windows.Media.Brushes, PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
// itterate all properties of the bruhses type
foreach (PropertyInfo propertyInfo in bruhsesType.GetProperties())
{
// display name of info
Console.WriteLine(propertyInfo.Name);
}
Next I figured I better work out an ObjectDataProvider to get the Brushes names from the GetProperties() method of Type. To do this we need to add a reference to the namespace containing the Type and Sting classes as the object data provider will require these...
xmlns:sys="clr-namespace:System;assembly=mscorlib"
We can use an ObjectDataProvider to get the PropertyInfo[] in XAML. This should be in the window.Resources section of the XAML, should have a meaningful key and We need to set its ObjectType property to type, and its MethodName property to GetType. This will provide use with the Brushes type...
<window.Resources>
<ObjectDataProvider x:Key="bruhsesTypeDataProvider" ObjectType="{x:Type sys:Type}" MethodName="GetType">
</ObjectDataProvider>
</window.Resources>
Now we want to add the method argument (which is for the Type.GetType(String) method) within the MethodArgument tag of ObjectDataProvider, which is the reference to system.Windows.Media.Brushes...
<ObjectDataProvider x:Key="bruhsesTypeDataProvider" ObjectType="{x:Type sys:Type}" MethodName="GetType">
<ObjectDataProvider.MethodParameters>
<sys:String>
System.Windows.Media.Brushes, PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
</sys:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
Now we have a reference to the Brushes type we can access the GetProperties method, and thus get our bruhses as a PropertyInfo[]! To do this add another ObjectDataProvider, this one setting the ObjectInstance to the other ObjectDataProvider, and the MethodName to the GetProperties method. This should also be added to window.Resources...
<ObjectDataProvider x:Key="brushNameDataProvider" ObjectInstance="{StaticResource bruhsesTypeDataProvider}" MethodName="GetProperties"/>
Wow we should now have an instance of PropertyInfo for each brush in Bruhses! But how do we use this to get our combo box to spit out a Brush, and accept one? We need a binding converter! If you don't know about Binding converters and IValueConverter then google them! Heres the converter I wrote, you can add the class below anywhere within your project...
A PropertyInfo to Brush converter...
/// <summary>
/// Converts between PropertyInfo and Brushes by name
/// </summary>
public class PropertyInfoToBrushConverter : IValueConverter
{
#region IValueConverter Members
object IValueConverter.Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
// if we have a brush
if (value is Brush)
{
// get type
Type brushType = (typeof(System.Windows.Media.Brushes));
// Get the public properties.
PropertyInfo[] brushPropertyInfo = brushType.GetProperties();
// hold value bruhs
SolidColorBrush valueBrush = value as SolidColorBrush;
// create converter
BrushConverter brushConverter = new BrushConverter();
// create brush
SolidColorBrush propertyBrush;
// itterate all property infos
foreach (PropertyInfo info in brushPropertyInfo)
{
// set item from colour
propertyBrush = (SolidColorBrush)brushConverter.ConvertFromString(info.Name);
// compare
if (propertyBrush.Color == valueBrush.Color)
{
// we have found our info! PHEW!
return info;
}
}
// just return null
return null;
}
else
{
// just return null
return null;
}
}
catch (Exception e)
{
// display
Console.WriteLine("Exception caught converting brush: {0}", e.Message);
// return null
return null;
}
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
// get brush from runtime property info
if (value is PropertyInfo)
{
// create converter
BrushConverter converter = new BrushConverter();
// convert neme to brush
Brush selectedBrush = (Brush)converter.ConvertFromString(((PropertyInfo)value).Name.ToString());
// get brush
return selectedBrush;
}
else
{
// return black
return Brushes.Black;
}
}
catch (Exception e)
{
// display
Console.WriteLine("Exception caught converting property info: {0}", e.Message);
// return black
return Brushes.Black;
}
}
#endregion
}
And then refernece the namespace the converter is part of in the XAML (in my case I just wrote it underneath my Window1 class...
xmlns:converterNamespace="clr-namespace:ColourComboBoxExample"
And create an instance as a resource in the window.Resources section...
<converterNamespace:PropertyInfoToBrushConverter x:Key="propertyInfoToBrushConverter"/>
Now we are ready to bind the ItemSource property of the combobox to the brushNameDataProvider's data...
ItemsSource="{Binding Source={StaticResource brushNameDataProvider}}"
And lastly bind the colour of our border and the description text block of the combo box's item template to be instance of the Brush that the item will represent. To do this we use the Name property. The completed combo box should look like this...
<ComboBox Name="brushSelectionBox" IsReadOnly="True" IsEditable="False" ItemsSource="{Binding Source={StaticResource brushNameDataProvider}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Border Height="25" Width="25" Margin="0,3,0,3" BorderThickness="1" BorderBrush="Black" Background="{Binding Path=Name, Mode=OneWay}"/>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Margin="10,0,0,0" Text="{Binding Path=Name, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Complete XAML...
Crikey! All done, and nearly time to try it out! First though, notice that both the bindings should have their Mode property set as OneWay. To test the combo box I'm going to bind it to the windows Background property, and also put the combo box in a grid. The finished XAML for the window should look something like this...
<Window x:Class="ColourComboBoxExample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:converterNamespace="clr-namespace:ColourComboBoxExample"
Title="Window1" Height="300" Width="300">
<window.Resources>
<ObjectDataProvider x:Key="bruhsesTypeDataProvider" ObjectType="{x:Type sys:Type}" MethodName="GetType">
<ObjectDataProvider.MethodParameters>
<sys:String>
System.Windows.Media.Brushes, PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
</sys:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<ObjectDataProvider x:Key="brushNameDataProvider" ObjectInstance="{StaticResource bruhsesTypeDataProvider}" MethodName="GetProperties"/>
<converterNamespace:PropertyInfoToBrushConverter x:Key="propertyInfoToBrushConverter"/>
</window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ComboBox Name="brushSelectionBox" IsReadOnly="True" IsEditable="False" ItemsSource="{Binding Source={StaticResource brushNameDataProvider}}" SelectedItem="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=Background, Mode=TwoWay, Converter={StaticResource propertyInfoToBrushConverter}}" Grid.Row="1">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Border Height="25" Width="25" Margin="0,3,0,3" BorderThickness="1" BorderBrush="Black" Background="{Binding Path=Name, Mode=OneWay}"/>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Margin="10,0,0,0" Text="{Binding Path=Name, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Window>
Notice that I have bound the SelectedItem property of the combo box to find it's ancestor window, and the actual binding is TwoWay and points to it's background. This is only relavent for this example. This is where you would bind whatever Brush property that you want the combo box to represent...
Press F5 and voila!
In hindsight...
This is probally not the best way of doing this, but as far as I'm concerned it works, and looks relatively pretty! This would be ultra easy to put into a user control that you could use whenever you would need a simple brush picker. However it is not overly intuitive as the colours are listed alphabetically...
Attached File(s)
-
Result.bmp (285.54K)
Number of downloads: 2 -
ColourComboBoxExample.zip (55.13K)
Number of downloads: 2
This post has been edited by ragingben: 09 February 2010 - 04:01 AM




MultiQuote



|