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

WinAPI C++主窗口与滚动子窗口Static控件重复及滚动交互问题

问题描述

我想要创建一个指定大小和位置的可滚动子窗口,窗口内用Static控件承载消息。尝试过不同类名后遇到以下问题:

  • 使用static类时,滚动条可见但无法交互;
  • 认为edit类不合适,因为需要自定义绘制消息;
  • 已知子窗口(messages_window)和主窗口使用相同类名导致逻辑混乱,但不知道如何修正,也不确定是否需要更改滚动条的创建方式。

代码如下:

#define UNICODE
#define _UNICODE

#include "windows.h"
#include <stdlib.h>
#include <stdio.h>
#include <cwctype>
#include <cwchar>
#include <iostream>
#include <cmath>

LRESULT CALLBACK window_message_procedure(HWND, UINT, WPARAM, LPARAM);

// fonst and style
HFONT hFont;
HFONT hErrorFont;
HBRUSH hStaticBrush;
int vscroll_pos = 0;
HWND messages_window;
HWND message_window;

static HBRUSH hBrush = CreateSolidBrush(RGB(230,230,230));

// controls
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE HprevInst, LPSTR args, int ncmdshow) {

    // set window values
    const wchar_t* window_class_name = L"WindowClass";
    const wchar_t* window_name = L"Window";

    const int WINDOW_W = 500;
    const int WINDOW_H = 500;

    // create window
    WNDCLASSW wc = {0};

    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    // wc.hbrBackground = CreateSolidBrush(RGB(24, 24, 24));

    // set cursor
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);

    wc.hInstance = hInst;
    wc.lpszClassName = window_class_name;
    // set procedure for window messages
    wc.lpfnWndProc = window_message_procedure;

    // register window class
    if (!RegisterClassW(&wc)) {
        return -1;
    }
    
    // create window
    HWND hWnd = CreateWindowW(
        // WS_EX_APPWINDOW,
        window_class_name, window_name, 
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT, 
        WINDOW_W, WINDOW_H,
        NULL, NULL, hInst, NULL
    );

    messages_window = CreateWindowEx(
        0,
        window_class_name, // it only works if I setting same class as main window
        L"",
        WS_CHILD | WS_VISIBLE | WS_VSCROLL,
        0, 200, 500, 200,
        hWnd,
        NULL,
        hInst,
        NULL
    );

    ShowWindow(messages_window, ncmdshow);
    UpdateWindow(messages_window);

    MSG message = {0};

    // main loop
    while (GetMessage(&message, NULL, 0, 0)) {
        TranslateMessage(&message);
        DispatchMessage(&message);
    }    

    return 0;
}


LRESULT CALLBACK window_message_procedure(HWND hWnd, UINT message, WPARAM wparam, LPARAM lparam) {
    switch (message) {
    // on window create
    case WM_CREATE: {
        HDC hdc = GetDC(hWnd);
        // set common font
        int fontSize = -MulDiv(14, GetDeviceCaps(hdc, LOGPIXELSY), 72);
        ReleaseDC(hWnd, hdc);

        hFont = CreateFontW(
            fontSize, 0, 0, 0,
            FW_NORMAL,
            FALSE, FALSE, FALSE,
            DEFAULT_CHARSET,
            OUT_OUTLINE_PRECIS,
            CLIP_DEFAULT_PRECIS,
            CLEARTYPE_QUALITY, // smooth text
            VARIABLE_PITCH,
            L"Segoe UI"
        );
        // error font
        fontSize = -MulDiv(10, GetDeviceCaps(hdc, LOGPIXELSY), 72);
        ReleaseDC(hWnd, hdc);

        hErrorFont = CreateFontW(
            fontSize, 0, 0, 0,
            FW_NORMAL,
            FALSE, FALSE, FALSE,
            DEFAULT_CHARSET,
            OUT_OUTLINE_PRECIS,
            CLIP_DEFAULT_PRECIS,
            CLEARTYPE_QUALITY, // smooth text
            VARIABLE_PITCH,
            L"Segoe UI"
        );

        message_window = CreateWindowW(
            L"static", L"test text hadshfasdf",
            WS_VISIBLE | WS_CHILD | ES_MULTILINE,
            0, 0, 60, 30,
            hWnd, NULL, NULL, NULL
        );

        SCROLLINFO scroll_info;
        scroll_info.cbSize = sizeof(scroll_info);
        scroll_info.nPos = 0;
        scroll_info.nMax = 800;
        scroll_info.nPage = 400;
        scroll_info.fMask = SIF_ALL;
        SetScrollInfo(messages_window, SB_VERT, &scroll_info, TRUE);
        // EnableScrollBar(messages_window, SB_VERT, ESB_ENABLE_BOTH);

        break;
    }

    case WM_VSCROLL:
    {
        int oldPos = vscroll_pos;

        switch (LOWORD(wparam))
        {
            case SB_LINEUP:
                vscroll_pos -= 10;
                break;

            case SB_LINEDOWN:
                vscroll_pos += 10;
                break;

            case SB_PAGEUP:
                vscroll_pos -= 50;
                break;

            case SB_PAGEDOWN:
                vscroll_pos += 50;
                break;

            case SB_THUMBTRACK:
                vscroll_pos = HIWORD(wparam);
                break;
        }

        vscroll_pos = std::max(0, std::min(400, vscroll_pos));

        if (vscroll_pos != oldPos)
        {
            int delta = oldPos - vscroll_pos;

            ScrollWindow(messages_window, 0, delta, NULL, NULL);

            SetScrollPos(messages_window, SB_VERT, vscroll_pos, TRUE);

            UpdateWindow(messages_window);
        }

        return 0;
    }
    // close window
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    
    // menu or button click
    case WM_COMMAND:
        switch (wparam) {
            
        }
    }
    return DefWindowProcW(hWnd, message, wparam, lparam);
}

