Contents
Yesterday, I posted an article about creating a WPF Graph, and when I post a new article, I always have a look at the new articles, including those awaiting public status. Yesterday, someone published a WPF color picker that looked really cool. The only problem was the code was not attached to that article and the article text gave you no clue as to how the damn thing worked. I joked to a fellow CodeProject author that I just may have to try and write a color picker for WPF, as this article sparked my interest a bit.
As luck would have it, I woke up today (Monday) and tried to get into work (I work in London and live 50 miles away), but was faced with the worst snow storm in the UK for some time. No trains/buses/taxis were on, so guess what I did? That's right, I wrote a color picker.
This article is it.
It is very simple actually. I should point out before I start that I did borrow an idea or two from other excellent sources from the www. So I would like to start by having a special thanks section.
Special Thanks Goes Out To
So how does it all work? Well, it is actually so simple you probably would think "Ha, is that it". Well yep, this next section explains all that is involved, which ain't that much. Simplicity is good.
There is a WPF Window
called ColorDialog
which holds the actual ColorPicker
control. Here is the XAML:
<Window x:Class="WPFColorPickerLib.ColorDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFColorPickerLib"
Icon="Images/ColorSwatchSquare.png"
Title="Pick Color" Height="370"
KeyDown="Window_KeyDown"
Width="520"
WindowStartupLocation="CenterOwner">
<local:ColorPicker x:Name="colorPicker"/>
</Window>
This ColorDialog
window is the one you can use from your own code. The ColorDialog
window exposes a single property which is really just the value of the currently selected color within the contained ColorPicker
control.
Here is the code-behind for the ColorDialog
window:
public partial class ColorDialog : Window
{
#region Ctor
public ColorDialog()
{
InitializeComponent();
}
#endregion
#region Public Properties
public Color SelectedColor
{
get { return colorPicker.SelectedColor; }
}
#endregion
#region Private Methods
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
this.Close();
}
}
private void btnOk_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
}
#endregion
}
And here is how you could use it from your own WPF code to set a Color
:
private void btnPickColor_Click(object sender, RoutedEventArgs e)
{
ColorDialog colorDialog = new ColorDialog();
colorDialog.Owner = this;
if ((bool)colorDialog.ShowDialog())
{
RectColorPicked.Fill = new SolidColorBrush(colorDialog.SelectedColor);
}
}
The actual ColorPicker
control works as follows:
There are a number of static swatch images:
that the user may pick from, using the image buttons provided at the top of the ColorPicker
control. The user is then able to use the mouse to move around the image, and also adjust the Alpha value using the slider. The SelectedColor
is worked out based on where the mouse is in relationship to the current static swatch image. Basically, a single pixel under the mouse is retrieved from the current static swatch image, and this is made into a 1*1 byte array, and then the values within this 1*1 byte array are used to create a Color
, taking into account the current Alpha slider value.
The following diagram may help to explain it a little better.
One of the nice things about this control is the Preview, which is simply using a WPF InkPresenter
control and showing some strokes on it. And the nice checker background is easily achieved in WPF using a DrawingBrush
.
In essence, that is how it works, so probably time for some code.
Here is the entire XAML for the ColorPicker
control:
<UserControl x:Class="WPFColorPickerLib.ColorPicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="340" Width="510">
<UserControl.Resources>
<DrawingBrush x:Key="CheckerboardBrush"
Stretch="None" TileMode="Tile"
AlignmentX="Left" AlignmentY="Top"
Viewport="0,0,10,10" ViewportUnits="Absolute">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="sc# 1,1,1">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,10,10" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="sc# 0.5,0.5,0.5">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,5,5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="sc# 0.5,0.5,0.5">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="5,5,5,5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</UserControl.Resources>
<Grid Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="35"/>
<RowDefinition Height="230"/>
<RowDefinition Height="70"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Height="35"
HorizontalAlignment="Stretch"
Orientation="Horizontal"
Background="Black">
<Label Content="Pick swatch type"
Foreground="White" FontWeight="Bold"
VerticalAlignment="Center"/>
<Image x:Name="ImgSqaure1"
Height="20" Width="20"
Source="Images/ColorSwatchSquare.png"
Margin="45,0,0,0"
ToolTip="Square swatch1"
MouseLeftButtonDown="Swatch_MouseLeftButtonDown"/>
<Image x:Name="ImgSqaure2"
Height="20" Width="20"
Source="Images/ColorSwatchSquare2.png" Margin="5,0,0,0"
ToolTip="Square swatch2"
MouseLeftButtonDown="Swatch_MouseLeftButtonDown"/>
<Image x:Name="ImgCircle1" Height="20" Width="20"
Source="Images/ColorSwatchCircle.png" Margin="5,0,0,0"
ToolTip="Circle swatch1"
MouseLeftButtonDown="Swatch_MouseLeftButtonDown"/>
</StackPanel>
<Grid Grid.Row="1" Height="230"
VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="170"/>
<ColumnDefinition Width="170"/>
<ColumnDefinition Width="170"/>
</Grid.ColumnDefinitions>
-->
<Grid Grid.Column="0"
Grid.Row="0" Margin="10,30,0,0" >
<Border BorderBrush="Black" BorderThickness="2"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Background="White"
Width="154" Height="154">
</Border>
<Image x:Name="ColorImage"
Width="150" Height="150"
HorizontalAlignment="Center"
VerticalAlignment="Top" Margin="2"
Source="Images/ColorSwatchSquare.png"/>
<Canvas x:Name="CanvImage"
Width="150" Height="150"
HorizontalAlignment="Center"
Background="Transparent"
VerticalAlignment="Top" Margin="2"
MouseDown="CanvImage_MouseDown"
MouseUp="CanvImage_MouseUp"
MouseMove="CanvImage_MouseMove">
<Ellipse x:Name="ellipsePixel" Width="10"
Height="10" Stroke="Black" Fill="White"
Canvas.Left="0" Canvas.Top="0"/>
</Canvas>
</Grid>
-->
<StackPanel Grid.Column="1" Orientation="Vertical" >
<Label Content="Preview" Margin="5,0,0,0"
HorizontalAlignment="Left"
Foreground="Black" FontWeight="Bold"
VerticalAlignment="Center"/>
<Border Margin="4,5,10,0" Width="154"
Height="154"
HorizontalAlignment="Left"
BorderBrush="Black" BorderThickness="2"
Background="{StaticResource CheckerboardBrush}">
<InkPresenter Name="previewPresenter"
Margin="0" Width="150" Height="150"
Strokes="AOcBAxdIEESAgYAERYQBGwIAJAFGhAEbAgAk
AQUBOBkgMgkA9P8CAekiOkUzCQD4nwIBWiA6RTgIAP4DAAAAg
H8RAACAPx8JEQAAAAAAAPA/CpYBNIfm3uajgcQgUUiUkjUelE
al0KkUBh0HichlM1mtJotZp9JodDl8jk8ZgcBiUOjUYl08
m0+l0+lFCjksjESAh+kg6auNwaEwSBQiEQyLRKTRiVSiUSSORy
LQ6JQSBIPFYnKZTL5fOZfMZXL4/H47DYLBYHFoJLIpEo9GgIP3OB5
PlxLCJiZmU1MISSi4SJiS74+D4+4o" />
</Border>
</StackPanel>
-->
<StackPanel Grid.Column="2" Orientation="Vertical" >
<StackPanel Orientation="Horizontal" Margin="0,30,0,0">
<Label Content="A" Margin="5,0,0,0"
HorizontalAlignment="Left"
Foreground="Black" FontWeight="Bold"
VerticalAlignment="Center"/>
<Border CornerRadius="5"
BorderBrush="Black" Background="LightGray"
BorderThickness="2"
Width="50" Height="30">
<TextBox x:Name="txtAlpha"
BorderThickness="0" Background="LightGray"
BorderBrush="Transparent"
Margin="5,1,5,1" IsReadOnly="True"/>
</Border>
<Border CornerRadius="5"
BorderBrush="Black" Background="LightGray"
BorderThickness="2" Margin="10,0,0,0"
Width="50" Height="30">
<TextBox x:Name="txtAlphaHex" BorderThickness="0"
Background="LightGray" BorderBrush="Transparent"
Margin="5,1,5,1" IsReadOnly="True"/>
</Border>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
<Label Content="R" Margin="5,0,0,0"
HorizontalAlignment="Left"
Foreground="Black" FontWeight="Bold"
VerticalAlignment="Center"/>
<Border CornerRadius="5" BorderBrush="Black"
Background="LightGray"
BorderThickness="2" Width="50"
Height="30">
<TextBox x:Name="txtRed"
BorderThickness="0" Background="LightGray"
BorderBrush="Transparent"
Margin="5,1,5,1" IsReadOnly="True"/>
</Border>
<Border CornerRadius="5"
BorderBrush="Black" Background="LightGray"
BorderThickness="2" Margin="10,0,0,0"
Width="50" Height="30">
<TextBox x:Name="txtRedHex"
BorderThickness="0" Background="LightGray"
BorderBrush="Transparent"
Margin="5,1,5,1" IsReadOnly="True"/>
</Border>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
<Label Content="G" Margin="5,0,0,0"
HorizontalAlignment="Left"
Foreground="Black" FontWeight="Bold"
VerticalAlignment="Center"/>
<Border CornerRadius="5"
BorderBrush="Black" Background="LightGray"
BorderThickness="2" Width="50" Height="30">
<TextBox x:Name="txtGreen" BorderThickness="0"
Background="LightGray" BorderBrush="Transparent"
Margin="5,1,5,1" IsReadOnly="True"/>
</Border>
<Border CornerRadius="5"
BorderBrush="Black" Background="LightGray"
BorderThickness="2" Margin="10,0,0,0"
Width="50" Height="30">
<TextBox x:Name="txtGreenHex" BorderThickness="0"
Background="LightGray" BorderBrush="Transparent"
Margin="5,1,5,1" IsReadOnly="True"/>
</Border>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
<Label Content="B" Margin="5,0,0,0"
HorizontalAlignment="Left"
Foreground="Black" FontWeight="Bold"
VerticalAlignment="Center"/>
<Border CornerRadius="5" BorderBrush="Black"
Background="LightGray"
BorderThickness="2" Width="50" Height="30">
<TextBox x:Name="txtBlue"
BorderThickness="0" Background="LightGray"
BorderBrush="Transparent"
Margin="5,1,5,1" IsReadOnly="True"/>
</Border>
<Border CornerRadius="5" BorderBrush="Black"
Background="LightGray"
BorderThickness="2" Margin="10,0,0,0"
Width="50" Height="30">
<TextBox x:Name="txtBlueHex" BorderThickness="0"
Background="LightGray" BorderBrush="Transparent"
Margin="5,1,5,1" IsReadOnly="True"/>
</Border>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
<Border CornerRadius="5" Margin="22,0,0,0"
Background="LightGray"
BorderBrush="Black" BorderThickness="2"
Width="112" Height="30">
<TextBox x:Name="txtAll"
BorderThickness="0" Background="LightGray"
BorderBrush="Transparent"
Margin="5,1,5,1" IsReadOnly="True"/>
</Border>
</StackPanel>
</StackPanel>
</Grid>
-->
<Border x:Name="AlphaBorder" Grid.Row="2" Grid.ColumnSpan="2"
BorderBrush="Black" Height="60"
BorderThickness="2" CornerRadius="5" Margin="10,5,10,5">
<Slider x:Name="AlphaSlider" Orientation="Horizontal"
Minimum="0" Maximum="255"
SmallChange="1" LargeChange="25"
VerticalAlignment="Center" Margin="5"
Value="255"
ValueChanged="AlphaSlider_ValueChanged"/>
</Border>
</Grid>
</UserControl>
And here is all the C# code-behind for the ColorPicker
control:
public partial class ColorPicker : UserControl
{
#region Data
private DrawingAttributes drawingAttributes = new DrawingAttributes();
private Color selectedColor = Colors.Transparent;
private Boolean IsMouseDown = false;
#endregion
#region Ctor
public ColorPicker()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(ColorPicker_Loaded);
}
#endregion
#region Public Properties
public Color SelectedColor
{
get { return selectedColor; }
private set
{
if (selectedColor != value)
{
selectedColor = value;
CreateAlphaLinearBrush();
UpdateTextBoxes();
UpdateInk();
}
}
}
#endregion
#region Private Methods
private void ColorPicker_Loaded(object sender, RoutedEventArgs e)
{
SelectedColor = Colors.Black;
}
private void CreateAlphaLinearBrush()
{
Color startColor = Color.FromArgb(
(byte)0,
SelectedColor.R,
SelectedColor.G,
SelectedColor.B);
Color endColor = Color.FromArgb(
(byte)255,
SelectedColor.R,
SelectedColor.G,
SelectedColor.B);
LinearGradientBrush alphaBrush =
new LinearGradientBrush(startColor, endColor,
new Point(0, 0), new Point(1, 0));
AlphaBorder.Background = alphaBrush;
}
private void Swatch_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Image img = (sender as Image);
ColorImage.Source = img.Source;
}
private void CanvImage_MouseMove(object sender, MouseEventArgs e)
{
if (!IsMouseDown)
return;
try
{
CroppedBitmap cb = new CroppedBitmap(ColorImage.Source as BitmapSource,
new Int32Rect((int)Mouse.GetPosition(CanvImage).X,
(int)Mouse.GetPosition(CanvImage).Y, 1, 1));
byte[] pixels = new byte[4];
try
{
cb.CopyPixels(pixels, 4, 0);
}
catch (Exception ex)
{
}
ellipsePixel.SetValue(Canvas.LeftProperty,
(double)(Mouse.GetPosition(CanvImage).X-5));
ellipsePixel.SetValue(Canvas.TopProperty,
(double)(Mouse.GetPosition(CanvImage).Y-5));
CanvImage.InvalidateVisual();
SelectedColor = Color.FromArgb((byte)AlphaSlider.Value,
pixels[2], pixels[1], pixels[0]);
}
catch (Exception exc)
{
}
}
private void UpdateTextBoxes()
{
txtAlpha.Text = SelectedColor.A.ToString();
txtAlphaHex.Text = SelectedColor.A.ToString("X");
txtRed.Text = SelectedColor.R.ToString();
txtRedHex.Text = SelectedColor.R.ToString("X");
txtGreen.Text = SelectedColor.G.ToString();
txtGreenHex.Text = SelectedColor.G.ToString("X");
txtBlue.Text = SelectedColor.B.ToString();
txtBlueHex.Text = SelectedColor.B.ToString("X");
txtAll.Text = String.Format("#{0}{1}{2}{3}",
txtAlphaHex.Text, txtRedHex.Text,
txtGreenHex.Text, txtBlueHex.Text);
}
private void UpdateInk()
{
drawingAttributes.Color = SelectedColor;
drawingAttributes.StylusTip = StylusTip.Ellipse;
drawingAttributes.Width = 5;
foreach (Stroke s in previewPresenter.Strokes)
{
s.DrawingAttributes = drawingAttributes;
}
}
private void AlphaSlider_ValueChanged(object sender,
RoutedPropertyChangedEventArgs<double> e)
{
SelectedColor =
Color.FromArgb(
(byte)AlphaSlider.Value,
SelectedColor.R,
SelectedColor.G,
SelectedColor.B);
}
private void CanvImage_MouseDown(object sender, MouseButtonEventArgs e)
{
IsMouseDown = true;
}
private void CanvImage_MouseUp(object sender, MouseButtonEventArgs e)
{
IsMouseDown = false;
}
#endregion
}
I would just like to ask, if you liked the article, please vote for it, and leave some comments, as it lets me know if the article was at the right level or not, and whether it contained what people need to know.
One of the readers of this article, a one Mark Treadwell, has taken this idea further and enhanced this dialog to allow the dialog to also show a selected Color
on ShowDialog()
, and also done various other enhancements. You can read more about this over at Mark's blog post at http://geekswithblogs.net/mtreadwell/archive/2010/01/03/137314.aspx, and here is a link to his source code: Marc's better ColorPicker.
A few people have asked for this, but I have been too busy, so thanks Marc.