【问题标题】:Dialog box improperly repaints its background对话框不正确地重新绘制其背景
【发布时间】:2014-01-14 15:26:01
【问题描述】:

我有一个对话框,应该有一个自定义图像作为背景。

我没有将整个图像作为位图或任何其他格式,所以我必须从头开始绘制。

我用了一个icon(截图左下角的男性),一个EMF文件(下图截图),其余的由一个绿色渐变笔刷,浅灰色阴影笔刷组成, 和文本 - 均使用GDI 绘制。我的绘图结果如下所示(屏幕截图也包括控件):

当我将对话框向左移动,然后将其移回原来的位置时,我面临的问题就显现出来了。下一个屏幕截图的图片底部说明了正在发生的伪影:

对话框是无模式的,我在WM_CTLCOLORDIALOG中绘制整个图像,然后返回NULL_BRUSH

这里是WM_CTLCOLORDIALOG处理程序的相关代码sn-p(注意我是直接在DC上画的,没有双缓冲。原因是这个比较快测试代码很快就出错了):

case WM_CTLCOLORDLG:
    {   
        RECT rect; // dialog's client rectangle

        GetClientRect( hwnd, &rect );

        // ligh gray brush for hatched brush

        HBRUSH hbPozadina = CreateSolidBrush( RGB( 242, 242, 242 ) );

        FillRect( (HDC)wParam, &rect, hbPozadina );

        // cleanup

        DeleteObject( hbPozadina );

        // draw grid "manualy"

        LOGBRUSH lbPozadina;

        HGDIOBJ hPenPozadina = NULL, hOldPenPozadina;

        lbPozadina.lbColor = RGB( 255, 255, 255 );
        lbPozadina.lbHatch = 0;
        lbPozadina.lbStyle = BS_SOLID;

        hPenPozadina = ExtCreatePen( PS_COSMETIC | PS_SOLID, 1, &lbPozadina, 0, NULL); 

        hOldPenPozadina = SelectObject((HDC)wParam, hPenPozadina);

        // draw vertical lines

        for( int i = rect.left + 12; i< rect.right; i += 12)
        {
            MoveToEx((HDC)wParam, i, rect.top, NULL );

            LineTo((HDC)wParam, i, rect.bottom - rect.top + 1 );
        }

        // draw horizontal lines

        for( int i = rect.top + 12; i< rect.bottom; i += 12)
        {
            MoveToEx((HDC)wParam, rect.left, i, NULL );

            LineTo((HDC)wParam, rect.right - rect.left + 1, i );
        }

        //clean up

        SelectObject((HDC)wParam, hOldPenPozadina);

        DeleteObject(hPenPozadina);

        // draw metafile of the map

        HENHMETAFILE hemf = GetEnhMetaFile( L".\\resources\\KartaDlg.emf" );
        ENHMETAHEADER emh; 
        GetEnhMetaFileHeader( hemf, sizeof(emh), &emh ); 

        // remove the "status bar" from the calculation

        RECT r;

        r.top = rect.top;
        r.bottom = rect.bottom - 30;
        r.left = rect.left;
        r.right = rect.right;

        // calculate rescaled metafile

        UINT o_height = emh.rclFrame.bottom - emh.rclFrame.top, 
            o_width =  emh.rclFrame.right - emh.rclFrame.left;

        float scale = 0.5;

        scale = (float)( r.right - r.left ) / o_width;

        if( (float)( r.bottom - r.top ) / o_height  <  scale )
            scale = (float)( r.bottom - r.top ) / o_height;

        int marginX = ( r.right - r.left ) - (int)( o_width * scale );
        int marginY = ( r.bottom - r.top ) - (int)( o_height * scale );

        marginX /= 2;
        marginY /= 2;

        r.left = r.left + marginX;
        r.right = r.right - marginX;
        r.top = r.top + marginY;
        r.bottom = r.bottom - marginY;

        // Draw the picture.  

        PlayEnhMetaFile( (HDC)wParam, hemf, &r ); 

        // Release the metafile handle.  

        DeleteEnhMetaFile(hemf); 

        // this function draws green gradient and icon

        drawFooter( (HDC)wParam, rect, 
            RGB( 0x48, 0xAC, 0xC6), RGB( 0x31, 0x83, 0x99 ) ); 

        //========= draw right text in status bar =============//

        SetBkMode( (HDC)wParam, TRANSPARENT );

        SIZE sBaner; // needed for proper positioning

        HFONT hf, hfOld;

        long lfHeight;

        lfHeight = -MulDiv( 8, GetDeviceCaps( (HDC)wParam, LOGPIXELSY), 72 );

        hf = CreateFont( lfHeight, 0, 0, 0, FW_BOLD, TRUE, 
            0, 0, 0, 0, 0, 0, 0, L"Arial Black" );

        hfOld = (HFONT)SelectObject( (HDC)wParam, hf ); // needed for proper cleanup

        GetTextExtentPoint32( (HDC)wParam, 
            L"ЦЕНТАР ЗА ОБНОВЉИВЕ ВОДНЕ ЕНЕРГЕТСКЕ РЕСУРСЕ",
            wcslen(L"ЦЕНТАР ЗА ОБНОВЉИВЕ ВОДНЕ ЕНЕРГЕТСКЕ РЕСУРСЕ"), 
            &sBaner );

        // position it properly

        r.bottom = rect.bottom;
        r.right = rect.left + sBaner.cx + 30;
        r.left = rect.left + 30;
        r.top = rect.bottom - rect.top - 30;

        // draw it 

        DrawTextEx( (HDC)wParam, 
            L"РУДАРСКО ГЕОЛОШКИ ФАКУЛТЕТ\nЦЕНТАР ЗА ОБНОВЉИВЕ ВОДНЕ ЕНЕРГЕТСКЕ РЕСУРСЕ",
            wcslen(L"РУДАРСКО ГЕОЛОШКИ ФАКУЛТЕТ\nЦЕНТАР ЗА ОБНОВЉИВЕ ВОДНЕ ЕНЕРГЕТСКЕ РЕСУРСЕ"), 
            &r, DT_CENTER | DT_VCENTER | DT_NOCLIP | DT_WORDBREAK, 0 );

        SelectObject( (HDC)wParam, hfOld ); // proper cleanup

        DeleteObject( hf );

        //============== right text in the status bar ==================//

        lfHeight = -MulDiv( 10, GetDeviceCaps( (HDC)wParam, LOGPIXELSY), 72 );

        hf = CreateFont( lfHeight, 0, 0, 0, FW_BOLD, TRUE, 
            0, 0, 0, 0, 0, 0, 0, L"Arial" );

        hfOld = (HFONT)SelectObject( (HDC)wParam, hf ); // needed for proper cleanup

        GetTextExtentPoint32( (HDC)wParam, 
            L" Дејан Миленић & Ана Врањеш © 2013 сва права задржана",
            wcslen(L" Дејан Миленић & Ана Врањеш © 2013 сва права задржана"), 
            &sBaner );

        // position it properly

        r.bottom = rect.bottom;
        r.right = rect.right - 10;
        r.left = rect.right - rect.left - sBaner.cx - 10;
        r.top = rect.bottom - rect.top - sBaner.cy;

        // draw it 

        DrawTextEx( (HDC)wParam, 
            L" Дејан Миленић & Ана Врањеш © 2013 сва права задржана",
            wcslen(L" Дејан Миленић & Ана Врањеш © 2013 сва права задржана"), 
            &r, DT_CENTER | DT_VCENTER | DT_NOCLIP | DT_WORDBREAK | DT_NOPREFIX, 0 );

        // perform proper cleanup

        SelectObject( (HDC)wParam, hfOld );

        DeleteObject(hf);
    }
    return (INT_PTR)GetStockObject(NULL_BRUSH);