编译命令:g++ test_scroll.cpp -mwindows -lcomctl32 -lgdi32


解决方案

1. 为滚动子窗口注册独立的窗口类

主窗口和子窗口共用同一个类会导致消息处理逻辑混乱,需要给滚动子窗口单独注册专属类:

// 在WinMain中注册主窗口类后,添加以下代码
const wchar_t* scroll_window_class = L"ScrollWindowClass";
WNDCLASSW scroll_wc = {0};
scroll_wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
scroll_wc.hCursor = LoadCursor(NULL, IDC_ARROW);
scroll_wc.hInstance = hInst;
scroll_wc.lpszClassName = scroll_window_class;
scroll_wc.lpfnWndProc = window_message_procedure; // 可复用同一个消息过程

if (!RegisterClassW(&scroll_wc)) {
    return -1;
}

// 创建messages_window时使用新类名
messages_window = CreateWindowEx(
    0,
    scroll_window_class, // 替换为新注册的滚动窗口类名
    L"",
    WS_CHILD | WS_VISIBLE | WS_VSCROLL,
    0, 200, 500, 200,
    hWnd,
    NULL,
    hInst,
    NULL
);

2. 修正滚动消息的处理逻辑

当前WM_VSCROLL处理未区分消息来源,会导致主窗口和子窗口的滚动逻辑冲突,需添加窗口判断:

case WM_VSCROLL:
{
    // 仅处理滚动子窗口的消息
    if (hWnd != messages_window) {
        return DefWindowProcW(hWnd, message, wparam, lparam);
    }

    int oldPos = vscroll_pos;
    SCROLLINFO scroll_info = {0};
    scroll_info.cbSize = sizeof(scroll_info);
    scroll_info.fMask = SIF_ALL;
    GetScrollInfo(hWnd, SB_VERT, &scroll_info);

    switch (LOWORD(wparam))
    {
        case SB_LINEUP:
            scroll_info.nPos -= 10;
            break;
        case SB_LINEDOWN:
            scroll_info.nPos += 10;
            break;
        case SB_PAGEUP:
            scroll_info.nPos -= scroll_info.nPage;
            break;
        case SB_PAGEDOWN:
            scroll_info.nPos += scroll_info.nPage;
            break;
        case SB_THUMBTRACK:
            scroll_info.nPos = HIWORD(wparam);
            break;
        default:
            return DefWindowProcW(hWnd, message, wparam, lparam);
    }

    // 限制滚动范围,避免超出内容边界
    scroll_info.nPos = std::max(0, std::min(scroll_info.nMax - (int)scroll_info.nPage, scroll_info.nPos));

    if (scroll_info.nPos != oldPos)
    {
        int delta = oldPos - scroll_info.nPos;
        ScrollWindow(hWnd, 0, delta, NULL, NULL); // 滚动子窗口内的所有控件
        scroll_info.fMask = SIF_POS;
        SetScrollInfo(hWnd, SB_VERT, &scroll_info, TRUE); // 更新滚动条位置
        vscroll_pos = scroll_info.nPos;
        UpdateWindow(hWnd);
    }
    return 0;
}

3. 将Static控件挂载到滚动子窗口

当前message_window创建在主窗口下,需修改父窗口为滚动子窗口,才能随滚动条移动:

message_window = CreateWindowW(
    L"static", L"test text hadshfasdf",
    WS_VISIBLE | WS_CHILD | SS_LEFT | SS_WORDELLIPSIS, // 替换为Static控件专属样式
    0, 0, 60, 30,
    messages_window, // 父窗口改为滚动子窗口
    NULL, NULL, NULL
);

4. 修复Static控件的样式错误

ES_MULTILINE是Edit控件的样式,Static控件应使用SS_LEFT、SS_WORDELLIPSIS等专属样式,避免样式冲突。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.02 05:34:51