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

通过所有者绘制控件实现的颜色选择器和字体选择器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (19投票s)

2002年7月31日

CPOL

4分钟阅读

viewsIcon

161149

downloadIcon

3446

演示如何通过所有者绘制控件实现 WIN32 颜色选择器和字体选择器

下载源代码和可执行文件 (VC6)

 

 

 

引言

虽然在这个网站上有很多功能齐全的颜色选择器 [1, 2, 3],但这些代码通常是 MFC 或 WTL 的。我想在这里介绍一个完全用 Windows API 编写的颜色选择器。我还想说服您,干净而基础的 API 函数可以产生友好、整洁且强大的控件。

首先,我将详细介绍如何通过使用所有者绘制列表框来生成部分类似 Office 的颜色选择器面板。其次,我将简要介绍如何使用所有者绘制组合框来完成类似的工作。

实现

首先,您必须启动 Visual C++,然后创建一个 Win32 应用程序,其中显示典型的“Hello World”字符串。接下来,我们需要创建一个对话框,然后在其中嵌入一个列表框。列表框的属性表应如下所示:

现在我们需要编写代码来自己绘制列表框。不要惊慌,因为代码行数并不多。我们只需要在对话框的回调函数(即列表框的所有者窗口)中响应一些消息。

/**
 * Callback function of "Background color" dialog box.
 */
LRESULT CALLBACK background(HWND hdlg, UINT message, WPARAM wParam, LPARAM lparam)
{    
    switch (message)
    {
    case WM_INITDIALOG:
        // Place your own code here
    case WM_MEASUREITEM:
        // Place your own code here   
    case WM_DRAWITEM:
        // Place your own code here
    case WM_CTLCOLORLISTBOX:
        // Place your own code here
    case WM_COMMAND:
        // Place your own code here
    }
    return FALSE;
}

为了初始化列表框控件,我们需要处理 WM_INITDIALOG 消息。我们所做的只是将预定义的全局变量颜色值分配给每个列表框项。

case WM_INITDIALOG:
    int nColor;
    for (nColor = 0; nColor < sizeof(g_crItems)/sizeof(DWORD); nColor++)
    {
        SendDlgItemMessage(hdlg, IDC_BACKGROUND, LB_ADDSTRING, nColor, (LPARAM) "");
        SendDlgItemMessage(hdlg, IDC_BACKGROUND, LB_SETITEMDATA, 
                           nColor, (LPARAM) g_crItems[nColor]);
        if (g_bgColor == g_crItems[nColor])
            SendDlgItemMessage(hdlg, IDC_BACKGROUND, LB_SETCURSEL, nColor, 0);
    }
    return TRUE;

接下来,在接收到 WM_MEASUREITEM 消息时,我们需要告诉对话框每个列表框项的尺寸。当创建列表框控件时会发送此消息。参数 lparam 是指向 MEASUREITEMSTRUCT 类型结构的指针。在演示颜色选择器面板中,我们有 6 行 8 列。

case WM_MEASUREITEM:
    RECT rc;
    LPMEASUREITEMSTRUCT lpmis; 
    lpmis = (LPMEASUREITEMSTRUCT) lparam; 
    GetWindowRect(GetDlgItem(hdlg, lpmis->CtlID), &rc);
    lpmis->itemHeight = (rc.bottom-rc.top)/6; 
    lpmis->itemWidth = (rc.right-rc.left)/8;
    break;

当所有者绘制控件需要重绘时,它会向其父窗口发送 WM_DRAWITEM 消息。这发生在控件首次创建时、按下或释放时、获得或失去输入焦点时,以及任何其他需要重绘的时候。lparam 消息参数是指向 DRAWITEMSTRUCT 类型结构的指针。它包含程序绘制控件所需的信息。与列表框配合使用的重要结构字段是 hDC(设备上下文)、rcItem(一个提供每个列表框项大小的 RECT 结构)、itemID(列表框 ID)和 itemState(指示该项是否被按下或是否获得输入焦点)。

