This is a multi-part series on building a Silverlight Color Picker Control, please check out the Introduction if you missed it!
Before we write any actual code, it's nice to have some visuals to use as a reference. There are many ways to represent color spaces. One of the most common color space representations allows the user to pick a hue and then pick a saturation/luminosity combination. The diagram below is a simplified view of this layout. From this diagram, we can infer the XAML primitives that will be necessary to present our control.
Hue Selector
The Hue Selector is really a gradient that traverses each color extreme. Do you remember your primary colors?
| Red |
Yellow |
Green |
Blue |
Cyan |
Magenta |
Red (Again) |
| #ff0000 |
#ffff00 |
#00ff00 |
#0000ff |
#00ffff |
#ff00ff |
#ff0000 |
| 0 |
0.17 |
0.33 |
0.50 |
0.66 |
0.83 |
1 |
As it turns out, this is easy to represent in XAML. Simply create a <Rectangle> primitive and use the the Linear Gradient to fill the rectangle.
1: <Rectangle Canvas.Left="0" Canvas.Top="0" Width="20" Height="180" >
2: <Rectangle.Fill>
3: <LinearGradientBrush StartPoint ="0,0" EndPoint="0,1">
4: <GradientStop Offset="0.00" Color="#ffff0000"/>
5: <GradientStop Offset="0.17" Color="#ffffff00"/>
6: <GradientStop Offset="0.33" Color="#ff00ff00"/>
7: <GradientStop Offset="0.50" Color="#ff00ffff"/>
8: <GradientStop Offset="0.66" Color="#ff0000ff"/>
9: <GradientStop Offset="0.83" Color="#ffff00ff"/>
10: <GradientStop Offset="1.00" Color="#ffff0000"/>
11: </LinearGradientBrush>
12: </Rectangle.Fill>
13: </Rectangle>
Saturation / Brightness Selector
The Saturation / Brightness Selector is really two orthogonal gradients on top of the selected hue. The saturation gradient starts out as White an becomes 100% transparent as you move from left to right. The brightness gradient starts out as black and becomes 100% transparent as you move from bottom to top. Creating these gradients and stacking them produces the desired effect.
1: <Rectangle x:Name="rectSample" Width="180" Height="180"></Rectangle>
2: <Rectangle x:Name="rectWhiteGradient" Width="180" Height="180">
3: <Rectangle.Fill>
4: <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
5: <GradientStop Offset="0" Color="#ffffffff"/>
6: <GradientStop Offset="1" Color="#00ffffff"/>
7: </LinearGradientBrush>
8: </Rectangle.Fill>
9: </Rectangle>
10: <Rectangle x:Name="rectBlackGradient" Width="180" Height="180">
11: <Rectangle.Fill>
12: <LinearGradientBrush StartPoint="0,1" EndPoint="0, 0">
13: <GradientStop Offset="0" Color="#ff000000"/>
14: <GradientStop Offset="1" Color="#00000000"/>
15: </LinearGradientBrush>
16: </Rectangle.Fill>
17: </Rectangle>
Hex Code and Selected Color
These are the easiest of all. Simply create two rectangles and position them.
1: <Canvas Canvas.Top="180" Canvas.Left="0">
2: <Rectangle x:Name="SelectedColor" Width="200" Height="20" Fill="Black" />
3: <Rectangle Width="60" Height="20" Fill="Black" />
4: <TextBlock x:Name="HexValue" Foreground="White" Width="100" Text="#FF0000"
FontFamily="Arial" FontSize="11" Canvas.Top="4" Canvas.Left="5" Height="10" />
5: </Canvas>
Cursors
There are two cursors that I've left of the diagram above. One cursor represents the selected hue and the other represents the selected saturation/brightness combination. I used Expression Design to create the hue selector cursor since it is created with a polygon. The saturation/brightness cursor could be built without any additional tool support.
1: <Canvas x:Name="SampleSelector" Width="10" Height="10" Canvas.Left="100" Canvas.Top="96">
2: <Ellipse Width="10" Height="10" StrokeThickness="3" Stroke="#FFFFFFFF"/>
3: <Ellipse Width="10" Height="10" StrokeThickness="1" Stroke="#FF000000"/>
4: </Canvas>
5:
6: <Canvas x:Name="HueSelector" Height="8" Canvas.Left="0" Canvas.Top="-4">
7: <Path Width="5" Height="8" Stretch="Fill" StrokeLineJoin="Round" Stroke="#FF000000"
Fill="#FF000000" Data="F1 M 276.761,316L 262.619,307.835L 262.619,324.165L 276.761,316 Z " />
8: <Path Width="5" Height="8" Canvas.Top="8" Canvas.Left="20" Stretch="Fill" StrokeLineJoin="Round"
Stroke="#FF000000" Fill="#FF000000"
Data="F1 M 276.761,316L 262.619,307.835L 262.619,324.165L 276.761,316 Z ">
9: <Path.RenderTransform>
10: <RotateTransform Angle="180" />
11: </Path.RenderTransform>
12: </Path>
13: </Canvas>
The End Result
Each of these pieces combine to produce something like the figure below. Of course, this is pretty much useless until we wire up the XAML primitives.
Conclusion
Hopefully this post gives you a little insight into how the UI was composed. Next time, we will finish off the UI and begin writing actual code. See you then!