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

Windows API父窗口内嵌子窗口:子窗口重复渲染问题求助

子窗口嵌入父窗口时出现重复渲染问题

我正在开发一个简单应用,通过Windows API将子应用窗口嵌入父窗口中。功能基本可行,但存在异常:子窗口出现重复渲染的情况(父窗口内显示两份子窗口内容)。以下是子应用与父应用的代码:

child_app.cpp

// child_app.cpp (corrected for Unicode)
#include <iostream>
#include <windows.h>

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
}

int main() {
    // 1. Register the window class (Unicode)
    const wchar_t CLASS_NAME[] = L"ChildWindowClass";

    WNDCLASSW wc = {}; // Use WNDCLASSW for Unicode
    wc.lpfnWndProc = WndProc;
    wc.hInstance = GetModuleHandle(NULL);
    wc.lpszClassName = CLASS_NAME;

    if (RegisterClassW(&wc) == 0) { // Use RegisterClassW for Unicode
        std::cerr << "Failed to register window class." << std::endl;
        return 1;
    }

    // 2. Create the window (Unicode)
    HWND hwnd = CreateWindowExW( // Use CreateWindowExW for Unicode
        0,
        CLASS_NAME,
        L"Child Window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
        NULL, NULL, GetModuleHandle(NULL), NULL
    );

    if (hwnd == NULL) {
        std::cerr << "Failed to create window." << std::endl;
        return 1;
    }

    ShowWindow(hwnd, SW_SHOW);
    UpdateWindow(hwnd);

    // 3. Message loop
    MSG msg;
    while (GetMessageW(&msg, NULL, 0, 0)) { // Use GetMessageW for Unicode
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

parent_app.cpp

// parent_app.cpp (Enhanced to Fix Double Reparenting Issue)
#include <iostream>
#include <windows.h>
#include <processthreadsapi.h>
#include <string>

HWND foundChildWnd = NULL;

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
}

BOOL CALLBACK EnumChildWindowsProc(HWND hwnd, LPARAM lParam) {
    DWORD processId;
    GetWindowThreadProcessId(hwnd, &processId);

    // Ensure we only process the first valid window and ignore duplicates
    if (processId == lParam && foundChildWnd == NULL) {
        // Check if the window is visible and has the correct class name (optional)
        WCHAR className[256];
        GetClassName(hwnd, className, sizeof(className) / sizeof(WCHAR));

        if (wcscmp(className, L"ChildWindowClass") == 0) { // Match child window class name
            foundChildWnd = hwnd;
            std::cout << "Found child window: HWND=" << hwnd << ", ProcessID=" << processId << std::endl;
            return FALSE; // Stop enumeration after finding the first valid window
        }
    }

    return TRUE; // Continue enumeration for non-matching windows
}

int main() {
    // 1. Register the window class
    const wchar_t CLASS_NAME[] = L"ParentWindowClass";

    WNDCLASS wc = {};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = GetModuleHandle(NULL);
    wc.lpszClassName = CLASS_NAME;

    RegisterClass(&wc);

    // 2. Create the window
    HWND hwnd = CreateWindowEx(
        0,
        CLASS_NAME,
        L"Parent Window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
        NULL, NULL, GetModuleHandle(NULL), NULL
    );

    if (hwnd == NULL) {
        std::cerr << "Failed to create window." << std::endl;
        return 1;
    }

    ShowWindow(hwnd, SW_SHOW);
    UpdateWindow(hwnd);

    // 4. Start child_app.exe
    STARTUPINFO startupInfo;
    PROCESS_INFORMATION processInfo;
    ZeroMemory(&startupInfo, sizeof(startupInfo));
    startupInfo.cb = sizeof(startupInfo);
    ZeroMemory(&processInfo, sizeof(processInfo));

    std::wstring childPath = L"child_app.exe";

    if (CreateProcess(
        NULL,
        (LPWSTR)childPath.c_str(),
        NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo
    )) {
        std::cout << "Child app started successfully, ProcessID=" << processInfo.dwProcessId << std::endl;

        // Wait for child process to become idle
        WaitForInputIdle(processInfo.hProcess, INFINITE);

        // Enumerate child windows of the child process
        EnumChildWindows(NULL, EnumChildWindowsProc, (LPARAM)processInfo.dwProcessId);

        if (foundChildWnd) {
            std::cout << "Child window found and reparented." << std::endl;
            SetParent(foundChildWnd, hwnd);
            RECT clientRect;
            GetClientRect(hwnd, &clientRect);
            SetWindowPos(foundChildWnd, NULL, 0, 0, clientRect.right, clientRect.bottom, SWP_SHOWWINDOW);
        }
        else {
            std::cerr << "Failed to find child window." << std::endl;
        }

        WaitForSingleObject(processInfo.hProcess, INFINITE);
        CloseHandle(processInfo.hProcess);
        CloseHandle(processInfo.hThread);

    }
    else {
        std::cerr << "Failed to start child app." << std::endl;
    }

    // 3. Message loop
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

问题原因与修复方案

核心原因

重复渲染源于子窗口的窗口样式冲突和父窗口消息循环阻塞:

  • 子窗口使用WS_OVERLAPPEDWINDOW样式(包含标题栏、边框),重父化后系统仍会按独立窗口处理其绘制逻辑,导致重复渲染。
  • 父窗口在启动子进程后调用WaitForSingleObject阻塞了自身消息循环,无法及时处理窗口刷新消息,加剧渲染异常。

修复步骤

  1. 修改子窗口创建样式
    在child_app.cpp的CreateWindowExW调用中,将窗口样式从WS_OVERLAPPEDWINDOW改为WS_CHILD | WS_VISIBLE,让子窗口以子控件模式创建:

    HWND hwnd = CreateWindowExW(
        0,
        CLASS_NAME,
        L"Child Window",
        WS_CHILD | WS_VISIBLE, // 替换原样式
        0, 0, 400, 300, // 初始位置设为(0,0),后续由父窗口调整
        NULL, NULL, GetModuleHandle(NULL), NULL
    );
    
  2. 清除子窗口的独立窗口扩展样式
    在父窗口调用SetParent后,移除子窗口的WS_EX_APPWINDOW扩展样式,避免系统将其视为独立窗口:

    if (foundChildWnd) {
        std::cout << "Child window found and reparented." << std::endl;
        SetParent(foundChildWnd, hwnd);
        // 清除独立窗口扩展样式
        LONG exStyle = GetWindowLongPtr(foundChildWnd, GWL_EXSTYLE);
        exStyle &= ~WS_EX_APPWINDOW;
        SetWindowLongPtr(foundChildWnd, GWL_EXSTYLE, exStyle);
        // 调整子窗口大小
        RECT clientRect;
        GetClientRect(hwnd, &clientRect);
        SetWindowPos(foundChildWnd, NULL, 0, 0, clientRect.right, clientRect.bottom, SWP_NOZORDER);
    }
    
  3. 父窗口处理WM_SIZE消息同步子窗口大小
    在父窗口的WndProc中添加WM_SIZE消息处理,确保父窗口大小变化时子窗口同步调整:

    LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
        switch (msg) {
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        case WM_SIZE:
            // 同步子窗口大小
            if (foundChildWnd != NULL) {
                RECT clientRect;
                GetClientRect(hwnd, &clientRect);
                SetWindowPos(foundChildWnd, NULL, 0, 0, clientRect.right, clientRect.bottom, SWP_NOZORDER);
            }
            return 0;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
        }
    }
    
  4. 避免父窗口消息循环阻塞
    将父窗口中WaitForSingleObject的阻塞逻辑改为异步线程处理,防止父窗口无法响应消息:

    if (CreateProcess(
        NULL,
        (LPWSTR)childPath.c_str(),
        NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo
    )) {
        std::cout << "Child app started successfully, ProcessID=" << processInfo.dwProcessId << std::endl;
    
        // 等待子进程初始化完成
        WaitForInputIdle(processInfo.hProcess, INFINITE);
    
        // 枚举子窗口并重父化
        EnumChildWindows(NULL, EnumChildWindowsProc, (LPARAM)processInfo.dwProcessId);
    
        // ... 重父化逻辑 ...
    
        // 创建异步线程等待子进程退出,不阻塞父窗口消息循环
        HANDLE hWaitThread = CreateThread(NULL, 0, [](LPVOID lpParam) -> DWORD {
            PROCESS_INFORMATION* pInfo = (PROCESS_INFORMATION*)lpParam;
            WaitForSingleObject(pInfo->hProcess, INFINITE);
            CloseHandle(pInfo->hProcess);
            CloseHandle(pInfo->hThread);
            delete pInfo;
            return 0;
        }, new PROCESS_INFORMATION(processInfo), 0, NULL);
        CloseHandle(hWaitThread);
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 18:17:04