Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF

Making a Drop Down Style Custom Color Picker in WPF

Rate me:
Please Sign up or sign in to vote.
4.90/5 (32 votes)
9 Oct 2009CPOL11 min read 98.2K   2.9K   41   15
Description of how I came up with making a drop down style custom color picker in WPF.

Introduction

Although WPF provides quite a rich collection of UI controls and components for development, a particular control has been missing. That is a color-picker control. A drop down style custom color picker has been required for one of the applications I was developing. For that application, a drop down style custom color picker has been made using WPF. In this article, I have described how I came up with implementing a drop down style custom color picker. The features of this color picker are the following:

  1. You can select colors from a default color list.
  2. You can make color from a color image.
  3. You can make color from an RGB value.
  4. You can make color from a Hex value.

For the drop down style of the color picker, I have used a button with its context menu. To behave like a drop down, on the click event of the button, the context menu is opened, and on a click outside of the button or on a second click of the button, the context menu is closed. This context menu contains the color picker. The color picker has two parts: default colors, and custom colors. In the default colors part, you can select a color from a predefined color palette. In the custom color part, you can make a color from an ARGB value, a hexadecimal value, or from a pixel of an image. Moreover, the custom color picker synchronizes making colors in different ways.

Implementation of the default colors part of this color picker

default_.png

To make a color palette for the default colors part, a set of colors is needed. As we know, the Colors class holds a set of predefined colors as its properties. Here, all the colors are taken from the properties of the Colors class. As the Colors class does not provide any enumerable collection or array for colors, it exposes colors as properties. Therefore, to retrieve color names from the properties of the Colors class, Reflection has been used. The typeof operator is applied to the Colors class to get its type object. Then, using its type object, the properties metadata is retrieved using the GetProperties() method. Then, an iteration is made over the properties collection and we convert the property name to color using the ColorConverter class. For this, the following code has been used:

C#
Type ColorsType = typeof(Colors);
PropertyInfo[] ColorsProperty = ColorsType.GetProperties();
foreach (PropertyInfo property in ColorsProperty)
{
   _SelectableColors.Add((Color)ColorConverter.ConvertFromString(property.Name));
}

I have used a value converter named ColorToSolidColorBrushConverter to transform the color to brush. The converter code is shown here:

C#
[ValueConversion(typeof(Color), typeof(Brush))]
public class ColorToSolidColorBrushConverter : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, 
           object parameter, System.Globalization.CultureInfo culture)
    {
        return new SolidColorBrush((Color)value);
    }

    public object ConvertBack(object value, Type targetType, 
           object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion
}

To make color boxes for each color, a data template consisting of a Rectangle where the Fill property is bound with a set of colors has been used, and the ColorToSolidColorBrushConverter is used to convert these colors to the corresponding brush. The data template is shown here:

XML
<datatemplate>
   <rectangle width="15" height="15" stroke="Black" 
        horizontalalignment="Center" 
        fill="{Binding Converter={StaticResourceFromColorToSolidColorBrushConverter}}" 
        margin="0 1 0 1"/>    
</dataTemplate >

To hold color boxes, a Listbox has been used where the items panel template consists of a wrap panel to wrap the color boxes. Here, a custom event named ColorChanged has been exposed to notify when the user changes color. If the user clicks any item of the Listbox (Rectangle), the SelectionChanged event is fired, and in the event handler, the selected color is changed and the context menu is closed. In the context menu close event handler, the event ColorChanged is fired with the selected color as the parameter.

Implementation of the custom colors portion of this color picker

custom.jpg

The custom color portion of this color picker supports the following three features: making color from an ARGB value, making color from a hexadecimal value, and making color from the pixel of an image under the mouse position. The custom color portion also synchronizes among the ARGB UI, the hexadecimal UI, and the image UI. That is, if the user makes a color using an ARGB value, this picker will update the UI for the hex value and the selected pixel.

Feature 1: making color from ARGB value

To make color form ARGB, the color picker has to take ARGV values from user. For that reason, 4 text boxes are incorporated in the UI. The names of the textboxes are txtA, txtR, txtG, txtB. The color is made on lost focus of the textbox and on pressing enter on a textbox. For that reson , event handler is set to Lostfocus event of each textbox to change color on focus change and another event handler is set to Keydown event of each textbox to change color on pressing enter.

C#
txtAlpha.LostFocus += new RoutedEventHandler(txtAlpha_TextChanged);
txtR.LostFocus += new RoutedEventHandler(txtR_TextChanged);
txtG.LostFocus += new RoutedEventHandler(txtG_TextChanged);
txtB.LostFocus += new RoutedEventHandler(txtB_TextChanged);
txtAll.LostFocus += new RoutedEventHandler(txtAll_TextChanged);
txtAlpha.KeyDown += new KeyEventHandler(txtAlpha_KeyDown);
txtR.KeyDown += new KeyEventHandler(txtR_KeyDown);
txtG.KeyDown += new KeyEventHandler(txtG_KeyDown);
txtB.KeyDown += new KeyEventHandler(txtB_KeyDown);
txtAll.KeyDown += new KeyEventHandler(txtAll_KeyDown);

