65.9K
CodeProject 正在变化。 阅读更多。
Home

制作一个带 WTL 的颜色探测器实用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (26投票s)

2003 年 7 月 9 日

MIT

8分钟阅读

viewsIcon

94278

downloadIcon

1689

制作使用 WTL 的颜色拾取器实用程序,并回顾剪贴板管理 API。

引言

想查看网页中使用的颜色吗?ColorSpy 是一个使用 WTL 编写的轻量级桌面实用程序。它让任何喜欢 WTL 的人都可以玩转颜色。ColorSpy 的功能有三项

  1. 采样屏幕上的像素颜色(是的,多种颜色)
  2. 从 HTML 源代码读取 RGB 三元组(#rrggbb 格式)并在颜色框中显示其颜色(参见上图截图)
  3. 读取颜色名称并显示其颜色以及 RGB 三元组

环境

ColorSpy 是使用 VC++ 6.0 SP5 和 WTL 7.0 创建的,主要在我 **Win2k SP3** 的机器上进行了测试。要编译,您需要安装 WTL。在撰写本文时,您可以在 此处 找到 MS WTL。如果您还没有试过,请去下载。

拾取和采样屏幕上的像素颜色

实际上,我创建了一个带有 WS_EX_TRANSPARENT 样式的透明窗口,它覆盖了整个屏幕,但很快意识到使用透明窗口不起作用;您可以透过透明窗口看到所有像素颜色,但鼠标事件却无法穿透。

ColorSpy 拾取像素颜色的方法受到了其他实用程序(例如 WindowFinder)的启发。它采用了这些实用程序中一条 Windows 编程的黄金法则。这条黄金法则是,当你发现没有合适的响应消息时,就自己创建一个。ColorSpy 还使用定时器定期创建其应用程序特定的消息(WM_APP_COLORSPY),然后自己响应该消息,拾取鼠标光标所在位置的像素的颜色。

  // Retrieve pixel color
  ::GetCursorPos (&m_cursor);
  HDC hdc = ::GetDC(NULL); //entire screen
  m_clrPixel = ::GetPixel(hdc, m_cursor.x, m_cursor.y);
  ::ReleaseDC(NULL, hdc);  //release dc

控制定时器将是采样颜色的关键。当按住 CTRL+SHIFT 时,通过停止定时器,ColorSpy 会采样像素颜色并将其存储在颜色框中。它将保持非活动状态,直到您再次单击颜色框。

  // Use CTRL+SHIFT keys for sampling pixel color
  if (::GetAsyncKeyState(VK_CONTROL) < 0 && 
                 ::GetAsyncKeyState(VK_SHIFT) < 0)
  {
    m_colorBox.Lock();
    ((CEdit)m_infoBox).SetReadOnly(FALSE);
  }

如果您继续运行定时器,您可以使用 CTRL+SHIFT 作为切换键来随时恢复颜色拾取……但自定义完全取决于您……总有一种以上的方法可以做到。

从 HTML 源代码读取颜色代码

当 ColorSpy 非活动状态(没有定时器运行时),ColorSpy 会执行不同的任务;它从 HTML 文件中读取颜色代码(以 '#RRGGBB' 格式,包括前面的 '#')。例如,您访问一个网站并查看源代码或在您使用的编辑器中打开一个 HTML 文件。

选择颜色代码并按 CTRL+C,您将获得所选的颜色。

为了实现这一点,ColorSpy 利用了一个名为剪贴板查看器窗口的剪贴板管理功能,因此它可以读取用户剪辑的颜色代码。这是一个模板类示例,可将 ColorSpy 变成一个用于颜色代码监控扩展的剪贴板查看器窗口。

template <typename T> 
class CColorSpyCBViewer
{
public:
  HWND m_hWndNextCBViewer;
  bool m_bRegistered;

  BEGIN_MSG_MAP(CColorSpyCBViewer< T >)
    ALT_MSG_MAP(1)
    // for CWindowImpl based windows
    MESSAGE_HANDLER(WM_CREATE, OnCreate) 
    // for CDialogImpl based windows
    MESSAGE_HANDLER(WM_INITDIALOG, OnCreate) 
    MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
    // Notification handlers
    MESSAGE_HANDLER(WM_DRAWCLIPBOARD, OnDrawClipboard)
    // necessary indeed
    MESSAGE_HANDLER(WM_CHANGECBCHAIN, OnChangeCbChain) 
  END_MSG_MAP()
          
  CColorSpyCBViewer() : m_hWndNextCBViewer(0), m_bRegistered(false) {}
  
  LRESULT OnCreate(UINT uMsg, WPARAM wParam, 
                  LPARAM lParam, BOOL& bHandled)
  {
    bHandled = FALSE;
    T* pT = static_cast<T*>(this);

    // Join the members of clipboard viewer chain
    m_hWndNextCBViewer = pT->SetClipboardViewer(); //can be nothing
    m_bRegistered = true;
    return 0;
  }
      
  LRESULT OnDestroy(UINT uMsg, WPARAM wParam, 
                   LPARAM lParam, BOOL& bHandled)
  {
    T* pT = static_cast<T*>(this);

    // Say good-bye to the members
    if (IsWindow(m_hWndNextCBViewer))
      pT->ChangeClipboardChain(m_hWndNextCBViewer);
    return 0;
  }
      
  LRESULT OnDrawClipboard(UINT uMsg, WPARAM wParam, LPARAM lParam, 
                            BOOL& /*bHandled*/)
  {
      // Viewer receives whenever the content of
      // the clipboard is changed
      // wParam is a handle to the window that
      // has put clipboard data
      
      T* pT = static_cast<T*>(this);
      
      // Registering this window as a CB wiewer window
      if (m_bRegistered == false) // calling ::SetClipboardViewer() in 
                                        // OnCreate handler
      {
            ATLTRACE("Registering... wParam: %#x\n", wParam);
            return 0; // do nothing but return gracefully
      }

    ATLTRACE("OnDrawClipboard(): Wnd %#x put CB data\n", wParam);

    if (IsClipboardFormatAvailable(CF_TEXT) && pT->OpenClipboard())
    {
      CString text;
      HANDLE hData = ::GetClipboardData( CF_TEXT );
      text = (char*)::GlobalLock( hData );
      ::GlobalUnlock( hData );
      CloseClipboard();

      pT->SetColor(text);
    }
    
    // Pass WM_DRAWCLIPBOARD to the next window
    // in clipboard viewer chain
    RouteMessage(uMsg, wParam, lParam);
    return 0;
  }
      
  LRESULT OnChangeCbChain(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, 
                           BOOL& /*bHandled*/)
  {
    T* pT = static_cast<T*>(this);

    // Clipboard wiewer window is supposed to
    // respond to WM_CHANGECBCHAIN 
    // to maintain the chain.
    HWND hWndRemove = reinterpret_cast<HWND>(wParam);
    HWND hWndAfter = reinterpret_cast<HWND>(lParam);
    
    if (m_hWndNextCBViewer == hWndRemove)
    {
          m_hWndNextCBViewer = hWndAfter;
    }
    
    // pass the WM_CHANGECBCHAIN (0x030D)
    RouteMessage(WM_CHANGECBCHAIN, wParam, lParam);
    return 0;
  }
  
  void RouteMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
  {
    // Toss WM_CHANGECBCHAIN message to the next Clipboard wiewer in the
    // viewer chain to notify of the update of chain.
    if (m_hWndNextCBViewer)
      ::SendMessage(m_hWndNextCBViewer, uMsg, wParam, lParam);
  }
};

剪贴板(查看器窗口)管理

之前我说错了。CB 查看器窗口实际上应该响应 WM_CHANGECBCHAIN 消息并刷新指向下一个查看器窗口的指针。

如果您在网上搜索剪贴板查看器,您可能会找到如下 SDK 文档:

创建剪贴板查看器窗口

剪贴板查看器窗口显示剪贴板的当前内容,并在剪贴板内容更改时接收消息。

要创建剪贴板查看器窗口,您的应用程序必须执行以下操作:

  • 将窗口添加到剪贴板查看器链中。
  • 处理 WM_CHANGECBCHAIN 消息。
  • 处理 WM_DRAWCLIPBOARD 消息。
  • 在窗口销毁之前将其从剪贴板查看器链中移除。

以下是我对 Windows 2000 SP3 上 CB 查看器的观察和重要更新。那就是,您机器上的结果很可能相同。

  • 将窗口添加到剪贴板查看器链中。=> TRUE。使用 WTL,您可以使用 CWindow::SetClipboardWindow() 来实现。
  • 处理 WM_CHANGECBCHAIN 消息。=> TRUE
  • 处理 WM_DRAWCLIPBOARD 消息。=> TRUE,但并非完全准确。SDK 文档说“WPARAM 未使用……”,或“必须为零……”。然而,WM_DRAWCLIPBOARD 消息附带填充的 WPARAM,而 WPARAM 实际上是放置 CB 数据的窗口的句柄。如果我使用 MFC,我可能不会意识到 WPARAM 的值是什么。WTL 很棒。
  • 在窗口销毁之前将其从剪贴板查看器链中移除。=> TRUE。确保在退出时调用 CWidnow::ChangeClipboardChain()

关于剪贴板管理 API 的文档是否值得一些说明?还是不值得?

颜色名称。告诉我你的名字。

如果采样颜色值与颜色名称到 RGB 值表中的 RGB 值匹配,ColorSpy 会在工具提示中显示其名称。当 ColorSpy 非活动状态时,您可以尝试颜色名称,例如 darkviolet、navy、navajowhite 等等。

在我们开始之前,您可能想拉取此更新的源代码。为了支持颜色名称(以下称为 colornames),ColorSpy 使用了 rgbtable.h 中定义的 RGB 颜色名称到值表的修改版本,该表取自 XFree86 的 Xpm 库(请参见 参考资料)。作为补充信息,ColorSpy 仅当采样像素颜色值与表中定义的任何 RGB 值匹配时,才将颜色名称显示为工具提示文本。除了颜色三元组 (#RRGGBB) 之外,它还可以通过查找表解释颜色名称并显示其颜色和 RGB 三元组。

我从没想到这种颜色表支持最终会成为另一个值得告诉你们的故事。

在测试 ColorSpy 的 colornames 时,我发现网络浏览器中有一些独特的颜色名称解释。你能说出以下颜色渲染出的名字吗?猜猜看。您现在可以运行 ColorSpy 来检查这些颜色的 RGB 值。

现在时间到了,先生。如果您认为这些是 Green 相关的,猜对了。它们被称为 Gray (grayXX)。与那些浏览器无法识别的颜色名称不同,这些名称的结果颜色可能取决于您使用的浏览器类型。正如您所见,这些名称却被以某种方式解释了。

由于这种解释 - RGB(0x00, 0xA0, 0xXX),其中 XX 是 Gray 颜色名称的数字部分,除了 gray100(即黑色) - 我不得不从原始颜色表中注释掉 grayXX 部分,以避免混淆。颜色名称可能信息丰富,但它们可能不是那么必不可少。一旦您玩弄了颜色名称并找到了您喜欢的颜色名称,您就可以改用该颜色的三元组。哦,对了,这种解释在浏览器中很常见。底线 - 令人困惑但又引人入胜,值得一试。

快速检查桌面颜色设置

此功能更多是为开发人员准备的,而不是为网页设计师准备的。您不必通过控制面板中的“显示属性”小程序来检查,而是可以直接从系统颜色类型(WinUser.h 中定义的 COLOR_XXXX)中检查桌面颜色设置。例如:

  • COLOR_INFOBK,即工具提示的背景色
  • COLOR_ACTIVECAPTION,即活动窗口标题栏的颜色(转到“控制面板”>“显示”>“外观”中的“颜色 1”)
  • COLOR_GRADIENTACTIVECAPTION,即活动标题栏的背景色。(颜色 2)

所有系统颜色类型都在 CSysColors 类中定义。大约有 30 种不同的显示方面作为系统颜色类型。在 colorbox.h 中,CSysColors 也用于根据您的桌面设置确定 ColorSpy 是否应使用系统颜色画笔,如下所示。

#define HANDLE_SYS_COLOR_BRUSH(index) \
  if (m_clrBackground == theSystemColors[index].clr) \
  { \
    return brh.CreateSysColorBrush(index); \
  }
    
  HBRUSH CreateBrush()
  {
    // Use system cache
    CBrushHandle brh;

    // whatever color is requested, try matching your system color settings
    // first; chances are the most of time it will hit one of them if you 
    // are not exploring the world of colors.

    HANDLE_SYS_COLOR_BRUSH(COLOR_BACKGROUND); // Desktop color (same as 
                     // COLOR_DESKTOP which sounds more descriptive)
    [...] // rest omitted
  }

当你的鼠标让你抓狂时

您可能已经尝试了早期版本,并意识到处理细小的图片很困难。为什么不试试放大镜?此功能在采样密集彩色图片中的像素颜色时可能特别有用。

一些实用程序提供放大镜,显示放大后的图片,但有时会让您在处理单个像素时感觉像个机器。为什么?因为在查看放大图片时,您仍然必须将鼠标指针精确地定位在屏幕上的单个像素上。毕竟 2 像素的鼠标移动仍然是 2 像素的距离,所以无论您多么仔细地查看放大镜,它只会告诉您鼠标光标仍然指向错误的位置,除非您可以在放大图片上操作。

ColorSpy 的放大镜像一个可点击的面板,具有可变的放大级别。所以先截张图。您不必在屏幕上精确地定位像素。然后随意点击放大后的图像。我希望有了这个功能,您就无需再启动另一个放大镜应用程序来方便查看了。下次当您感觉像个机器时,您可能会说“糟糕的鼠标,还是软件?(可能还有令人毛骨悚然的背景音乐)”。

简单胜于花哨

我的 ColorSpy 是一个简单的应用程序。我希望它能成为您桌面工具的一部分,因为它足够简单。有了源代码,您可以制作自己的 ColorSpy。想法完全取决于您。

这是我第一次尝试写文章,我一直很钦佩 CodeProject 上那些辛勤工作的贡献者们,他们贡献了自己的文章。如果您能先告诉我您阅读我文章的原因,那么糟糕的评分也是受欢迎的……

祝您 WTL 编程愉快!

参考文献

  • rgbtable.h - 硬编码的 rgb.txt RGB 颜色数据库。FTP

历史

  • 2003/7/6 初始版本。
  • 2003/7/20 为设计师添加颜色名称。为开发人员添加系统颜色类型。
  • 2003/7/24 由于我关于剪贴板(查看器窗口)管理的描述有误,更新了文章。请参见下方的讨论。谢谢,Mike-。
  • 2003/9/29 引入了新功能,可点击的放大镜和色相-饱和度-亮度(亮度)显示……
© . All rights reserved.