使用ScrollWindowEx()实现GUI滚动视图时内容显示异常的问题
问题:自定义垂直滚动视图显示异常
创建了一个宽700、高600的窗口,使用ScrollWindowEx()处理WM_MOUSEWHEEL消息实现垂直滚动,绘制了一个高1000的矩形,但滚动时始终无法看到矩形底部,显示内容异常。
原代码
#include <stdio.h> #include <windows.h> #include <gdiplus.h> int SCREEN_SIZE_WIDTH, SCREEN_SIZE_HEIGHT; static unsigned long long GDI_PLUS_TOKEN; static GdiplusStartupInput GDI_PLUS_STARTUP_INPUT = { .GdiplusVersion = 1, .SuppressExternalCodecs = FALSE, .SuppressBackgroundThread = FALSE }; static WNDCLASSEXW WINDOW_CLASS_DEFAULT = (WNDCLASSEXW) { sizeof(WNDCLASSEXW), CS_HREDRAW|CS_VREDRAW|CS_CLASSDC, NULL, 0, 16, NULL, NULL, NULL, NULL, NULL, L"MY_CLASS", NULL }; LRESULT CALLBACK window_callback(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) { HWND window; if((SCREEN_SIZE_WIDTH=GetSystemMetrics(SM_CXSCREEN))==0 || (SCREEN_SIZE_HEIGHT=GetSystemMetrics(SM_CYSCREEN))==0) { printf("GetSystemMetrics() failed.\r\n"); ExitProcess(EXIT_FAILURE); } if(GdiplusStartup(&GDI_PLUS_TOKEN, &GDI_PLUS_STARTUP_INPUT, NULL)!=Ok) { printf("GdiplusStartup() failed.\r\n"); ExitProcess(EXIT_FAILURE); } WINDOW_CLASS_DEFAULT.hInstance=hInstance; WINDOW_CLASS_DEFAULT.hbrBackground= CreateSolidBrush(RGB(255,255,255)); WINDOW_CLASS_DEFAULT.hCursor=LoadCursor(NULL, IDC_ARROW); WINDOW_CLASS_DEFAULT.lpfnWndProc=window_callback; if(!RegisterClassExW(&WINDOW_CLASS_DEFAULT)) { printf("RegisterClassExW() failed.\r\n"); ExitProcess(EXIT_FAILURE); } window=CreateWindowExW( 0, L"MY_CLASS", L"MY PROJECT", WS_POPUP|WS_OVERLAPPEDWINDOW, ((SCREEN_SIZE_WIDTH - 700) / 2), // Center of the screen ((SCREEN_SIZE_HEIGHT - 400) / 2), // Center of the screen 700, 600, NULL, NULL, hInstance, NULL); if(window==NULL) { printf("CreateWindowExA() failed.\r\n"); goto failure; } ShowWindow(window, SW_NORMAL); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } UnregisterClassA("DRAWPEN", hInstance); ExitProcess(EXIT_SUCCESS); failure: UnregisterClassA("DRAWPEN", hInstance); ExitProcess(EXIT_FAILURE); } LRESULT CALLBACK window_callback(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; static int scroll_value = 0; switch (uMsg) { case WM_MOUSEWHEEL: { // On scroll wheel, let's use ScrollWindowEx() function // to scroll vertical only and here is my problem ! ScrollWindowEx( hwnd, 0, GET_WHEEL_DELTA_WPARAM(wParam), NULL, NULL, NULL, NULL, SW_INVALIDATE); UpdateWindow(hwnd); } break; case WM_PAINT: printf("WM_PAINT\r\n"); hdc=BeginPaint(hwnd, &ps); GpGraphics *gfx; GdipCreateFromHDC(hdc, &gfx); GpPen *pen; GdipCreatePen1(0xFF4c4c4c, 15, UnitPixel, &pen); GdipDrawRectangle(gfx, pen, 20, 20, 100, 1000); EndPaint(hwnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProcW(hwnd, uMsg, wParam, lParam); } return 0; }
问题截图
程序启动时

向下滚动时(无法看到矩形底部)

向上滚动时

