Eye Dropper control in WPF

April 28, 2012
Eye Dropper control in WPF


Usually we come across different types of eye dropper controls in designers. We can move the mouse over the desktop and other applications to pick the color under the mouse. A normal eye dropper will pick color only within the application like the one in Adobe Illustrator or Photoshop. But the control I posted here will helps you to choose color from anywhere even outside your application like the one in Expression Blend or Visual Studio Designer. 


The base idea is to pick color from the screen wherever the mouse moving. The underlying magic behind the implementation is, need to take a snap shot of the entire desktop. For every mouse move we going to pick the appropriate pixel information from the image.

Capturing Screenshot 

Lets start with capturing the screen shot, 
Capturing the screenshot is pretty easy with Windows Forms. But in WPF, we need to call pinvoke methods to do that. We need few methods from User32.dll and gdi32.dll.
public class InteropHelper
public static extern IntPtr GetDesktopWindow();

// http://msdn.microsoft.com/en-us/library/dd144871(VS.85).aspx
public static extern IntPtr GetDC(IntPtr hwnd);

// http://msdn.microsoft.com/en-us/library/dd183370(VS.85).aspx
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool BitBlt(IntPtr hDestDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, Int32 dwRop);

// http://msdn.microsoft.com/en-us/library/dd183488(VS.85).aspx
public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);

// http://msdn.microsoft.com/en-us/library/dd183489(VS.85).aspx
[DllImport("gdi32.dll", SetLastError = true)]
public static extern IntPtr CreateCompatibleDC(IntPtr hdc);

// http://msdn.microsoft.com/en-us/library/dd162957(VS.85).aspx
[DllImport("gdi32.dll", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

// http://msdn.microsoft.com/en-us/library/dd183539(VS.85).aspx
public static extern bool DeleteObject(IntPtr hObject);

// http://msdn.microsoft.com/en-us/library/dd162920(VS.85).aspx
public static extern int ReleaseDC(IntPtr hwnd, IntPtr dc);
Using these Interop Helpers take a screen shot of your desktop. The screen capture method will get parameters like X, Y, Width and height parameters. Since we going to take snap shot of the entire screen, get Width and Height of the screen using the static class SystemParameters. 
public static BitmapSource CaptureRegion(IntPtr hWnd, int x, int y, int width, int height)
IntPtr sourceDC = IntPtr.Zero;
IntPtr targetDC = IntPtr.Zero;
IntPtr compatibleBitmapHandle = IntPtr.Zero;
BitmapSource bitmap = null;

// gets the main desktop and all open windows
sourceDC = InteropHelper.GetDC(InteropHelper.GetDesktopWindow());

//sourceDC = User32.GetDC(hWnd);
targetDC = InteropHelper.CreateCompatibleDC(sourceDC);

// create a bitmap compatible with our target DC
compatibleBitmapHandle = InteropHelper.CreateCompatibleBitmap(sourceDC, width, height);

// gets the bitmap into the target device context
InteropHelper.SelectObject(targetDC, compatibleBitmapHandle);

// copy from source to destination
InteropHelper.BitBlt(targetDC, 0, 0, width, height, sourceDC, x, y, InteropHelper.SRCCOPY);

// Here's the WPF glue to make it all work. It converts from an
// hBitmap to a BitmapSource. Love the WPF interop functions
bitmap = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
compatibleBitmapHandle, IntPtr.Zero, Int32Rect.Empty,

catch (Exception ex)

ReleaseDC(IntPtr.Zero, sourceDC);
ReleaseDC(IntPtr.Zero, targetDC);

return bitmap;
Method invoke:
InteropHelper.CaptureRegion(InteropHelper.GetDesktopWindow(),(int)SystemParameters.VirtualScreenLeft,(int)SystemParameters.VirtualScreenTop, (int)SystemParameters.PrimaryScreenWidth,(int)SystemParameters.PrimaryScreenHeight); 

Global Mouse position 

Now we are done with taking the screen shot. Lets pick the color from the appropriate pixel by matching the mouse position. So now we need the Mouse Move event for not only the entire application but also outside of the app to get the mouse position. To achieve a global mouse move hook, we need some native method calls as explained in thisarticle. But this is little bit complex. So I have started a timer while clicking the eye dropper button. And each tick of the timer, I am getting the mouse position using the following code,
System.Drawing.Point _point = System.Windows.Forms.Control.MousePosition;

Copy Pixel Information 

Now we are done with getting the mouse position. Using this position get the appropriate pixel information from theBitmapSource that we have taken. BitmapSource.CopyPixel will give you an array of bytes, in which the first 3 values are enough to find the color. 
int stride = (screenimage.PixelWidth * screenimage.Format.BitsPerPixel + 7) / 8;
pixels = new byte[screenimage.PixelHeight * stride];
Int32Rect rect = new Int32Rect((int)point.X, (int)point.Y, 1, 1);
screenimage.CopyPixels(rect, pixels, stride, 0);
rectcolor.Fill = new SolidColorBrush(Color.FromRgb(pixels[2], pixels[1], pixels[0]));

Global Mouse cursor  

(The following implementation has not included in the attached sample and source code for safety reasons, since it will affect the client registry values. It was considered the following code may risky in certain conditions and ignored in the sample.) 

Everything is fine except the mouse cursor.  It is very obvious that we can change the cursor in WPF usingFrameworkElement.Cursor. But the trick is, it only works within your application and not outside your application Main Window. In case if you want to change the cursor for the entire OS, we don’t have any direct way in WPF. But most of the developers worried why we need to change the entire Windows cursor. But take an example, if we are developing an eye dropper control in WPF (used to pick color). Not like the one in Illustrator or Photoshop (cannot pick color outside the application), but the one we have in Expression Blend or Visual Studio designer (can pick color even outside the application also).
In that cases, the cursor should be changed, because arrow cursor will not be a comfortable one to pick color. Normally cursor values resides in registry.
Registry Key : HKEY_CURRENT_USER\Control Panel\Cursors
Changing the values here will change the cursor, but your system needs a reboot to take effect (I can understand, none of the developers will accept this). To avoid that and make your app. taking immediate effect, you need to invoke a pinvoke call. 
The following method will refresh the cursor  values, 
[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
public static extern bool SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
Iterate through registry values and change the cursor path. 
private void ChangeCursor()
RegistryKey pRegKey = Registry.CurrentUser;
pRegKey = pRegKey.OpenSubKey(@"Control Panel\Cursors");
foreach (var key in pRegKey.GetValueNames())
Object _key = pRegKey.GetValue(key);
//Take a backup.

paths.Add(key, _key.ToString());
Object val = Registry.GetValue(@"HKEY_CURRENT_USER\Control Panel\Cursors", key, null);
Registry.SetValue(@"HKEY_CURRENT_USER\Control Panel\Cursors", key, "foo.cur");

SystemParametersInfo(InteropHelper.SPI_SETCURSORS, 0, null, InteropHelper.SPIF_UPDATEINIFILE | InteropHelper.SPIF_SENDCHANGE);
Make sure you store the registry values before change it, so that you can restore the cursor to the default values. 
 private void ResetCursorToDefault()
RegistryKey pRegKey = Registry.CurrentUser;
pRegKey = pRegKey.OpenSubKey(@"Control Panel\Cursors");
foreach (string key in paths.Keys)
string path = paths[key];
Registry.SetValue(@"HKEY_CURRENT_USER\Control Panel\Cursors", key, path);
InteropHelper.SystemParametersInfo(InteropHelper.SPI_SETCURSORS, 0, null, InteropHelper.SPIF_UPDATEINIFILE | InteropHelper.SPIF_SENDCHANGE);