WPF Simple ColorPicker Control






4.41/5 (16 votes)
WPF Simple ColorPicker control with color change notification
Introduction
This is a simple ColorPicker
control. It allows you to pick a color from a specified range. This ColorPicker
includes some event wrappers for CurrentColorChanged
and LastColorChanged
. So when the value changes, the respective event will be thrown.
Background
The ColorPicker
is split in four main parts:
ColorPickerUnit
ColorSelectorUnit
ColorBarUnit
ColorInfoUnit
The ColorPickerUnit
is the left square, the ColorSelectorUnit
describes the centered square in the image you can see, the ColorBarUnit
is at the bottom, and shows the Current and Last selected color. Last but not least is the ColorInfoUnit
which shows you the RGB and HEX value of the color. The main idea of this project was to clip the mouse to the ColorPicker
or ColorSelectorUnit
in the way which one has been clicked. So what I mean is, the mouse gets "jailed", you can't move out of this location. My second idea was to set the mouse to the last position wherever you clicked one of these units.
So far so good!
Using the Code
My project includes the following classes:
- _ColorPicker.xaml with codefile: This one is the root element. It includes the basic events and the units mentioned above. So I can say this class controls the main user interaction.
- CopyBox.cs: This class inherits from border, and the border includes a textbox, and one property called
value
, which sets or gets the current value of theCopyBoxes
. That's all. - MouseClipping.cs : This is one tricky class. It allows to clip the mouse to any
UIElement
. This is possible by using an API-Method calledClipCursor
which is stored in the user32.dll. - MouseControling.cs : This one also includes some API calls ->
SetCursorPos
,GetCursorPos
,GetPixel
,GetWindowDC
andReleaseDC
. - ValueBox.cs : This class has some properties ->
Maximum
andValue
, and some events. When you set thevalue
property, the class calculates the length of a border. So you can see the value stored in a textbox and the position in graphic mode.
Let's Dig Deeper!
Here is some important code of the _ColorPicer.xaml.cs.
A Dependency Property Implementation
In practice, dependency properties are just normal .NET properties hooked into some extra WPF infrastructure. This is all accomplished via WPF APIs; no .NET languages (other than XAML) has an intrinsic understanding of a dependency property.
In my case, I used the DependencyProperties for getting a notification if Current or last Color changed. For an example, I registered a DependencyProperty
called IsCurrentColorProperty.
The first parameter is the name of the property, the second one is the type of the property. I used color because the value which will be stored in it will be a RGB color. Typeof _ColorPicker
means that the property is registered on the _ColorPicker
class. The last one is an FrameworkPropertyMetadata
. The first parameter is my default color, which is white
, and the last one is an PropertyChangedCallback
which call the method OnIsCurrentColorChanged.
A Routed Event Implementation
Just as dependency properties are represented as public static DependencyProperty
fields with a conventional Property
suffix, routed events are represented as public static
RoutedEvent
fields with a conventional Event
suffix. The routed event is registered much like a dependency property in the static
constructor, and a normal .NET event-or event wrapper-is defined to enable more familiar use from procedural code and adding a handler in XAML with event attribute syntax.
Now you can think why he registered an RoutedEvent
. He already has a DependencyProperty
with a ChangeCallback
. That is right but when you use this ColorPicker
control, you want to write your own code when some color changes. So my Routed
events take four parameters. First the name of the Event
, that's the name of the event you will find. The second one is the RoutingStrategy
. I used Tunnel
which means that the event is first raised on the root, then on each element down the tree until the source element is reached. Typeof RoutedEventHandler
simply describes the type of the RoutedEvent
, and last typeof _ColorPicker
means that the holder of this RoutedEvent
is the class _ColorPicker
. Slightly below, you will find the RoutedEventWraper.
It allows you to add or remove the EventHandler
.
#region DependencyProperty - RoutedEvets declaration
private static readonly DependencyProperty IsCurrentColorProperty =
DependencyProperty.Register(
"IsCurrentColor", typeof(Color), typeof(_ColorPicker),
new FrameworkPropertyMetadata(Color.FromRgb(255, 255, 255),
new PropertyChangedCallback(OnIsCurrentColorChanged)));
private static readonly RoutedEvent CurrentColorChangedEvent =
EventManager.RegisterRoutedEvent(
"CurrentColorChanged",
RoutingStrategy.Tunnel,
typeof(RoutedEventHandler), typeof(_ColorPicker));
private static readonly DependencyProperty IsLastColorProperty =
DependencyProperty.Register(
"IsLastColor", typeof(Color), typeof(_ColorPicker),
new FrameworkPropertyMetadata(Color.FromRgb(255, 255, 255),
new PropertyChangedCallback(OnIsLastColorChanged)));
private static readonly RoutedEvent LastColorChangedEvent =
EventManager.RegisterRoutedEvent(
"LastColorChanged",
RoutingStrategy.Tunnel,
typeof(RoutedEventHandler),typeof(_ColorPicker));
#endregion
Routed Events Wrappers
As with a property wrapper, an event wrapper must not do anything in its accessors other than call AddHander
and RemoveHandler
:
#region Event wrappers
/// <summary>
/// Occurs when the current color changed.
/// </summary>
public event RoutedEventHandler CurrentColorChanged
{
add { AddHandler(CurrentColorChangedEvent, value); }
remove { RemoveHandler(CurrentColorChangedEvent, value); }
}
/// <summary>
/// Occurs when the last color changed.
/// </summary>
public event RoutedEventHandler LastColorChanged
{
add { AddHandler(LastColorChangedEvent, value); }
remove { RemoveHandler(LastColorChangedEvent, value); }
}
#endregion
Change Notification
Whenever the value of a dependency property changes, WPF can automatically trigger a number of actions depending on the property´s metadata. These actions can be re-rendering the appropriate elements, updating the current layout, refreshing data binding, and much more.
In my case, I call a method SetColorInfo
which takes a color. This method sets the text of the ValueBoxes
and the CopyBoxes
. Also I set the _currentColor.Background
and very importantly, I raise my RoutedEvent
.
private static void OnIsCurrentColorChanged
(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
SetColorInfos((Color)e.NewValue);
_currentColor.Background = new SolidColorBrush((Color)e.NewValue);
_vbB.RaiseEvent(new RoutedEventArgs(CurrentColorChangedEvent,
(Colore.NewValue));
}
private static void OnIsLastColorChanged(DependencyObject o,
DependencyPropertyChangedEventArgs e)
{
_vbB.RaiseEvent(new RoutedEventArgs(LastColorChangedEvent,
(Color)e.NewValue));
}
Here is some important code of MouseCliping.cs:
That's the basic API method signature to call ClipCursor
:
#region API - Methodes
[DllImport("user32.dll")]
static extern bool ClipCursor(ref RECT lpRect);
#endregion
This is the method to calculate a rect
from a UIElement
's position. myrect
is an instance of a struct
called RECT
. This one has some properties -> Left
, Top
, Right
, Bottom
. The struct RECT
converts the positions to a System.Drawing.Point
.
To view the struct
, please download the project source.
/// <summary>
/// Clips the cursor to an UIElement.
/// </summary>
/// <param name=""element""></param>
public static void OnUIElement(UIElement element)
{
uIEelement = element;
myrect.Left = (int)(element.PointFromScreen(new Point(0, 0)).X * -1);
myrect.Top = (int)(element.PointFromScreen(new Point(0, 0)).Y * -1);
myrect.Right = (int)((element.PointFromScreen(new Point(0, 0)).X * -1) +
element.RenderSize.Width);
myrect.Bottom = (int)((element.PointFromScreen(new Point(0, 0)).Y * -1) +
element.RenderSize.Height);
ClipCursor(ref myrect);
}
Here is some important code of MouseControling.cs:
SetCursorPos
- Sets the cursor to a point on the screenGetCursorPos
- Gets the cursor position like the WPF method
(UIElement.PointToScreen (Point point)
)GetPixel
- Gets the color of a pixel on the screenGetWindowDC
- Gets the current handle of the windowReleasDC
- Release the current handle of the window
[System.Runtime.InteropServices.DllImportAttribute
("user32.dll", EntryPoint = "SetCursorPos")]
[return: System.Runtime.InteropServices.MarshalAsAttribute
(System.Runtime.InteropServices.UnmanagedType.Bool)]
private static extern bool SetCursorPos(int X, int Y);
[DllImport("user32.dll")]
private static extern int GetCursorPos(ref POINT lpPoint);
[DllImport("gdi32.dll")]
private static extern int GetPixel(int hdc, int nXPos, int nYPos);
[DllImport("user32.dll")]
private static extern int GetWindowDC(int hWnd);
[DllImport("user32.dll")]
private static extern int ReleaseDC(int hWnd, int hDC);
This is a very nice and useful method to get the pixel color under the mouse.
I create an instance point
of the struct POINT
. The API-method GetCursorPos
takes a reference to the Struct
. Now the point contains the current mouse position (this is an absolute position from the left top). Next I create a System.Media.Point
, and give it the x and y from the struct
. Why so complicated? Because The API-method can't handle a System.Media.Point
. lDC
takes the handle of the Current window. Now we have all information to call GetPixel()
. This API-method takes the current handle lDC
and the x
and y
coordinates. Don't forget to call ReleaseDC()
! The last step is to extract the bytes. I used an array with the length of 4 to store all ARGB bytes. I found the information where the bytes are stored on www.pinvoke.net.
private struct POINT
{
public uint x;
public uint y;
}
/// <summary>
/// Returns the color of the pixel under the mouse.
/// </summary>
/// <returns></returns>
public static Color PixelUnderMouse()
{
POINT point = new POINT();
GetCursorPos(ref point);
Point pos = new Point();
pos.X = point.x;
pos.Y = point.y;
int lDC = GetWindowDC(0);
int intColor = GetPixel(lDC, (int)pos.X, (int)pos.Y);
ReleaseDC(0, lDC);
byte[] argb = new byte[4];
//A
//argb[0] = (byte)((intColor >> 0x18) & 0xffL);
//B
argb[1] = (byte)(intColor & 0xffL);
//G
argb[2] = (byte)((intColor >> 8) & 0xffL);
//B
argb[3] = (byte)((intColor >> 0x10) & 0xffL);
return Color.FromRgb(argb[1], argb[2], argb[3]);
}
Points of Interest
As I started this project, I had no idea of API calls. After spending a few hours on Google, I found a very useful site called www.pinvoke.net. This site contains a large library of API methods and a "how to use" them. The Visual Studio plugin is also very nice and allows you to search for API signatures from Visual Studio.
History
- 4th February, 2009: Initial post