为了更完整,我提交了drawFooter 函数及其辅助函数:

// Fills triangle with gradient brush

void GradientTriangle( HDC MemDC, 
    LONG x1, LONG y1, LONG x2, LONG y2, LONG x3, LONG y3, 
    COLORREF top, COLORREF bottom )
{
    TRIVERTEX vertex[3];

    vertex[0].x     = x1;
    vertex[0].y     = y1;
    vertex[0].Red   = GetRValue(bottom) << 8;
    vertex[0].Green = GetGValue(bottom) << 8;
    vertex[0].Blue  = GetBValue(bottom) << 8;
    vertex[0].Alpha = 0x0000;

    vertex[1].x     = x2;
    vertex[1].y     = y2;
    vertex[1].Red   = GetRValue(top) << 8;
    vertex[1].Green = GetGValue(top) << 8;
    vertex[1].Blue  = GetBValue(top) << 8;
    vertex[1].Alpha = 0x0000;

    vertex[2].x     = x3;
    vertex[2].y     = y3; 
    vertex[2].Red   = GetRValue(bottom) << 8;
    vertex[2].Green = GetGValue(bottom) << 8;
    vertex[2].Blue  = GetBValue(bottom) << 8;
    vertex[2].Alpha = 0x0000;

    // Create a GRADIENT_TRIANGLE structure that
    // references the TRIVERTEX vertices.

    GRADIENT_TRIANGLE gTriangle;

    gTriangle.Vertex1 = 0;
    gTriangle.Vertex2 = 1;
    gTriangle.Vertex3 = 2;

    // Draw a shaded triangle.

    GradientFill( MemDC, vertex, 3, &gTriangle, 1, GRADIENT_FILL_TRIANGLE);
}

