首页 > 解决方案 > 如何在 Win32 应用程序中拖动纯色矩形而不会出现白色闪烁且不会干扰屏幕上的其他对象?

问题描述

我一直在玩弄一个小玩具 Win32 应用程序来尝试掌握基本绘图和处理鼠标和键盘消息的窍门。基本上,目标是在客户区域周围单击以放置矩形,按 alt 单击它们以在 4 种颜色之间循环,右键单击它们以直接使用 f 键之一重新分配颜色,或者右键单击并按住以通过拖放重新定位,当前选定的矩形具有虚线轮廓而不是实线。

事实证明,正确拖动是很困难的。拖动时,被拖动的选定矩形闪烁白色/闪烁,并暂时擦除其他矩形的笔绘制边框。克服这两个问题是我需要帮助的。

一个 c++ 向量用于收集和处理动态分配的 CRect 对象,以防万一。


    //relevant code from WndProc

        case WM_RBUTTONDOWN:
        {
            ClipCursor(&rcClip);
            if (prcSelected)
            {
                prcSelected->deselect();
                prcSelected = nullptr;
                InvalidateRect(hWnd, NULL, TRUE);
            }
            int x{ LOWORD(lParam) }, y{ HIWORD(lParam) };
            for (auto rc : vRect)
            {
                if (rc->IsClicked(x, y))
                {
                    rc->select();
                    prcSelected = rc;
                    InvalidateRect(hWnd, NULL, TRUE);
                    break;
                }
            }
        }
            break;

        case WM_RBUTTONUP:
            ClipCursor(&rcOldClip);
            InvalidateRect(hWnd, NULL, TRUE);
            break;

        case WM_MOUSEMOVE:
        {
            if (wParam & MK_RBUTTON && prcSelected)
            {
                CRect rcPrev{ *prcSelected };
                CRect crIsect{};
                int x{ LOWORD(lParam) }, y{ HIWORD(lParam) };
                int xShift{ x - prcSelected->r.left }, yShift{ y - prcSelected->r.top };
                prcSelected->shift(xShift, yShift);     
                rcPrev.SetFill(CR_WHITE);
                rcPrev.SetOutline(CR_WHITE);
                rcPrev.draw();

                for (auto rc : vRect)
                    if (IntersectRect(&crIsect.r, &(rc->r), &(rcPrev.r)))
                    {
                        crIsect.SetFill(rc->GetFill());
                        crIsect.SetOutline(rc->GetFill());
                        crIsect.draw();
                    }

                prcSelected->draw();
            }
        }
            break;

        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);

            for (auto rc : vRect)
                rc->draw(hdc);

            EndPaint(hWnd, &ps);
        }
            break;


    //Rectangle drawing code

    void CRect::draw()
    {
        HDC hdc{ GetDC(hWnd) };
        HPEN hpenDot;
        SelectObject(hdc, GetStockObject(DC_BRUSH));
        SetDCBrushColor(hdc, crBrush);
        if (!fSelected)
        {
            SelectObject(hdc, GetStockObject(DC_PEN)); // Can be set to any color. No need to release.
            SetDCPenColor(hdc, crPen);
            Rectangle(hdc, r.left, r.top, r.right, r.bottom);
        }
        else
        {
            hpenDot = CreatePen(PS_DOT, 1, crPen);
            SelectObject(hdc, hpenDot);
            Rectangle(hdc, r.left, r.top, r.right, r.bottom);
            DeleteObject(hpenDot);
        }
        ReleaseDC(hWnd, hdc);
    }

    void CRect::draw(HDC& hdc)
    {
        HPEN hpenDot;
        SelectObject(hdc, GetStockObject(DC_BRUSH));
        SetDCBrushColor(hdc, crBrush);
        if (!fSelected)
        {
            SelectObject(hdc, GetStockObject(DC_PEN)); 
            SetDCPenColor(hdc, crPen);
            Rectangle(hdc, r.left, r.top, r.right, r.bottom);
        }
        else
        {
            hpenDot = CreatePen(PS_DOT, 1, crPen);
            SelectObject(hdc, hpenDot);
            Rectangle(hdc, r.left, r.top, r.right, r.bottom);
            DeleteObject(hpenDot);
        }
    }


    void CRect::shift(int x, int y)
    {
        r.left   += x;
        r.top    += y;
        r.right  += x;
        r.bottom += y;
    }

    bool CRect::IsClicked(int x, int y)
    {
        POINT pt{ x, y };
        return (bool)PtInRect(&r, pt);

    }


标签: c++cwinapidraggdi

解决方案


TL;DR:您应该只在 WM_PAINT 处理程序中进行绘画。

而不是直接在 WM_MOUSEMOVE 处理程序中的屏幕表面上绘画,您应该执行以下操作:

  1. WM_MOUSEMOVE只需调用以InvalidateRect(,prcSelected) 使拖动矩形占用的区域无效。

  2. 在你的WM_PAINT处理程序中

    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
    
        // clear surface
        FillRect(hdc, ps.rcPaint, COLORREF(0xFFFFFF));
    
        for (auto rc : vRect)
            rc->draw(hdc);
    
        if(prcSelected)
          DrawDragRectangle(...); // drawing code from your WM_MOUSEMOVE
    
        EndPaint(hWnd, &ps);
    }
    

这样,您将仅以正确的顺序在 WM_PAINT 中进行绘图。

即使在这种情况下,您可能仍然有闪烁。如果是这样,通过向窗口添加WS_EX_COMPOSITED标志来 启用双缓冲。


推荐阅读