问题分析与修复
核心问题点
- 未维护滚动偏移量:代码中定义了
scroll_value但未使用,绘制时没有根据滚动位置偏移内容坐标,导致滚动逻辑和绘制脱节。 - ScrollWindowEx参数错误:直接使用鼠标滚轮的原始delta值(±120)作为滚动步长,步长过大且方向不符合用户预期;同时未限制滚动范围,无法滚动到内容底部。
- 资源泄漏:GDI+创建的
GpGraphics和GpPen未释放,会导致资源占用。 - 类名不匹配:
UnregisterClassA使用了错误的类名DRAWPEN,但实际注册的是MY_CLASS。
修复后的代码
#include <stdio.h> #include <windows.h> #include <gdiplus.h> int SCREEN_SIZE_WIDTH, SCREEN_SIZE_HEIGHT; static unsigned long long GDI_PLUS_TOKEN; static GdiplusStartupInput GDI_PLUS_STARTUP_INPUT = { .GdiplusVersion = 1, .SuppressExternalCodecs = FALSE, .SuppressBackgroundThread = FALSE }; static WNDCLASSEXW WINDOW_CLASS_DEFAULT = (WNDCLASSEXW) { sizeof(WNDCLASSEXW), CS_HREDRAW|CS_VREDRAW|CS_CLASSDC, NULL, 0, 16, NULL, NULL, NULL, NULL, NULL, L"MY_CLASS", NULL }; LRESULT CALLBACK window_callback(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) { HWND window; if((SCREEN_SIZE_WIDTH=GetSystemMetrics(SM_CXSCREEN))==0 || (SCREEN_SIZE_HEIGHT=GetSystemMetrics(SM_CYSCREEN))==0) { printf("GetSystemMetrics() failed.\r\n"); ExitProcess(EXIT_FAILURE); } if(GdiplusStartup(&GDI_PLUS_TOKEN, &GDI_PLUS_STARTUP_INPUT, NULL)!=Ok) { printf("GdiplusStartup() failed.\r\n"); ExitProcess(EXIT_FAILURE); } WINDOW_CLASS_DEFAULT.hInstance=hInstance; WINDOW_CLASS_DEFAULT.hbrBackground= CreateSolidBrush(RGB(255,255,255)); WINDOW_CLASS_DEFAULT.hCursor=LoadCursor(NULL, IDC_ARROW); WINDOW_CLASS_DEFAULT.lpfnWndProc=window_callback; if(!RegisterClassExW(&WINDOW_CLASS_DEFAULT)) { printf("RegisterClassExW() failed.\r\n"); ExitProcess(EXIT_FAILURE); } window=CreateWindowExW( 0, L"MY_CLASS", L"MY PROJECT", WS_POPUP|WS_OVERLAPPEDWINDOW, ((SCREEN_SIZE_WIDTH - 700) / 2), // Center of the screen ((SCREEN_SIZE_HEIGHT - 600) / 2), // 修正窗口垂直居中计算 700, 600, NULL, NULL, hInstance, NULL); if(window==NULL) { printf("CreateWindowExW() failed.\r\n"); // 修正函数名打印错误 goto failure; } ShowWindow(window, SW_NORMAL); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } GdiplusShutdown(GDI_PLUS_TOKEN); // 关闭GDI+ UnregisterClassW(L"MY_CLASS", hInstance); // 修正类名和宽字符版本 ExitProcess(EXIT_SUCCESS); failure: GdiplusShutdown(GDI_PLUS_TOKEN); // 关闭GDI+ UnregisterClassW(L"MY_CLASS", hInstance); // 修正类名和宽字符版本 ExitProcess(EXIT_FAILURE); } LRESULT CALLBACK window_callback(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; static int scroll_value = 0; const int CONTENT_HEIGHT = 1000; // 内容高度常量 switch (uMsg) { case WM_MOUSEWHEEL: { // 转换滚轮delta为合适的滚动步长,反转方向符合用户直觉 int delta = GET_WHEEL_DELTA_WPARAM(wParam); int scroll_step = delta > 0 ? -30 : 30; // 获取客户区尺寸,计算最大滚动范围 RECT client_rect; GetClientRect(hwnd, &client_rect); int client_height = client_rect.bottom - client_rect.top; int max_scroll = CONTENT_HEIGHT - client_height; if(max_scroll < 0) max_scroll = 0; // 计算新的滚动位置并限制范围 int new_scroll = scroll_value + scroll_step; if(new_scroll < 0) new_scroll = 0; if(new_scroll > max_scroll) new_scroll = max_scroll; // 仅当滚动位置变化时执行滚动操作 int scroll_delta = new_scroll - scroll_value; if(scroll_delta != 0) { // ScrollWindowEx的dy参数是内容移动的偏移:负数值表示内容向上移(用户向下滚) ScrollWindowEx(hwnd, 0, -scroll_delta, NULL, NULL, NULL, NULL, SW_INVALIDATE); scroll_value = new_scroll; UpdateWindow(hwnd); } } break; case WM_PAINT: printf("WM_PAINT\r\n"); hdc=BeginPaint(hwnd, &ps); GpGraphics *gfx; GdipCreateFromHDC(hdc, &gfx); GpPen *pen; GdipCreatePen1(0xFF4c4c4c, 15, UnitPixel, &pen); // 根据滚动偏移绘制内容 GdipDrawRectangle(gfx, pen, 20, 20 - scroll_value, 100, CONTENT_HEIGHT); // 释放GDI+资源 GdipDeletePen(pen); GdipDeleteGraphics(gfx); EndPaint(hwnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProcW(hwnd, uMsg, wParam, lParam); } return 0; }
关键修改说明
滚动逻辑修正:
- 将滚轮delta转换为30像素的步长,方向反转以符合用户直觉(向下滚时内容向上移动)。
- 计算客户区高度,限制滚动范围为
内容高度 - 客户区高度,确保能滚动到内容底部。 - 使用
scroll_value维护当前滚动位置,仅当位置变化时执行滚动操作。
绘制逻辑修正:
- 绘制矩形时,y坐标减去
scroll_value,实现内容随滚动偏移。
- 绘制矩形时,y坐标减去
资源与细节修复:
- 释放GDI+创建的
GpPen和GpGraphics,避免资源泄漏。 - 修正窗口垂直居中的计算错误(原代码用400代替窗口高度600)。
- 修正
UnregisterClass的类名和宽字符版本,匹配注册的类名。 - 新增
GdiplusShutdown关闭GDI+,释放相关资源。
- 释放GDI+创建的
内容的提问来源于stack exchange,提问作者HelloGUI
相关产品推荐
相关产品推荐