// draw the window's footer ( "status bar" )

void drawFooter( HDC MemDC, RECT r, COLORREF top, COLORREF bottom )
{
    // bottom triangle

    GradientTriangle( MemDC, 
        r.right, r.bottom, 
        ( r.right - r.left ) / 2,
        r.bottom - r.top - 15,
        r.left, 
        r.bottom, 
        top, bottom );

    // upper triangle

    GradientTriangle( MemDC, 
        r.right, r.bottom - r.top - 30, 
        ( r.right - r.left ) / 2, 
        r.bottom - r.top - 15,
        r.left, 
        r.bottom - r.top - 30, 
        top, bottom );

    // left triangle

    GradientTriangle( MemDC, 
        r.left, r.bottom, 
        ( r.right - r.left ) / 2, 
        r.bottom - r.top - 15,
        r.left, 
        r.bottom - r.top - 30, 
        top, bottom );

    // right triangle

    GradientTriangle( MemDC, 
        r.right, 
        r.bottom - r.top - 30,
        ( r.right - r.left ) / 2,
        r.bottom - r.top - 15, 
        r.right, 
        r.bottom, 
        top, bottom );

    // draw icon

    DrawIconEx( MemDC, r.left, r.bottom - r.top - 30, 
        hiAdmin, // a global variable for icon
        30, 30, NULL, NULL, DI_NORMAL );
}

视觉样式已启用 - 这可能很重要,我不知道。

我没有处理WM_ERASEBKGNDWM_SIZEWM_MOVE对话框无法调整大小。)。我已经尝试过,但没有帮助(为WM_ERASEBKGND 返回TRUE,为WM_SIZEWM_MOVE 返回InvalidateRect)。我在互联网上找不到任何可以帮助我的东西。

问题:如何更改我的代码以修复我面临的错误?

【问题讨论】:

  • 为什么在 WM_CTLCOLORDLG 而不是 WM_PAINT 中绘制对话框?
  • 地图有任何功能用途吗?你会在上面画更多的东西吗?如果不是,我建议不要花时间在不相关的东西上,并完全删除图像和背景图案。
  • @MarkRansom:我是初学者,不知道如何正确实现它。
  • @Dialectus:我的客户强制使用这种类型的背景——在我看来这只是一种“不必要的炫耀”,但我能做些什么......
  • 只是查看了错误的图像,我怀疑您发送渐变填充坐标的方式存在错误。我可以想象,如果你得到的坐标顺序错误或计数错误,你最终可能会得到几个像你在那里看到的颜色的三角形。我会专注于那里并观察调试器中发生的情况。

标签: c++ winapi gdi


【解决方案1】:

