Introduction
Well, I still hadn't found the perfect WPF ColorPicker Control to use in my projects. There are some nice ones out there, but my favourite is KAXAML's, so, I thought I might try to replicate it.
Background
The project is loosely MVVM and contains two UserControls, ColorEditor
and BrushEditor and are
packaged in a separate class library project for your convenience.
The first hurdle I had was trying to generate the ColorPicker swatch. WriteableBitmap was terribly slow, so I decided to cache the 100 Hue swatches (being 0 - 1 with increments of 1%). Generating the Bitmaps was made much easier (having never had an occasion to use them) with the SimpleBitmap class found in the Community Content section of WriteableBitmap (http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.writeablebitmap.aspx)
The next hurdle for me was retemplating the Sliders for Hue and Alpha, again, something I hadn't done before. I mucked about with a couple of ControlTemplates before taking KAXAML's own Simple Styles style and bending it to my will. The Hue Slider background is simply a Border with a LinearGradientBrush and 7 GradientStops.
The Alpha Slider and selected color box were somewhat tricky too, and I must profess to not fully understanding why the DrawingBrush that displays the checker pattern works.
However, I think the trickiest part of this, to my surprise, was actually the RGBA/HSLA TextBox's! Basically, the problem is this. If you update say, the R value, you must also update H, S and L. If you update H, you must update R, if you update S, you must update R. This mightn't be so bad except that the conversion code from RGB to HSL isn't perfect. Ie, HSL (0.56, .60, .23) might not align perfectly to an RGB value. So, it rounds the RGB value. But perhaps that rounding alters the H value just a tiny bit. So we alter the RGB values, which skew the H just a tiny bit. In the end, I simply said, if you change the H value, set a flag, update the RGB values, when they attempt to update the HSL values, check if that flag is set. If so, don't update. Simple in retrospect of course.
Using the code
The editors are used just like any other UserControl.
<Window
x:Class="BrushEditor.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ColorEditor="clr-namespace:Lovatts.ColorEditor;assembly=Lovatts.ColorEditor"
Title="MainWindow"
Height="350"
Width="525">
<ColorEditor:BrushEditor></ColorEditor:BrushEditor>
</Window>
But you can also bind to/use the underlying ViewModels:
ColorEditor colorEditor = new ColorEditor();
colorEditor.ColorEditorViewModel.Color = Colors.Blue;
colorEditor.ColorEditorViewModel.RGB.A = 67;
Lovatts.ColorEditor.BrushEditor brushEditor = new Lovatts.ColorEditor.BrushEditor();
brushEditor.BrushEditorViewModel.BrushType = BrushTypes.Radial;
brushEditor.BrushEditorViewModel.Center = new Point(1, 0);
brushEditor.BrushEditorViewModel.GradientStops.Clear();
brushEditor.BrushEditorViewModel.GradientStops.Add(new GradientStopViewModel(Colors.Blue, 1);
brushEditor.BrushEditorViewModel.GradientStops.Add(new GradientStopViewModel(Colors.Red, 0));
Brush serialization/deserialization methods are included for convenience.
string xml = brushEditor.BrushEditorViewModel.SerializeBrushToXml();
brushEditor.BrushEditorViewModel.DeserializeBrushFromXml(xml);
The XML output being...
<RadialGradientBrush
Center="1,0" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<RadialGradientBrush.GradientStops>
<GradientStop
Color="#FFFF0000"
Offset="0" />
<GradientStop
Color="#FF0000FF"
Offset="1" />
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
Points of Interest
Well.. this is my first ever CodeProject submission and took about a day to write.
There's a slight issue when changing the hue slider, in that it will sometimes snap the palette thumb to an RGB value.
The hex code TextBox is automatically set to OverType
(using the method below) which I find much more user friendly:
private void MakeHexTextBoxOverType()
{
PropertyInfo textEditorProperty = typeof (TextBox).GetProperty(
"TextEditor", BindingFlags.NonPublic | BindingFlags.Instance);
object textEditor = textEditorProperty.GetValue(hexTextBox, null);
PropertyInfo overtypeModeProperty = textEditor.GetType().GetProperty(
"_OvertypeMode", BindingFlags.NonPublic | BindingFlags.Instance);
overtypeModeProperty.SetValue(textEditor, true, null);
}
History
- 19/6/2012: Posted article.
- 20/6/2012: Uploaded source
- 26/6/2012: Updated Background