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

Win32 API的InvalidateRect函数为何看似无法正常工作?

Win32 API的InvalidateRect函数为何看似无法正常工作?

我仔细看了你提供的Win32桌面应用代码,发现几个关键问题导致InvalidateRect看似无法正常工作,甚至引发了相反的效果,咱们一步步拆解:

先贴出你的完整代码方便对照

// Win32-Desktop-Tab-Overlay-Splitter-RichEdit.cpp : Defines the entry point for the application.
//
#include "framework.h"
#include <commctrl.h>
#include <iostream>
#include "resource.h"

#pragma comment(lib, "comctl32.lib")

#define ID_TABCTRL 100
#define ID_SPLITTER 101
#define MAX_LOADSTRING 100

HINSTANCE hInst;
HWND hWnd;
RECT hWnd_Rect;
HWND hwndTabMain;
HWND hwndDrawArea;
HWND hwndOverlay;
HWND hSplitter;
HWND hRichEdit;
int splitterPos = 200; // Initial splitter position

static WNDPROC OldSplitterProc;
static bool isDragging = false;

WCHAR szTitle[MAX_LOADSTRING];
WCHAR szWindowClass[MAX_LOADSTRING];

ATOM RegisterWindowClasses(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK SplitterProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
void AllocateConsole();
void UpdateOverlayPosition(HWND hWnd, HWND hwndTabMain, HWND hwndOverlay);

void AllocateConsole() {
    AllocConsole();
    FILE* fpOut;
    freopen_s(&fpOut, "CONOUT$", "w", stdout);
    FILE* fpIn;
    freopen_s(&fpIn, "CONIN$", "r", stdin);
    FILE* fpErr;
    freopen_s(&fpErr, "CONOUT$", "w", stderr);
    std::cout << "Console window initialized successfully!" << std::endl;
}

LRESULT CALLBACK DrawAreaProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_PAINT: {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);
        RECT rect;
        GetClientRect(hwnd, &rect);
        InvalidateRect(hWnd, &rect, TRUE); // 这里是第一个大坑!
        HBRUSH whiteBrush = (HBRUSH)GetStockObject(WHITE_BRUSH);
        FillRect(hdc, &rect, whiteBrush);
        HPEN blackPen = (HPEN)GetStockObject(BLACK_PEN);
        HPEN oldPen = (HPEN)SelectObject(hdc, blackPen);
        MoveToEx(hdc, 0, 0, NULL);
        LineTo(hdc, rect.right, rect.bottom);
        SelectObject(hdc, oldPen);
        EndPaint(hwnd, &ps);
        return 0;
    }
    case WM_LBUTTONDOWN: {
        int x = LOWORD(lParam);
        int y = HIWORD(lParam);
        std::cout << "DrawArea Left Click at: (" << x << ", " << y << ")" << std::endl;
        return 0;
    }
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

LRESULT CALLBACK OverlayProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_PAINT: {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);
        RECT rect;
        GetClientRect(hwnd, &rect);
        InvalidateRect(hwnd, &rect, TRUE); // 第二个坑!
        HPEN redPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
        HPEN oldPen = (HPEN)SelectObject(hdc, redPen);
        MoveToEx(hdc, rect.left, rect.top, NULL);
        LineTo(hdc, rect.right, rect.bottom);
        MoveToEx(hdc, rect.right, rect.top, NULL);
        LineTo(hdc, rect.left, rect.bottom);
        MoveToEx(hdc, rect.left, rect.top, NULL);
        LineTo(hdc, rect.right, rect.top);
        MoveToEx(hdc, rect.right, rect.top, NULL);
        LineTo(hdc, rect.right, rect.bottom);
        MoveToEx(hdc, rect.right, rect.bottom, NULL);
        LineTo(hdc, rect.left, rect.bottom);
        MoveToEx(hdc, rect.left, rect.bottom, NULL);
        LineTo(hdc, rect.left, rect.top);
        SelectObject(hdc, oldPen);
        DeleteObject(redPen);
        EndPaint(hwnd, &ps);
        std::cout << "OverlayProc: WM_PAINT completed" << std::endl;
        return 0;
    }
    case WM_LBUTTONDOWN: {
        int x = LOWORD(lParam);
        int y = HIWORD(lParam);
        std::cout << "Overlay Left Click at: (" << x << ", " << y << ")" << std::endl;
        POINT pt = { x, y };
        ClientToScreen(hwnd, &pt);
        ScreenToClient(hwndDrawArea, &pt);
        pt.x -= 4;
        pt.y -= 4;
        if (pt.x < 0) pt.x = 0;
        if (pt.y < 0) pt.y = 0;
        std::cout << "Forwarding click to DrawArea at: (" << pt.x << ", " << y << ")" << std::endl;
        SendMessage(hwndDrawArea, WM_LBUTTONDOWN, wParam, MAKELPARAM(pt.x, pt.y));
        return 0;
    }
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

LRESULT CALLBACK SplitterProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_LBUTTONDOWN:
        SetCapture(hWnd);
        isDragging = true;
        return 0;
    case WM_MOUSEMOVE:
        if (isDragging) {
            POINT pt;
            GetCursorPos(&pt);
            ScreenToClient(GetParent(hWnd), &pt);
            int newSplitterPos = pt.y;
            RECT rect;
            GetClientRect(GetParent(hWnd), &rect);
            if (newSplitterPos < 100) newSplitterPos = 100;
            if (newSplitterPos > rect.bottom - 100) newSplitterPos = rect.bottom - 100;
            MoveWindow(hwndTabMain, 0, 0, rect.right, newSplitterPos, TRUE);
            MoveWindow(hSplitter, 0, newSplitterPos, rect.right, 5, TRUE);
            MoveWindow(hRichEdit, 0, newSplitterPos + 5, rect.right, rect.bottom - newSplitterPos - 5, TRUE);
            RECT displayRect1 = { 0, 0, rect.right, newSplitterPos };
            TabCtrl_AdjustRect(hwndTabMain, FALSE, &displayRect1);
            MoveWindow(hwndDrawArea, displayRect1.left, displayRect1.top, displayRect1.right - displayRect1.left, displayRect1.bottom - displayRect1.top, TRUE);
            UpdateOverlayPosition(GetParent(hWnd), hwndTabMain, hwndOverlay);
            splitterPos = newSplitterPos;
        }
        return 0;
    case WM_LBUTTONUP:
        ReleaseCapture();
        isDragging = false;
        return 0;
    }
    return CallWindowProc(OldSplitterProc, hWnd, msg, wParam, lParam);
}

ATOM RegisterWindowClasses(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32DESKTOPTABOVERLAYSPLITTERRICHEDIT));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_WIN32DESKTOPTABOVERLAYSPLITTERRICHEDIT);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    ATOM atom = RegisterClassExW(&wcex);

    // 注册DrawArea窗口类
    WNDCLASSEXW drawClass = {0};
    drawClass.cbSize = sizeof(WNDCLASSEX);
    drawClass.style = CS_HREDRAW | CS_VREDRAW;
    drawClass.lpfnWndProc = DrawAreaProc;
    drawClass.hInstance = hInstance;
    drawClass.hCursor = LoadCursor(nullptr, IDC_CROSS);
    drawClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    drawClass.lpszClassName = L"DrawAreaClass";
    RegisterClassExW(&drawClass);

    // 注册Overlay窗口类
    WNDCLASSEXW overlayClass = {0};
    overlayClass.cbSize = sizeof(WNDCLASSEX);
    overlayClass.style = CS_HREDRAW | CS_VREDRAW;
    overlayClass.lpfnWndProc = OverlayProc;
    overlayClass.hInstance = hInstance;
    overlayClass.hCursor = LoadCursor(nullptr, IDC_HAND);
    overlayClass.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH); // 透明背景
    overlayClass.lpszClassName = L"OverlayClass";
    RegisterClassExW(&overlayClass);

    return atom;
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // Store instance handle in our global variable

   hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   // 创建Tab控件
   hwndTabMain = CreateWindowW(WC_TABCONTROLW, L"", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
       0, 0, 600, 200, hWnd, (HMENU)ID_TABCTRL, hInstance, nullptr);

   // 添加Tab项
   TCITEMW tci;
   tci.mask = TCIF_TEXT;
   tci.pszText = L"Draw Tab";
   TabCtrl_InsertItemW(hwndTabMain, 0, &tci);

   // 创建DrawArea
   hwndDrawArea = CreateWindowW(L"DrawAreaClass", L"", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
       0, 0, 580, 160, hwndTabMain, nullptr, hInstance, nullptr);

   // 创建Overlay窗口(覆盖在DrawArea上)
   hwndOverlay = CreateWindowExW(WS_EX_LAYERED | WS_EX_TRANSPARENT, L"OverlayClass", L"",
       WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
       10, 10, 100, 100, hwndDrawArea, nullptr, hInstance, nullptr);
   SetLayeredWindowAttributes(hwndOverlay, RGB(0,0,0), 128, LWA_ALPHA); // 半透明

   // 创建Splitter
   hSplitter = CreateWindowW(L"STATIC", L"", WS_CHILD | WS_VISIBLE | SS_SUNKEN,
       0, 200, 600, 5, hWnd, (HMENU)ID_SPLITTER, hInstance, nullptr);
   OldSplitterProc = (WNDPROC)SetWindowLongPtrW(hSplitter, GWLP_WNDPROC, (LONG_PTR)SplitterProc);

   // 创建RichEdit控件
   LoadLibraryW(L"riched20.dll");
   hRichEdit = CreateWindowW(RICHEDIT_CLASSW, L"", WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL,
       0, 205, 600, 300, hWnd, nullptr, hInstance, nullptr);

   AllocateConsole();

   return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // Parse the menu selections:
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_SIZE:
        {
            RECT rect;
            GetClientRect(hWnd, &rect);
            MoveWindow(hwndTabMain, 0, 0, rect.right, splitterPos, TRUE);
            MoveWindow(hSplitter, 0, splitterPos, rect.right, 5, TRUE);
            MoveWindow(hRichEdit, 0, splitterPos + 5, rect.right, rect.bottom - splitterPos -5, TRUE);
            RECT displayRect1 = {0,0, rect.right, splitterPos};
            TabCtrl_AdjustRect(hwndTabMain, FALSE, &displayRect1);
            MoveWindow(hwndDrawArea, displayRect1.left, displayRect1.top, displayRect1.right - displayRect1.left, displayRect1.bottom - displayRect1.top, TRUE);
            UpdateOverlayPosition(hWnd, hwndTabMain, hwndOverlay);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

void UpdateOverlayPosition(HWND hWnd, HWND hwndTabMain, HWND hwndOverlay)
{
    RECT tabRect, drawRect, overlayRect;
    GetWindowRect(hwndTabMain, &tabRect);
    ScreenToClient(hWnd, (POINT*)&tabRect.left);
    ScreenToClient(hWnd, (POINT*)&tabRect.right);
    GetClientRect(hwndDrawArea, &drawRect);
    MapWindowPoints(hwndDrawArea, hWnd, (POINT*)&drawRect.left, 2);
    GetClientRect(hwndOverlay, &overlayRect);
    int overlayX = drawRect.left + (drawRect.right - drawRect.left - overlayRect.right)/2;
    int overlayY = drawRect.top + (drawRect.bottom - drawRect.top - overlayRect.bottom)/2;
    MoveWindow(hwndOverlay, overlayX - drawRect.left, overlayY - drawRect.top, overlayRect.right, overlayRect.bottom, TRUE);
}

核心问题分析

1. 在WM_PAINT内部调用InvalidateRect是大忌

你在DrawAreaProc和OverlayProc的WM_PAINT处理中都调用了InvalidateRect:

  • 当系统处理WM_PAINT时,BeginPaint会清除窗口的无效区域,完成绘制后EndPaint会更新窗口状态。
  • 但你在绘制中间又调用InvalidateRect,会再次把窗口区域标记为“需要重绘”,系统会立刻再发送一条WM_PAINT消息。
  • 这
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 06:29:28