On the lost focus event handler, the color is made using the method FromArgb of the Color class. This method takes four parameters: alpha value, red value, green value, and blues value. The type of each parameter is byte. For example, on the lost focus handler of txtr (Red), at first, a check is made to determine whether the value of txtr is greater than 255 or not. If the value is greater than 255, the value is set to 255, and this value is converted to byte. Then, using the method Color.FromArgb, the color is made. The code for this is shown here:

C#
void txtR_TextChanged(object sender, RoutedEventArgs e)
{
    try
    {
        if (string.IsNullOrEmpty(((TextBox)sender).Text)) return;
        int val = Convert.ToInt32(((TextBox)sender).Text);
        if (val > 255)
            ((TextBox)sender).Text = "255";
        else
        {
            byte byteValue = Convert.ToByte(((TextBox)sender).Text);
            CustomColor = Color.FromArgb(  _customColor.A, byteValue,
            CustomColor.G,CustomColor.B);                             
            Reposition();
        }
    }
    catch
    {
    }
}

On the key down event handler, the color is also made using the method FromArgb of the Color class. In this event handler, first a check is made to test whether the pressed key is numeric or not and whether the pressed key is Enter or not. If the user presses a non-numeric key, it will ignore it. If the user presses a numeric key, it will be added in the textbox. If the user presses the Enter key, the method MakeColorFromRGB is called, which makes the color from the ARGB textbox values. After making the color, the method Reposition() is called, which synchronizes the UI among the selected pixel, hexadecimal value, and the ARGB value. Details of this method will be elaborated in the next section. To test whether the key is numeric or not, the Int32.Parse method is used. If it throws an exception, then the key is not numeric; otherwise numeric. For this, the following code is used.

C#
void txtR_KeyDown(object sender, KeyEventArgs e)
{
    NumericValidation(e);
    if (e.Key == Key.Tab)
    {
        txtG.Focus();
    }
}

private void NumericValidation(System.Windows.Input.KeyEventArgs e)
{
    string input = e.Key.ToString().Substring(1);
    try
    {
        if (e.Key == Key.Enter)
        {
            CustomColor = MakeColorFromRGB();
            Reposition();
        }
        int inputDigit = Int32.Parse(input);
    }
    catch  
    {
        e.Handled = true; 
    }
}

private Color MakeColorFromRGB()
{
    byte abyteValue = Convert.ToByte(txtAlpha.Text);
    byte rbyteValue = Convert.ToByte(txtR.Text);
    byte gbyteValue = Convert.ToByte(txtG.Text);
    byte bbyteValue = Convert.ToByte(txtB.Text);
    Color rgbColor =
         Color.FromArgb(
             abyteValue,
             rbyteValue,
             gbyteValue,
             bbyteValue);
    return rgbColor;
}

Feature 2: making color from a Hex value

To make a color from a hexadecimal value, the color picker has to take a hexadecimal value from the user. For that reason, a text box is incorporated in the UI. The name of the textbox is txtAll. The color is made on the lost focus event of the textbox and on pressing Enter on a textbox. For that reason, an event handler is set to the Lostfocus event of txtAll to change color on focus change, and another event handler is set to the KeyDown event of txtAll to change the color on pressing Enter.

C#
txtAll.LostFocus += new RoutedEventHandler(txtAll_TextChanged);
txtAll.KeyDown += new KeyEventHandler(txtAll_KeyDown);

On the lost focus event handler, the color is made using the method FromArgb of the Color class. This method takes four parameters: alpha value, red value, green value, and blues value. The type of each parameter is byte. On the lost focus handler of txtAll, a check is made to test whether the value of txtAll is empty/null or not. If its value is null or empty, the function returns. Otherwise, the method MakeColorFromHex is called to make the color from the hexadecimal value. In the MakeColorFromHex method, the method named ConvertFrom of the class named ColorConverter is used to convert the hexadecimal value to color. If the text of txtAll is not a proper hexadecimal string, then it will throw an exception, and in the catch block, the previous value is reset. The code for this is shown here:

C#
void txtAll_TextChanged(object sender, RoutedEventArgs e)
{
    try
    {
        if (string.IsNullOrEmpty(((TextBox)sender).Text)) return;
        CustomColor = MakeColorFromHex(sender);
        Reposition();
    }
    catch
    {}
}

