![](/img/trans.png)
[英]DrawFocusRect function: Strange 2-pixel focus rectangle in Windows 10
[英]Why is DrawFocusRect affected by the Text color?
我在TreeView
自定义绘图例程中使用DrawFocusRect()
得到了奇怪的结果。 轮廓有些不同,有的几乎是实线,有的是虚线。 我发现影响它的是 HDC SetTextColor()
值。 即使选择条填充颜色完全一样,因为我更改了各种文本 colors,确实绘制的轮廓不同。
我最终设置了文本颜色以匹配高亮栏的填充颜色,它给出了与默认树绘制例程相同的轮廓。
这在任何地方都没有记录吗? 还有更多吗?
谢谢?
DrawFocusRect
使用按位异或运算绘制焦点矩形。 这在 SDK 文档中有所暗示,在“备注”部分下,它说:
因为
DrawFocusRect
是 XOR function,所以用同一个矩形第二次调用它会将矩形从屏幕上移除。
这是DrawFocusRect
的全部优势。 这意味着您(或 window 管理员)可以绘制焦点矩形并在需要时将其擦除,而无需重新绘制整个底层内容。 (这在双缓冲、memory、快速图形卡等之前的时代是一个巨大的性能胜利)焦点矩形在需要绘制的地方绘制,然后,当需要擦除时,它只是在第一次绘制的地方再次绘制。 您(或 window 经理)唯一需要跟踪的是矩形的坐标。
您可以使用如下代码手动绘制 XOR 矩形,从而获得与DrawFocusRect
完全相同的效果:
void DrawXorRect(HDC hDC, const RECT* prc)
{
// (1)
static const WORD pattern[] = { 0x5555, 0xAAAA,
0x5555, 0xAAAA,
0x5555, 0xAAAA,
0x5555, 0xAAAA,
};
HBITMAP hbmpPattern = CreateBitmap(8, 8, 1, 1, pattern);
// (2)
HBRUSH hbrPattern = CreatePatternBrush(hbmpPattern);
// (3)
HBRUSH hbrOriginal = (HBRUSH)SelectObject(hDC, hbrPattern);
// (4)
UINT cx;
UINT cy;
SystemParametersInfo(SPI_GETFOCUSBORDERWIDTH , 0, &cx, 0);
SystemParametersInfo(SPI_GETFOCUSBORDERHEIGHT, 0, &cy, 0);
// (5)
PatBlt(hDC, prc->left , prc->top , prc->right - prc->left, cy , PATINVERT); // top
PatBlt(hDC, prc->left , prc->bottom - cy, prc->right - prc->left, cy , PATINVERT); // bottom
PatBlt(hDC, prc->left , prc->top + cy, cx , prc->bottom - prc->top - (cy * 2), PATINVERT); // left
PatBlt(hDC, prc->right - cx, prc->top + cy, cx , prc->bottom - prc->top - (cy * 2), PATINVERT); // right
// (6)
SelectObject(hDC, hbrOriginal);
DeleteObject(hbrPattern);
DeleteObject(hbmpPattern);
}
为简洁起见,省略了错误检查,但这段代码确实有效,而且它确实模仿DrawFocusRect
。 (是的,我什至测试过它。)
让我们 go 通过代码,一步一步,看看它做了什么,它是如何工作的,以及它意味着什么:
首先,它创建一个单色8×8 bitmap ( CreateBitmap()
),由交替的位模式 ( pattern[]
) 组成:开、关、开、关等。(如果你不知道为什么,请打开Windows 计算器,将其切换到十六进制模式,然后粘贴构成模式的十六进制值。看看这些位:看看它们如何在 0 和 1 之间交替?现在,想象一下从中创建一个 bitmap。)
然后,它使用 bitmap 创建画笔 ( CreatePatternBrush()
)。
接下来,它将新创建的画笔选择到 DC ( SelectObject()
) 中。 它保存了返回值,也就是原来在DC中选中的画笔,因为后面需要恢复。
创建所有绘图对象后,它调用SystemParametersInfo()
来检索焦点矩形的宽度 ( SPI_GETFOCUSBORDER_WIDTH
) 和高度 ( SPI_GETFOCUSBORDERHEIGHT
)。
配备了绘图所需的所有对象和信息,它使用PATINVERT
光栅操作对矩形的 4 个边中的每一个进行 blit。 什么是PATINVERT
?
使用 Boolean 异或运算符将指定模式的 colors 与目标矩形的 colors 组合起来。
最后,它通过恢复最初选择的画笔并删除它创建的绘图对象来自行清理。
因此,基本上,焦点矩形是通过将开关像素的交替模式(棋盘模式)与设备上下文的内容(即屏幕上的任何内容)进行异或来绘制的。 这就是PATINVERT
所做的。 但是等等——我们知道设备上下文的 colors 是什么,但是刷子的 colors 是什么? 好吧,请记住画笔是从单色 bitmap 创建的。CreateBitmap 的CreateBitmap
文档说:
如果 bitmap 是单色的,则 0 表示前景色,1 表示目标设备上下文的背景色。
啊哈,所以当画笔的 bitmap 模式为 0 时,画笔的颜色是设备上下文的前景色( SetTextColor
); 当画笔的 bitmap 模式为 1 时,画笔的颜色是设备上下文的背景颜色 ( SetBackColor
)。 这就是设备上下文的前景色和背景色发挥作用的方式。 这些 colors 通过异或运算与设备上下文中原始像素的颜色合并在一起。
使用DrawFocusRect
时,您的 DC 的前景和背景 colors 应分别为黑色和白色。 这是设备上下文的默认前景和背景 colors,因此如果您没有调用SetTextColor
或SetBackColor
函数,这些将是它的 colors。如果您调用了这些函数中的任何一个,则需要:
保存每个返回的值并稍后恢复它,一旦你完成绘图,但在你调用DrawFocusRect
之前(就像上面的代码对第一次调用SelectObject
的返回值所做的那样),或者
在绘图代码的开头调用SaveDC
function 以保存其 state(通过将其压入内部堆栈),然后在绘图代码的末尾调用RestoreDC
function 将其恢复为原始 state。
当 DC 的前景和背景 colors 分别为黑色和白色时, DrawFocusRect
function 将按预期工作。 如果您想匹配 window 管理器在控件周围绘制焦点矩形的方式,这就是您需要做的。 (显然,你确实想要这样做!)
如果出于某种原因,您不想重置 DC 的 colors,那么您将无法使用DrawFocusRect
。 您需要采取另一种方法,您自己控制 colors。 例如,您可以创建颜色(非单色)bitmap,其 colors 固定为白色和黑色,然后从中创建画笔。 或者,您可以考虑用笔和R2_NOT
ROP 代码绘图; 例如:
void DrawXorRect2(HDC hDC, const RECT* prc)
{
LOGBRUSH lb;
lb.lbStyle = BS_SOLID;
lb.lbColor = RGB(0, 0, 0); // black
HPEN hpenNew = ExtCreatePen(PS_COSMETIC | PS_ALTERNATE, 1, &lb, 0, NULL);
HPEN hpenOld = (HPEN)SelectObject(hDC, hpenNew);
int ropOld = SetROP2(hDC, R2_NOT);
int modeOld = SetBkMode(hDC, TRANSPARENT);
HBRUSH hbrOriginal = (HBRUSH)SelectObject(hDC, GetStockObject(NULL_BRUSH));
Rectangle(hDC, prc->left, prc->top, prc->right, prc->bottom);
SelectObject(hDC, hbrOriginal);
SetBkMode(hDC, modeOld);
SetROP2(hDC, ropOld);
SelectObject(hDC, hpenOld);
DeleteObject(hpenNew);
}
我不确定这实际上好得多,因为它仍然需要您设置 DC 的属性,即 ROP 和后台模式。 如果你要设置和恢复DC的属性,为什么不设置和恢复前景和背景colors? 此外,这种基于笔的实现还有一个额外的缺点,即不尊重用户对焦点矩形厚度的偏好,这是一个可访问性错误。 此外,与内置的DrawFocusRect
相比,这里的像素网格略有不同,但不可否认,这是一个非常小的缺点。
另一种方法是创建具有自定义图案的PS_GEOMETRIC
笔( PS_USERSTYLE
,使其交替),并将 ROP 样式设置为R2_XORPEN
/ R2_NOTXORPEN
:
void DrawXorRect3(HDC hDC, const RECT* prc)
{
UINT cx;
UINT cy;
SystemParametersInfo(SPI_GETFOCUSBORDERWIDTH , 0, &cx, 0);
SystemParametersInfo(SPI_GETFOCUSBORDERHEIGHT, 0, &cy, 0);
LOGBRUSH lb;
lb.lbStyle = BS_SOLID;
lb.lbColor = RGB(0, 0, 0);
static const DWORD pattern[] = { 0, 2 };
HPEN hpenWidth = ExtCreatePen(PS_GEOMETRIC | PS_USERSTYLE, cx, &lb, 2, pattern);
HPEN hpenHeight = ExtCreatePen(PS_GEOMETRIC | PS_USERSTYLE, cy, &lb, 2, pattern);
int ropOriginal = SetROP2(hDC, R2_NOTXORPEN);
HPEN hpenOriginal = (HPEN)SelectObject(hDC, hpenHeight);
MoveToEx(hDC, prc->left , prc->top , NULL); // \ top
LineTo (hDC, prc->right , prc->top ); // / edge
MoveToEx(hDC, prc->left + cx, prc->bottom - cy , NULL); // \ bottom
LineTo (hDC, prc->right - cx, prc->bottom - cy ); // / edge
SelectObject(hDC, hpenWidth);
MoveToEx(hDC, prc->left , prc->top + (cy * 2), NULL); // \ left
LineTo (hDC, prc->left , prc->bottom - cy ); // / edge
MoveToEx(hDC, prc->right - cx, prc->top + cy , NULL); // \ right
LineTo (hDC, prc->right - cx, prc->bottom ); // / edge
SelectObject(hDC, hpenOriginal);
SetROP2(hDC, ropOriginal);
DeleteObject(hpenHeight);
DeleteObject(hpenWidth);
}
这解决了可访问性错误并使像素模式与DrawFocusRect
生成的匹配,但代价是创建额外的笔。 与第二次尝试一样,无论 DC 的前景或背景 colors 设置为什么,它都会按预期工作。
请注意,如果您一次创建所需的绘图对象并缓存它们,而不是每次都创建和销毁它们,那么DrawXorRect
的所有这些实现都会变得更加高效。 这肯定是 window 管理器提供DrawFocusRect
function 供应用程序使用的一个重要原因(连同便利性——谁愿意编写所有这些丑陋的代码?)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.