You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修改Win32绘图程序代码,使鼠标绘制线条滚动时保留?

Win32绘图程序滚动窗口线条异常的解决方案

你的问题核心在于没有持久化绘制的线条数据,且滚动时未正确处理坐标偏移。Win32窗口滚动时,客户区可见区域会变化,仅在鼠标事件中临时绘制的线条会因窗口重绘丢失或错位,必须结合以下几点修复:

关键修复步骤

  • 持久化存储所有绘制的线条(用结构体保存每条线的起点/终点,基于逻辑坐标而非客户区坐标)
  • 维护全局滚动偏移量,鼠标操作时将客户区坐标转换为不受滚动影响的逻辑坐标
  • 在WM_PAINT中根据滚动偏移调整绘图原点,重绘所有存储的线条
  • 正确处理滚动消息,更新滚动条位置并滚动窗口区域

示例代码实现

1. 全局变量定义

#include <windows.h>

typedef struct {
    POINT start;
    POINT end;
} Line;

#define MAX_LINES 1000
Line lines[MAX_LINES];
int lineCount = 0;
int cxScroll = 0, cyScroll = 0; // 水平/垂直滚动偏移量
const int CANVAS_WIDTH = 1200;   // 画布总宽度
const int CANVAS_HEIGHT = 1200;  // 画布总高度

2. 窗口过程关键消息处理

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_CREATE:
            // 设置滚动条范围
            SetScrollRange(hwnd, SB_HORZ, 0, CANVAS_WIDTH, FALSE);
            SetScrollRange(hwnd, SB_VERT, 0, CANVAS_HEIGHT, FALSE);
            break;

        case WM_LBUTTONDOWN:
            SetCapture(hwnd);
            // 客户区坐标转逻辑坐标(加上滚动偏移)
            lines[lineCount].start.x = LOWORD(lParam) + cxScroll;
            lines[lineCount].start.y = HIWORD(lParam) + cyScroll;
            break;

        case WM_MOUSEMOVE:
            if (GetCapture() == hwnd) {
                lines[lineCount].end.x = LOWORD(lParam) + cxScroll;
                lines[lineCount].end.y = HIWORD(lParam) + cyScroll;
                // 局部重绘,避免全窗口闪烁
                InvalidateRect(hwnd, NULL, FALSE);
            }
            break;

        case WM_LBUTTONUP:
            ReleaseCapture();
            if (lineCount < MAX_LINES - 1) {
                lineCount++;
            }
            break;

        case WM_HSCROLL:
        case WM_VSCROLL: {
            int nScrollCode = LOWORD(wParam);
            int nPos = HIWORD(wParam);
            RECT rcClient;
            GetClientRect(hwnd, &rcClient);
            int cxClient = rcClient.right - rcClient.left;
            int cyClient = rcClient.bottom - rcClient.top;

            if (uMsg == WM_HSCROLL) {
                switch (nScrollCode) {
                    case SB_LINELEFT: cxScroll -= 10; break;
                    case SB_LINERIGHT: cxScroll += 10; break;
                    case SB_PAGELEFT: cxScroll -= cxClient; break;
                    case SB_PAGERIGHT: cxScroll += cxClient; break;
                    case SB_THUMBPOSITION: cxScroll = nPos; break;
                }
                // 限制滚动范围,避免超出画布
                cxScroll = max(0, min(cxScroll, CANVAS_WIDTH - cxClient));
                SetScrollPos(hwnd, SB_HORZ, cxScroll, TRUE);
                ScrollWindowEx(hwnd, -((nScrollCode == SB_THUMBPOSITION) ? (cxScroll - nPos) : 10), 0, NULL, NULL, NULL, NULL, SW_INVALIDATE);
            } else {
                switch (nScrollCode) {
                    case SB_LINEUP: cyScroll -= 10; break;
                    case SB_LINEDOWN: cyScroll += 10; break;
                    case SB_PAGEUP: cyScroll -= cyClient; break;
                    case SB_PAGEDOWN: cyScroll += cyClient; break;
                    case SB_THUMBPOSITION: cyScroll = nPos; break;
                }
                cyScroll = max(0, min(cyScroll, CANVAS_HEIGHT - cyClient));
                SetScrollPos(hwnd, SB_VERT, cyScroll, TRUE);
                ScrollWindowEx(hwnd, 0, -((nScrollCode == SB_THUMBPOSITION) ? (cyScroll - nPos) : 10), NULL, NULL, NULL, NULL, SW_INVALIDATE);
            }
            UpdateWindow(hwnd);
            break;
        }

        case WM_PAINT: {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            // 设置视口原点,将逻辑坐标映射到当前可见的客户区
            SetViewportOrgEx(hdc, -cxScroll, -cyScroll, NULL);
            
            // 绘制所有存储的线条
            for (int i = 0; i < lineCount; i++) {
                MoveToEx(hdc, lines[i].start.x, lines[i].start.y, NULL);
                LineTo(hdc, lines[i].end.x, lines[i].end.y);
            }

            EndPaint(hwnd, &ps);
            break;
        }

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
    return 0;
}

核心原理说明

  • 持久化线条:所有绘制的线条都存在数组中,WM_PAINT时遍历重绘,避免滚动后丢失
  • 坐标转换:鼠标事件中把客户区坐标加上滚动偏移,得到逻辑坐标(画布上的绝对位置)
  • 视口调整:WM_PAINT中用SetViewportOrgEx将逻辑坐标映射到当前可见的客户区,确保线条位置正确
  • 高效滚动:用ScrollWindowEx滚动窗口区域,减少不必要的重绘,提升流畅度

内容的提问来源于stack exchange,提问作者shiroha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 11:23:18