private Color MakeColorFromHex(object sender)
{
    try
    {
        ColorConverter cc = new ColorConverter();
        return (Color)cc.ConvertFrom(((TextBox)sender).Text);

    }
    catch
    {
        string alphaHex = CustomColor.A.ToString("X").PadLeft(2, '0');
        string redHex = CustomColor.R.ToString("X").PadLeft(2, '0');
        string greenHex = CustomColor.G.ToString("X").PadLeft(2, '0');
        string blueHex = CustomColor.B.ToString("X").PadLeft(2, '0');
        txtAll.Text = String.Format("#{0}{1}{2}{3}",
        alphaHex, redHex,
        greenHex, blueHex);
    }
    return _customColor;
}

Feature 3: making color from a color image

The implementation of this feature is based on copying a single pixel from a bitmap and retrieving the color information from that pixel. To implement this, a 1*1 pixel selected by the user from the image has to be cropped. There is a class named CroppedBitmap in the namespace System.Windows.Media.Imaging, which is used for image cropping. This class takes a bitmap source and a rect (which specifies what portion you would like to crop) in the constructor. This class has a method Copypixel, which is used to convert the cropped 1*1 pixel bitmap to 4 (ARGB) byte color information. Then, the color is made from this color information using the method FromArgb of the Color class. The code for this is shown here:

C#
private Color GetColorFromImage(int i, int j)
{
    CroppedBitmap cb = new CroppedBitmap(image.Source as BitmapSource,
        new Int32Rect(i,
            j, 1, 1));
    byte[] color = new byte[4];
    cb.CopyPixels(color, 4, 0);
    Color Colorfromimagepoint = 
      Color.FromArgb((byte)SdA.Value, color[2], color[1], color[0]);
    return Colorfromimagepoint;
}

To implement this theme on the color picker, the following image is placed in the UI. This image can be changed.

image.png

The image is wrapped using a Canvas and the user can select a pixel of image using a mouse click. Moreover, an ellipse is provided to indicate the selected pixel. After selecting a pixel using the mouse from the above image, the above method named GetColorFromImage is called to get the color from this image. To make the color from the pixel of the image under the mouse position, the event MouseLeftButtonDown of the wrap canvas is handled by an event handler. On that event handler, the ChangeColor method is called. In the ChangeColor method, the Mouse.GetPosition() method is used to retrieve the x, y position of the mouse and the GetColorFromImage method is called using this x, y value. Then, the Movepointer method is called to move the ellipse to indicate the selected pixel.

C#
private void ChangeColor()
{
    try
    {
        CustomColor = GetColorFromImage((int)Mouse.GetPosition(CanColor).X,
       (int)Mouse.GetPosition(CanColor).Y);
        MovePointer();
    }
    catch 
    {}
}

Synchronizing among ARGB value, hexadecimal value, and the selected pixel of the image

There are three ways in the custom portion of this color picker by which the user can make a color. When the user is making a color using one of these ways, the color picker has to update the other corresponding UIs to make the UIs consistent. When the user is making colors using any of the approaches, the CustomColor property is updated.

C#
public Color CustomColor
{
    get { return _customColor; }
    set
    {
        if (_customColor != value)
        {
            _customColor = value;
            UpdatePreview();
        }
    }
}

On setting the CustomColor property, the method UpdatePreview() is called to update the preview, the ARGB textboxes, the hexadecimal value text box, and the slider value (represents alpha). In the UpdatePreview method, at first, a SolidColorBrush is created from CustomColor to set the background of the Label (used for preview). Then, the alpha, red, green, and blue values are extracted from the CustomColor and the corresponding ARGB textboxes are updated. Then, we create the hex value from the alpha, red, green, and blue values, which are extracted from the CustomColor property. This hex value is then set to the hex textbox (txtall). For this, the following code is used:

C#
private void UpdatePreview()
{
    lblPreview.Background = new SolidColorBrush(CustomColor);
    txtAlpha.Text = CustomColor.A.ToString();
    string alphaHex = CustomColor.A.ToString("X").PadLeft(2, '0');
    txtR.Text = CustomColor.R.ToString();
    string redHex = CustomColor.R.ToString("X").PadLeft(2, '0');
    txtG.Text = CustomColor.G.ToString();
    string greenHex = CustomColor.G.ToString("X").PadLeft(2, '0');
    txtB.Text = CustomColor.B.ToString();
    string blueHex = CustomColor.B.ToString("X").PadLeft(2, '0');
    txtAll.Text = String.Format("#{0}{1}{2}{3}",
    alphaHex, redHex,
    greenHex, blueHex);
    SdA.Value = CustomColor.A;
}