case WM_DRAWITEM: 
	HDC hdc; 
	COLORREF cr; 
	HBRUSH hbrush; 
	DRAWITEMSTRUCT * pdis; 
	pdis = (DRAWITEMSTRUCT *)lparam; 
	hdc = pdis->hDC; 
	rc = pdis->rcItem; 
	// Transparent. 
	SetBkMode(hdc,TRANSPARENT); 
	// NULL object 
	if (pdis->itemID == -1) return 0; 
	switch (pdis->itemAction) 
	{ 
	case ODA_DRAWENTIRE: 
		switch (pdis->CtlID) 
		{ 
		case IDC_BACKGROUND: 
			rc = pdis->rcItem; 
			cr = (COLORREF) pdis->itemData; 
			InflateRect(&rc, -3, -3); 
			hbrush = CreateSolidBrush((COLORREF)cr); 
			FillRect(hdc, &rc, hbrush); 
			DeleteObject(hbrush); 
			FrameRect(hdc, &rc, (HBRUSH) GetStockObject(GRAY_BRUSH)); 
			break; 
		} 
		// *** FALL THROUGH *** 
		case ODA_SELECT: 
			rc = pdis->rcItem; 
			if (pdis->itemState & ODS_SELECTED) 
			{ 
				rc.bottom --; 
				rc.right --; 
				// Draw the lighted side. 
				HPEN hpen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_BTNSHADOW)); 
				HPEN holdPen = (HPEN)SelectObject(hdc, hpen); 
				MoveToEx(hdc, rc.left, rc.bottom, NULL); 
				LineTo(hdc, rc.left, rc.top); 
				LineTo(hdc, rc.right, rc.top); 
				SelectObject(hdc, holdPen); 
				DeleteObject(hpen); 
				// Draw the darkened side. 
				hpen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_BTNHIGHLIGHT)); 
				holdPen = (HPEN)SelectObject(hdc, hpen); 
				LineTo(hdc, rc.right, rc.bottom); 
				LineTo(hdc, rc.left, rc.bottom); 
				SelectObject(hdc, holdPen); 
				DeleteObject(hpen); 
			} 
			else 
			{ 
				hbrush = CreateSolidBrush(GetSysColor(COLOR_3DFACE)); 
				FrameRect(hdc, &rc, hbrush); DeleteObject(hbrush); 
			} 
			break; 
		case ODA_FOCUS: 
			rc = pdis->rcItem; 
			InflateRect(&rc, -2, -2); 
			DrawFocusRect(hdc, &rc); 
			break; 
		default: 
			break; 
	} 
	return true;    
    

列表框的默认背景颜色是白色,这使得它看起来很奇怪,因为对话框的背景是灰色的。要修改列表框的背景颜色,我们需要响应 WM_CTLCOLORLISTBOX 消息。在系统绘制列表框之前会发送此消息。我们只需返回一个画刷句柄,系统将使用它来绘制列表框的背景。

case WM_CTLCOLORLISTBOX:
    return (LRESULT) CreateSolidBrush(GetSysColor(COLOR_3DFACE));

最后,当用户单击“确定”按钮时,我们需要检索用户选择的颜色,如果用户单击“取消”按钮,则直接关闭对话框。

case WM_COMMAND:
    if (LOWORD(wParam) == IDOK) 
    {
        int nItem;
        nItem = SendDlgItemMessage(hdlg, IDC_BACKGROUND, LB_GETCURSEL, 0, 0L);
        g_bgColor = SendDlgItemMessage(hdlg, 
                                       IDC_BACKGROUND, 
                                       LB_GETITEMDATA, 
                                       nItem, 0L);
        EndDialog(hdlg, LOWORD(wParam));
        InvalidateRect(GetParent(hdlg), NULL, true);
        return TRUE;
    }
    else if (LOWORD(wParam) == IDCANCEL)
    {
        EndDialog(hdlg, LOWORD(wParam));
        return TRUE;
    }
    break;

啊哈,就这样。编译代码,您将得到一个颜色选择器,如本文顶部所示。

类似地,如果您想从所有者绘制的组合框创建颜色选择器,只需要进行少量修改。首先,将消息前缀 LB_ 替换为 CB_,其次,不再需要处理 WM_CTLCOLORLISTBOX。对话框的最终外观如下所示。嗯,这还不够好,因为每个颜色的名称应该放在每项的右侧。Charles Petzold(《Programming Windows》第五版作者)有一个很好的例子来说明如何做到这一点。

然而,这还不是全部。通过更改列表框项的内容并控制它们的大小,还可以通过这种方式创建更多花哨的控件。下图显示了字体选择器。在此字体选择器控件中,演示了一些花哨的技术。

  • Windows 中字体列表的枚举。
  • 更改按钮控件的字体样式。
  • 一个可以记住历史条目的自定义组合框。

 

问题

对于所有者绘制列表框颜色选择器,如果它是标签顺序中的第一个控件,则焦点矩形可能无法正确绘制。我花了很长时间试图纠正这一点,但最终放弃了。此外,当鼠标光标移动到每个颜色项上时,最好弹出一个显示颜色名称的工具提示。您的反馈和代码改进将不胜感激。

© . All rights reserved.