繁体   English   中英

为什么 DrawFocusRect 受文本颜色影响?

[英]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 通过代码,一步一步,看看它做了什么,它是如何工作的,以及它意味着什么:

  1. 首先,它创建一个单色8×8 bitmap ( CreateBitmap() ),由交替的位模式 ( pattern[] ) 组成:开、关、开、关等。(如果你不知道为什么,请打开Windows 计算器,将其切换到十六进制模式,然后粘贴构成模式的十六进制值。看看这些位:看看它们如何在 0 和 1 之间交替?现在,想象一下从中创建一个 bitmap。)

  2. 然后,它使用 bitmap 创建画笔 ( CreatePatternBrush() )。

  3. 接下来,它将新创建的画笔选择到 DC ( SelectObject() ) 中。 它保存了返回值,也就是原来在DC中选中的画笔,因为后面需要恢复。

  4. 创建所有绘图对象后,它调用SystemParametersInfo()来检索焦点矩形的宽度 ( SPI_GETFOCUSBORDER_WIDTH ) 和高度 ( SPI_GETFOCUSBORDERHEIGHT )。

  5. 配备了绘图所需的所有对象和信息,它使用PATINVERT光栅操作对矩形的 4 个边中的每一个进行 blit。 什么是PATINVERT

    使用 Boolean 异或运算符将指定模式的 colors 与目标矩形的 colors 组合起来。

  6. 最后,它通过恢复最初选择的画笔并删除它创建的绘图对象来自行清理。

因此,基本上,焦点矩形是通过将开关像素的交替模式(棋盘模式)与设备上下文的内容(即屏幕上的任何内容)进行异或来绘制的。 这就是PATINVERT所做的。 但是等等——我们知道设备上下文的 colors 是什么,但是刷子的 colors 是什么? 好吧,请记住画笔是从单色 bitmap 创建的。CreateBitmap 的CreateBitmap文档说:

如果 bitmap 是单色的,则 0 表示前景色,1 表示目标设备上下文的背景色。

啊哈,所以当画笔的 bitmap 模式为 0 时,画笔的颜色是设备上下文的前景色( SetTextColor ); 当画笔的 bitmap 模式为 1 时,画笔的颜色是设备上下文的背景颜色 ( SetBackColor )。 这就是设备上下文的前景色和背景色发挥作用的方式。 这些 colors 通过异或运算与设备上下文中原始像素的颜色合并在一起。

使用DrawFocusRect时,您的 DC 的前景和背景 colors 应分别为黑色和白色。 这是设备上下文的默认前景和背景 colors,因此如果您没有调用SetTextColorSetBackColor函数,这些将是它的 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM