Как мне изменить свой код, чтобы линии, нарисованные мышью, оставались на холсте окна во время прокрутки окна?

Я использую Win32 API для создания программы рисования, и когда я пытаюсь решить эту проблему с помощью мыши для рисования линий, остаются линии на холсте, это не работает должным образом.

Это функция процесса Win:

LRESULT MainWindow::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static int canvasWidth = 1200;              //初始的画布大小, initialize canvas width
    static int canvasHeight = 700;              //初始的画布大小, initialize canvas height
    static int workWidth = canvasWidth + 60;    //设定的工作区大小, set work area width
    static int workHeight = canvasHeight + 60;  //设定的工作区大小, set work area height
    static std::vector<Scroll*> scrolls;        //滚动条容器, Scroll container
    static RECT clientRC;                       //窗口客户区矩形, window client rectangle
    static HDC hMemDC;                          //内存设备上下文, memory device context
    static HBITMAP hMemBM;                      //内存位图, memory bitmap
    static POINT ptPreviouse;                   //鼠标上一次位置, previous mouse position
    static POINT ptCurrent;                     //鼠标当前位置, current mouse position
    static bool bDraw = false;                  //是否开始绘制, whether to start drawing
    static HDC hDC;                             //绘图设备上下文, drawing device context
    static RECT canvasRect;                     //画布区域, canvas area
    TRACKMOUSEEVENT mouseEvent;                 //鼠标追踪事件, mouse track event
    static int CanvasLTop_X = 0;                //画布的左上x坐标, canvas left top x
    static int CanvasLTop_Y = 0;                //画布的左上y坐标, canvas left top y
    static int OldCanvasLTop_X = 0;             //上一个画布的左上x坐标, previous canvas left top x
    static int OldCanvasLTop_Y = 0;             //上一个画布的左上y坐标, previous canvas left top y
    static int VscrollPos = 0;                  //垂直滚动条位置, vertical scroll bar position
    static int HscrollPos = 0;                  //水平滚动条位置, horizontal scroll bar position

    switch (message)
    {
    case WM_COMMAND:
    {
        // 分析菜单选择:
        //analysis menu selection
        switch (LOWORD(wParam))
        {
        case IDM_ABOUT:
            DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, MainWindow::About);
            break;
        case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
        case IDC_BUTTON1:
            // TODO: 在此处添加按钮事件处理代码...
            //TODO: add button event handling code here...

            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
    }
    break;

    case WM_CREATE:
    {
        // TODO: 在此处添加控件创建代码...
        //TODO: add control creation code here...
        GetClientRect(hWnd, &clientRC);
        hDC = GetDC(hWnd);

        //创建垂直滚动条
        //create vertical scroll bar
        Scroll* VerticalScroll = new Scroll(hWnd, SB_VERT, 0, workHeight, clientRC.bottom);
        scrolls.push_back(VerticalScroll);

        //创建水平滚动条
        //create horizontal scroll bar
        Scroll* HorizontalScroll = new Scroll(hWnd, SB_HORZ, 0, workWidth, clientRC.right);
        scrolls.push_back(HorizontalScroll);

        //创建内存设备上下文
        //create memory device context
        hMemDC = CreateCompatibleDC(hDC);
        hMemBM= CreateCompatibleBitmap(hDC, canvasWidth, canvasHeight);
        SelectObject(hMemDC, hMemBM);
        PatBlt(hMemDC, 0, 0, clientRC.right, clientRC.bottom, WHITENESS);

        ReleaseDC(hWnd, hDC);

    }

    break;

    case WM_DRAWITEM:
    {
        DRAWITEMSTRUCT* ptrDIS = (DRAWITEMSTRUCT*)lParam; // 转换为DRAWITEMSTRUCT结构, convert to DRAWITEMSTRUCT structure

        // TODO: 在此处添加绘制控件代码...
        //TODO: add control drawing code here...
    }
    break;

    case WM_SIZE:
    {
        // TODO: 在此处添加大小变化事件处理代码...
        //TODO: add size change event handling code here...

        hDC = GetDC(hWnd);

        //将内容保存至临时位图
        //copy content to temporary bitmap
        HDC tempDC = CreateCompatibleDC(hDC);
        HBITMAP tempBM = CreateCompatibleBitmap(hDC, clientRC.right, clientRC.bottom);
        SelectObject(tempDC, tempBM);
        BitBlt(tempDC, 0, 0, clientRC.right, clientRC.bottom, hMemDC, 0, 0, SRCCOPY);

        GetClientRect(hWnd, &clientRC);

        //更新滚动条信息
        //update scroll bar information
        for (Scroll* scroll : scrolls)
        {
            if (scroll->widgeType == SB_VERT)
            {
                SCROLLINFO newVSI = scroll->GetScrollStruct();
                newVSI.cbSize = sizeof(SCROLLINFO);
                newVSI.fMask = SIF_ALL;
                newVSI.nMax = workHeight;
                newVSI.nPage = clientRC.bottom;
                scroll->UpdateScrollInfo(hWnd, newVSI);
            }

            if (scroll->widgeType == SB_HORZ)
            {
                SCROLLINFO newHSI = scroll->GetScrollStruct();
                newHSI.cbSize = sizeof(SCROLLINFO);
                newHSI.fMask = SIF_ALL;
                newHSI.nMax = workWidth;
                newHSI.nPage = clientRC.right;
                scroll->UpdateScrollInfo(hWnd, newHSI);
            }

        }

        //计算画布的新位置
        //calculate new canvas position
        if (clientRC.right - 45 < canvasWidth) CanvasLTop_X = 30;
        else CanvasLTop_X = (clientRC.right - canvasWidth) / 2;

        if (clientRC.bottom - 45 < canvasHeight) CanvasLTop_Y = 30;
        else CanvasLTop_Y = (clientRC.bottom - canvasHeight) / 2;

        //将临时位图内容复制到内存设备上下文
        //copy temporary bitmap content to memory device context
        hMemDC= CreateCompatibleDC(hDC);
        hMemBM = CreateCompatibleBitmap(hDC, clientRC.right, clientRC.bottom);
        SelectObject(hMemDC, hMemBM);
        PatBlt(hMemDC, 0, 0, clientRC.right, clientRC.bottom, WHITENESS);
        BitBlt(hMemDC, CanvasLTop_X, CanvasLTop_Y, canvasWidth, canvasHeight, tempDC, OldCanvasLTop_X, OldCanvasLTop_Y, SRCCOPY);

        //更新画布坐标
        //update canvas coordinates
        OldCanvasLTop_X = CanvasLTop_X;
        OldCanvasLTop_Y = CanvasLTop_Y;

        DeleteObject(tempBM);
        DeleteDC(tempDC);

    }
    break;

    case WM_PAINT:
    {
        // TODO: 在此处添加使用 hdc 的任何绘图代码...
        //TODO: add any drawing code using hdc here...

        PAINTSTRUCT ps;
        hDC = BeginPaint(hWnd, &ps);
        GetClientRect(hWnd, &clientRC);

        //获取滚动条位置
        //get scroll bar position
        for (Scroll* scroll : scrolls)
        {
            if (scroll->widgeType == SB_VERT)
            {
                VscrollPos = scroll->GetScrollPos(hWnd);
            }
            if (scroll->widgeType == SB_HORZ)
            {
                HscrollPos = scroll->GetScrollPos(hWnd);
            }
        }

        //跟新画布区域
        //update canvas area
        canvasRect = { CanvasLTop_X - HscrollPos, CanvasLTop_Y - VscrollPos , CanvasLTop_X + canvasWidth - HscrollPos, CanvasLTop_Y + canvasHeight - VscrollPos };
        
        //将内存设备上下文内容复制到窗口设备上下文
        //copy memory device context content to drawing device context
        BitBlt(hDC, canvasRect.left, canvasRect.top, canvasWidth, canvasHeight, hMemDC, canvasRect.left, canvasRect.top, SRCCOPY);

        EndPaint(hWnd, &ps);
        ReleaseDC(hWnd, hDC);

    }
    break;

    case WM_VSCROLL:
    {
        // TODO: 在此处添加垂直滚动条事件处理代码...
        //TODO: add vertical scroll bar event handling code here...

        SCROLLINFO verticalSI;

        for (Scroll* scroll : scrolls)
        {
            if (scroll->widgeType == SB_VERT)
            {
                verticalSI = scroll->GetScrollStruct();
                int newPos = scroll->GetScrollPos(hWnd);
                switch (LOWORD(wParam))
                {
                case SB_LINEUP:
                    newPos -= 10;
                    break;
                case SB_LINEDOWN:
                    newPos += 10;
                    break;
                case SB_PAGEUP:
                    newPos -= verticalSI.nPage;
                    break;
                case SB_PAGEDOWN:
                    newPos += verticalSI.nPage;
                    break;
                case SB_THUMBTRACK:
                    newPos = verticalSI.nTrackPos;
                    break;
                }

                if (newPos < 0) newPos = 0;
                if (newPos > verticalSI.nMax - verticalSI.nPage) newPos = verticalSI.nMax - verticalSI.nPage;

                ScrollWindowEx(hWnd, 0, scroll->GetScrollPos(hWnd) - newPos, nullptr, nullptr, nullptr, nullptr, SW_INVALIDATE | SW_ERASE);
                scroll->UpdateScrollInfo(hWnd, newPos);
                UpdateWindow(hWnd);
                break;
            }
        }
    }
    break;

    case WM_HSCROLL:
    {
        // TODO: 在此处添加水平滚动条事件处理代码...
        //TODO: add horizontal scroll bar event handling code here...

        for (Scroll* scroll : scrolls)
        {
            if (scroll->widgeType == SB_HORZ)
            {
                SCROLLINFO horizontalSI = scroll->GetScrollStruct();
                int newPos = scroll->GetScrollPos(hWnd);
                switch (LOWORD(wParam))
                {
                case SB_LINEUP:
                    newPos -= 10;
                    break;
                case SB_LINEDOWN:
                    newPos += 10;
                    break;
                case SB_PAGEUP:
                    newPos -= horizontalSI.nPage;
                    break;
                case SB_PAGEDOWN:
                    newPos += horizontalSI.nPage;
                    break;
                case SB_THUMBTRACK:
                    newPos = horizontalSI.nTrackPos;
                    break;
                }

                if (newPos < 0) newPos = 0;
                if (newPos > horizontalSI.nMax - horizontalSI.nPage) newPos = horizontalSI.nMax - horizontalSI.nPage;

                ScrollWindowEx(hWnd, scroll->GetScrollPos(hWnd) - newPos, 0, nullptr, nullptr, nullptr, nullptr, SW_INVALIDATE | SW_ERASE);
                scroll->UpdateScrollInfo(hWnd, newPos);
                UpdateWindow(hWnd);
                break;
            }
        }
    }

    break;

    case WM_LBUTTONDOWN:
    {
        hDC = GetDC(hWnd);
        bDraw = true;
        ptPreviouse.x = LOWORD(lParam);
        ptPreviouse.y = HIWORD(lParam);
    }
    break;

    case WM_MOUSEMOVE:
    {
        if (bDraw)
        {
            MoveToEx(hMemDC, ptPreviouse.x, ptPreviouse.y, nullptr);
            LineTo(hMemDC, LOWORD(lParam), HIWORD(lParam));
            BitBlt(hDC, canvasRect.left, canvasRect.top, canvasWidth, canvasHeight, hMemDC, canvasRect.left, canvasRect.top,  SRCCOPY);
            ptPreviouse.x = LOWORD(lParam);
            ptPreviouse.y = HIWORD(lParam);

            mouseEvent.cbSize = sizeof(TRACKMOUSEEVENT);
            mouseEvent.dwFlags = TME_LEAVE;
            mouseEvent.hwndTrack = hWnd;
            TrackMouseEvent(&mouseEvent);
        }
    }
    break;

    case WM_LBUTTONUP:
    {
        bDraw = false;
        ReleaseDC(hWnd, hDC);
    }
    break;
    case WM_MOUSELEAVE:
    {
        bDraw = false;
        ReleaseDC(hWnd, hDC);
    }
    break;

    case WM_DESTROY:
    {
        //释放scrolls容器中的对象
        //release objects in scrolls container
        for (Scroll* scroll : scrolls)
        {
            delete scroll;
        }
        scrolls.clear();
        PostQuitMessage(0);
    }
    break;


    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }

    return 0;
}

Эффект:

Первый розыгрыш

Как мне изменить свой код, чтобы линии, нарисованные мышью, оставались на холсте окна во время прокрутки окна?

Прокрутите окно вниз:

Как мне изменить свой код, чтобы линии, нарисованные мышью, оставались на холсте окна во время прокрутки окна?

Прокрутите назад:

Как мне изменить свой код, чтобы линии, нарисованные мышью, оставались на холсте окна во время прокрутки окна?

Второй розыгрыш

Как мне изменить свой код, чтобы линии, нарисованные мышью, оставались на холсте окна во время прокрутки окна?

К слову: ваш WM_SIZE пропускает старые hMemDC и hMemBM объекты при изменении размера окна. Необходимо освободить их. Кроме того, CreateCompatibleDC() изначально создает HDC с растровым изображением 1x1, которое вы также теряете после замены его на SelectObject(). Вам нужно либо освободить это исходное растровое изображение, либо сохранить его и восстановить перед освобождением hMemDC.

Remy Lebeau 25.07.2024 21:44
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
72
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Не рисуйте прямо в окне за пределами обработчика сообщений WM_PAINT. Все, что вы нарисуете, будет стерто при следующей перекраске окна.

У вас есть несколько вариантов:

  • Пусть ваш обработчик мыши сохранит координаты где-нибудь в списке, а затем обработчик WM_PAINT (пере) нарисует все строки, находящиеся в данный момент в списке.

  • Создайте в памяти растровое изображение того же размера, что и ваше окно, и обработчик мыши будет рисовать линии на этом растровом изображении по мере необходимости. Всякий раз, когда растровое изображение меняется, вызывайте InvalidateRect(), чтобы вызвать перерисовку окна. Затем обработчик WM_PAINT нарисует текущее растровое изображение как есть в окне.

Похоже, вы пытаетесь использовать второй подход, за исключением того, что ваш обработчик WM_MOUSEMOVE сразу рисует растровое изображение в окне, а не ждет прибытия следующего WM_PAINT сообщения.

Попробуйте еще что-нибудь вроде этого:

RESULT MainWindow::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    ...

    switch (message)
    {
    ...

    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        hDC = BeginPaint(hWnd, &ps);
        ...
        BitBlt(hDC, ..., hMemDC, ...);
        ...
        EndPaint(hWnd, &ps);
    }
    break;

    ...

    case WM_LBUTTONDOWN:
    {
        bDraw = true;
        ptPreviouse.x = GET_X_LPARAM(lParam);
        ptPreviouse.y = GET_Y_LPARAM(lParam);
    }
    break;

    case WM_MOUSEMOVE:
    {
        if (bDraw)
        {
            MoveToEx(hMemDC, ptPreviouse.x, ptPreviouse.y, nullptr);
            LineTo(hMemDC, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
            ptPreviouse.x = GET_X_LPARAM(lParam);
            ptPreviouse.y = GET_Y_LPARAM(lParam);
            InvalidateRect(hWnd, nullptr, TRUE);
            ...
        }
    }
    break;

    case WM_LBUTTONUP:
    case WM_MOUSELEAVE:
    {
        bDraw = false;
    }
    break;

    ...
    }

    return 0;
}

Большое спасибо за ваш ответ. У меня вопрос: если размер окна изменится, размер HBITMAP "hMemBM" необходимо обновить. Итак: ``` case WM_SIZE: { .... hMemBM = CreateCompatibleBitmap(hDC, clientRC.right, clientRC.bottom); ..... } ``` Содержимое hMemBM будет очищено. Итак, следует ли мне создавать временное растровое изображение во время записи моего кода, чтобы временно сохранить содержимое необновленного hMemBM?

shiroha 26.07.2024 11:30

Да, вам нужно создать новое растровое изображение памяти и постоянный ток памяти и скопировать старые пиксели в новое растровое изображение. Вы делаете это, но вам также придется освободить старых, чего вы не делаете.

Remy Lebeau 26.07.2024 16:41

Я понимаю. Еще раз спасибо

shiroha 26.07.2024 17:09

Другие вопросы по теме