您正在滥用WM_CTLCOLORDLG 消息。它旨在提供一种简单的方法来更改对话框的背景颜色,而不是自定义绘制它。

你应该在那里返回NULL_BRUSH,甚至完全忽略这条消息,然后在WM_ERASEBKGND中画你的背景。

或者甚至更好,您可以忽略 WM_ERASEBKGND 并在 WM_PAINT 中进行绘画,就像任何其他窗口一样。

更新:在下面几个 cmets 之后,问题似乎是调用 GradientFill() 三角形中的顶点顺序。那就是:

  • 顶点按顺时针方向列出:绘制三角形。
  • 顶点按逆时针方向列出:未绘制三角形。

或者也许是相反的方式,我永远无法告诉......

无论如何,为什么有时它不管顺序如何都有效,而为什么有时它只适用于特定顺序仍然是个谜。此外,这是否记录在任何地方?

我猜这可能是驱动程序/2D 加速问题...所以这将取决于 DC 是在显示器上还是在内存上,但很难说。

【讨论】:

  • 我确实尝试过,但失败了。我会再试一次并报告我的结果。谢谢你。最好的问候。
  • 好的,把case WM_CTLCOLORDIALOG改成case WM_ERASEBKGND,把return (INT_PTR)GetStockObject(NULL_BRUSH);改成return TRUE,效果一样。我试图从WM_ERASEBKGND 返回TRUE 并在WM_PAINT 中绘制图片。 HDC 是通过调用BeginPaint 获得的,绘画代码相同,只是(HDC)wParamBeginPaint 的DC 替换-剩下的。有什么想法吗?谢谢你。最好的问候。
  • 一个疯狂的猜测:EMF 正在改变 DC 中的某些东西,使您的三角形无法工作。尝试在PlayEnhMetaFile() 之前和RestoreDC() 之后调用SaveDC()。另外,检查GradientFill 的返回值是否有错误。
  • 我已经注释掉了绘制 EMF 的部分,但问题仍然存在。我已经注释掉了页脚的绘制,并用股票画笔(LTGRAY_BRUSH)绘制了那个矩形。问题似乎消失了。我的函数drawFooter 调用GradientFill API,如上所述。也许我传递了不正确的顶点值?我将尝试对此进行试验。
  • @AlwaysLearningNewStuff:现在我在猜测三角形的缠绕顺序。尝试将Vertex{1,2,3} 的顺序从0, 1, 2 更改为0, 2, 1
【解决方案2】:

您正在处理错误的消息。

你不应该在WM_CTLCOLORDLG 消息上画任何东西。只需将您的绘画代码移至WM_ERASEBKGND 处理程序即可。

【讨论】:

  • 我会尝试并报告我的结果。谢谢你。最好的问候。
  • OK,把case WM_CTLCOLORDIALOG改成case WM_ERASEBKGND,把return (INT_PTR)GetStockObject(NULL_BRUSH);改成return TRUE,效果一样。所有其他代码保持不变。有什么想法吗?不管怎么说,还是要谢谢你。最好的问候。
【解决方案3】:

好的。您的下一个问题是您正在重新绘制整个窗口。请注意,确实需要更新的窗口左侧部分已绘制好。问题是窗口的主要(右侧)部分,从左侧移动窗口后实际上不需要更新。

WM_ERASEBKGND 处理程序中,您可以调用GetUpdateRect() 来获取需要更新的矩形。

如果您不想过多更改代码,您至少可以调用例如IntersectClipRect() API 在绘制任何东西之前。这会将您的所有绘画剪辑到该矩形。

【讨论】:

  • 我会查看MSDN 的文档,看看我能做什么。同时,这可能很重要:我已经注释掉了页脚的绘制,并用股票画笔(LTGRAY_BRUSH)绘制了那个矩形。问题似乎消失了。我的函数drawFooter 调用GradientFill API,如上所述。也许我传递了不正确的顶点值?我将尝试对此进行试验。
猜你喜欢
  • 1970-01-01
  • 2011-09-07
  • 1970-01-01
  • 1970-01-01
  • 2023-03-31
  • 2013-02-13
  • 2023-01-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多