Page 1 of 1

A simple Brush picker in WPF Creating a simple bruhs selecter in WPF using the ObjectDataProvider

#1 ragingben  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 175
  • View blog
  • Posts: 641
  • Joined: 07-October 08

Posted 08 February 2010 - 02:35 PM

Why???

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)


This post has been edited by ragingben: 09 February 2010 - 04:01 AM


Is This A Good Question/Topic? 0
  • +

Replies To: A simple Brush picker in WPF

#2 Guest_Eric*


Reputation:

Posted 27 March 2010 - 05:38 PM

ragingben, you really did a lot of work here. Thank you for your efforts.
A very good and clear article. This is exactly what I've been looking for.

Would you mind going a little bit further and explain how it could be
applied to a different kind of controls. For instance I would like to
make a colour picker that changes foreground in label 'lblTest'.
How would you go about it? I am not very good with binding theory so
it would help me a lot to understand it.

Cheers

Eric
Was This Post Helpful? 0

#3 ragingben  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 175
  • View blog
  • Posts: 641
  • Joined: 07-October 08

Posted 31 March 2010 - 02:31 AM

Glad it was of some use to you Eric!

The way I would go about doing what you want is to...

-Make a new WPF user control, called something like ColourPickerComboControl
-Add all the code to that control
-Add a property and a corresponding dependency property called something like SelectedBrush
-Setup a two way binding to the brush selected with the combo box to the SelectedBrush property
-Now add the ColourPickerComboControl to any control/window or whatever where you need it, and bind any brush to its SelectedBrush property.
-Job done!

This sounds long winded, but it is easier than it sounds.

If I get a chance I'll do a smaple later and post it here

Cheers, Ben
Was This Post Helpful? 0
  • +
  • -

#4 ragingben  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 175
  • View blog
  • Posts: 641
  • Joined: 07-October 08

Posted 31 March 2010 - 02:45 AM

Here you go Eric, give this a try, not much code, but enough to make it annying to post so I zipped it up...

Attached File(s)


Was This Post Helpful? 0
  • +
  • -

#5 Guest_Tom*


Reputation:

Posted 14 December 2010 - 03:39 PM

Great control. What I need is one that looks the same but allows the selection of color rather than brush. Is there an easy way to alter this to expose a SelectedColor and SelectedColorProperty? I am new to brushes and don't quite understand how to do this.
Was This Post Helpful? 0

#6 ragingben  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 175
  • View blog
  • Posts: 641
  • Joined: 07-October 08

Posted 13 January 2011 - 09:04 AM

Hi Tom - not sure if you will check back ever - but you could always select the brush, and then use the SolidColorBrush.Color property to obtain the color?
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1