When the user makes a color using an ARGB or HEX value, the color picker has to reposition the selected color pixel to keep the color picker's UI consistent. To update the selected pixel of an image according to the current custom color, the Reposition() method is used. As we know, an image is a two-dimensional array (matrix), where each cell contains a different color value. In the Reposition() method, two for loops are used to explore the color of each cell and to test whether the color of that cell matches with the currently selected color. If a match is found, the ellipse is moved to that cell position to indicate the currently selected pixel. An exact match between the pixel color and the currently selected color is always not found, as the image does not contain all the colors. So, a similar color is selected using the SimmilarColor method.

C#
private void Reposition()
{
    for (int i = 0; i < CanColor.ActualWidth; i++)
    {
        bool flag = false;
        for (int j = 0; j < CanColor.ActualHeight; j++)
        {
            try
            {
                Color Colorfromimagepoint = GetColorFromImage(i, j);
                if (SimmilarColor(Colorfromimagepoint, _customColor))
                {
                    MovePointerDuringReposition(i, j);
                    flag = true;
                    break;
                }
            }
            catch 
            { }
        }
        if (flag) break;
    }
}

private bool SimmilarColor(Color pointColor, Color selectedColor)
{
    int diff = Math.Abs(pointColor.R - selectedColor.R) + Math.Abs(pointColor.G -  
    selectedColor.G) + Math.Abs(pointColor.B - selectedColor.B);
    if (diff < 20) return true;
    else
        return false;
}

In the SimmilarColor method, the red, green, and blue values of the pixel color are subtracted from the corresponding values of the currently selected color. Then, the subtraction results are summed, and if summed result is less than 20, the method returns true, which indicates a match was found.

Using the code

Here, the color picker project is provided as a class library. To use the color picker, add the color picker class library project as a reference in your project and write the following XAML code to include the color picker:

XML
xmlns:cp="clr-namespace:DropDownCustomColorPicker;assembly=CustomColorPicker"
<customcolorpicker width="40" x:name="customCP"></customcolorpicker>

Conclusion

Thanks for reading this write up. I hope that this article will be helpful for some people. If you guys have any questions, I will love to answer them. I always appreciate comments.

References

  • Sacha Barber: He has a great article on making a simple WPF color picker: A Simple Color Picker With Preview, which he made on a single day. He has written a large number of articles. If you are interested in WPF, I suggest reading his articles and keeping eyes on his blog. I always try to manage some time to read his articles. Thanks Sacha for contributing so much to the community.
  • Lee Brimelow: For his ideas of having an image swatch image, and copying the pixel under the mouse.

History

  • Initial release – 09/10/09.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) CP
Australia Australia
I am an Independent Contractor in Brisbane, Australia. For me, programming is a passion first, a hobby second, and a career third.

My Blog: http://weblogs.asp.net/razan/






Comments and Discussions

 
BugMy vote of 1 Pin
bspiljak17-Mar-13 11:16
bspiljak17-Mar-13 11:16 
AnswerRe: My vote of 1 Pin
bspiljak17-Mar-13 11:51
bspiljak17-Mar-13 11:51 
QuestionInitial Color Pin
Tremal125-Apr-12 8:16
Tremal125-Apr-12 8:16 
AnswerRe: Initial Color Pin
Tremal14-May-12 4:39
Tremal14-May-12 4:39 
QuestionOrange Border MouseOver Effect Pin
Jibrohni23-Apr-12 2:31
Jibrohni23-Apr-12 2:31 
Questionvery nice Pin
BillW3313-Dec-11 11:20
professionalBillW3313-Dec-11 11:20 
QuestionDefault Colour Selection Pin
Jibrohni16-Nov-11 23:03
Jibrohni16-Nov-11 23:03 
Generalxaml serialization Pin
tmtt1-Oct-10 7:02
tmtt1-Oct-10 7:02 
GeneralExcellent!! Pin
groovecontrol26-Oct-09 0:01
groovecontrol26-Oct-09 0:01 
GeneralRe: Excellent!! Pin
Razan Paul (Raju)26-Oct-09 3:39
Razan Paul (Raju)26-Oct-09 3:39 
GeneralCool! Pin
Wes Aday13-Oct-09 8:45
professionalWes Aday13-Oct-09 8:45 
GeneralRe: Cool! Pin
Razan Paul (Raju)13-Oct-09 17:59
Razan Paul (Raju)13-Oct-09 17:59 
GeneralI did one of these ages ago, it was a ColorPickerDialog though Pin
Sacha Barber12-Oct-09 15:40
Sacha Barber12-Oct-09 15:40 
GeneralRe: I did one of these ages ago, it was a ColorPickerDialog though Pin
poolboy14-Oct-09 9:45
poolboy14-Oct-09 9:45 
GeneralRe: I did one of these ages ago, it was a ColorPickerDialog though Pin
Sacha Barber15-Oct-09 2:01
Sacha Barber15-Oct-09 2:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.