在图形设计软件中,颜色拾取工具是一个非常有用的功能,它允许用户从屏幕上的任何位置选择颜色。不同于Adobe Illustrator或Photoshop中的颜色拾取器,只能在应用程序内部工作,有些工具如Expression Blend或Visual Studio Designer中的颜色拾取器,可以在整个操作系统范围内工作。本文将介绍如何在WPF应用程序中实现一个类似的全局颜色拾取工具。
实现全局颜色拾取工具的关键在于能够捕获整个桌面的屏幕截图,并能够随着鼠标移动获取屏幕像素的颜色信息。这涉及到Windows API的调用,以及对WPF应用程序的屏幕截图功能的实现。
在Windows Forms中,捕获屏幕截图相对简单,但在WPF中,需要调用pinvoke方法来实现。这需要使用User32.dll和Gdi32.dll中的一些方法。
public class InteropHelper
{
[DllImport("user32.dll")]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
public static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("gdi32.dll", SetLastError = true)]
public static extern bool BitBlt(IntPtr hDestDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, Int32 dwRop);
[DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
[DllImport("gdi32.dll", SetLastError = true)]
public static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
[DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hwnd, IntPtr dc);
}
使用这些Interop Helpers,可以捕获桌面的屏幕截图。屏幕捕获方法将获取X、Y、Width和Height参数。由于要捕获整个屏幕的截图,需要使用SystemParameters静态类获取屏幕的宽度和高度。
现在已经完成了屏幕截图的捕获,接下来需要通过匹配鼠标位置来获取相应像素的颜色信息。因此,需要捕获整个应用程序以及应用程序之外的鼠标移动事件,以获取鼠标位置。为了实现全局鼠标移动钩子,需要调用一些本地方法。
System.Drawing.Point _point = System.Windows.Forms.Control.MousePosition;
使用这段代码,可以在点击颜色拾取按钮时启动一个计时器,并在每次计时器的滴答中获取鼠标位置。
获取鼠标位置后,可以使用这个位置从已经捕获的BitmapSource中获取相应的像素信息。BitmapSource.CopyPixels方法将给一个字节数组,其中前三个值足以找到颜色。
int stride = (screenimage.PixelWidth * screenimage.Format.BitsPerPixel + 7) / 8;
byte[] 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]));
这段代码将从屏幕截图中提取像素颜色,并将其设置为拾取器的颜色。
除了鼠标光标之外,一切都很好。在WPF中,可以使用FrameworkElement.Cursor更改光标,但这仅在应用程序内部有效,而不是在应用程序的主窗口之外。如果想要更改整个操作系统的光标,WPF没有直接的方法。但是,大多数开发人员担心为什么需要更改整个Windows光标。但是,以开发WPF中的颜色拾取器为例(用于拾取颜色),不同于Illustrator或Photoshop中的颜色拾取器(不能在应用程序外部拾取颜色),颜色拾取器可以在应用程序之外拾取颜色。
在这种情况下,光标应该更改,因为箭头光标不适合拾取颜色。通常,光标值存储在注册表中。
HKEY_CURRENT_USER\Control Panel\Cursors
更改这里的值将更改光标,但系统需要重新启动才能生效(理解,没有开发人员会接受这一点)。为了避免这种情况并使应用程序立即生效,需要调用pinvoke调用。
[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
public static extern bool SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
以下方法将刷新光标值:
private void ChangeCursor()
{
RegistryKey pRegKey = Registry.CurrentUser;
pRegKey = pRegKey.OpenSubKey(@"Control Panel\Cursors");
paths.Clear();
foreach (var key in pRegKey.GetValueNames())
{
Object _key = pRegKey.GetValue(key);
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);
}
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);
}