C ++ win32 API窗口不会在WindowUpdate上更新

时间:2015-12-17 00:15:36

标签: winapi visual-c++

我有一个程序应该显示每秒递增的计数。计数器在一个单独的线程中。我发生更改时调用WindowUpdate,但窗口中的计数不会更新,除非我将鼠标指针悬停在窗口上或调整窗口大小。我尝试过InvalidateRect和RedrawWindow但它们也不起作用。

为什么计数器更新不会显示?

TextView

3 个答案:

答案 0 :(得分:2)

免责声明:这个答案是我的裤子所在。如果我在某个地方犯了错误,请纠正我。)

你没有做你想做的事情。

首先,正如Jonathan Potter所说,UpdateWindow()本身不会更新窗口,除非它有无效区域InvalidateRect()调用将使矩形无效。所以你需要两个......

...但实际上你并不想直接调用UpdateWindow(),因为它绕过了Windows绘图模型。调用InvalidateRect()后,如果下次调用GetMessage()时没有待处理的消息,并且Windows本身决定是时候用新数据刷新屏幕内容,那么您将收到WM_PAINT条消息。系统知道什么时候最好画画;通过使用它,您将使您的生活更轻松。只有在您希望窗口重绘现在至关重要时才使用UpdateWindow()

(无效区域的点也是由于Windows绘图模型:绘图可能非常昂贵,因此Windows尝试通过仅重新绘制所需内容来优化绘图。仅使窗口中需要更新的部分无效,你会得到更好的表现。)

但是还有一个更深层次的问题:你没有正确实现多线程。

您的工作线程每秒生成数据并覆盖共享结构。您的窗口正在从该共享结构中读取。没有任何地方可以确保一次只有一个线程访问该共享结构。因此,如果您的工作线程从一个简单的整数增长到一个庞大而复杂的数据结构,那么您将结束可能的混合状态。

您需要同步数据访问。在线程之间进行通信。

如何?显而易见的方法是使用同步对象,如互斥锁。你完全可以做到这一点。

但是有更好的方法:因为你的一个线程有一个窗口,只需使用一个窗口消息!使用SendMessage()发送的窗口消息将在窗口的线程上接收(调用线程被阻塞);使用PostMessage()发布的消息将被放置在窗口的线程的消息队列中。

另外,您可以在该消息中传递一个而不是两个信息! wParamlParam都是指针大小的整数,所以只需在其中一个中添加指针并使用SendMessage()PostMessage()

但是你用什么信息?窗口类可以使用[WM_USERWM_APP范围内的每条*消息来决定使用它的内容,以及范围[WM_APP,{{1}中的每条消息})是由应用程序决定使用它的原因。所以选择一个并使用它!

请记住0xC000SendMessage()的工作原理。如果数据是在堆上分配的,并且每个数据都是单独分配的,那么这并不重要;如果数据是指向局部变量的指针,则PostMessage()是正确的答案;如果数据是一个可以适合指针大小的整数的数值,则两者都可以。如果您需要响应,您的工作线程将需要一个自己的窗口或使用SendMessage()从窗口中获取值。

有很多方法可以做到这一点。这完全取决于您需要沟通的数据。正如Go编程语言的设计者所说(有所改变):不通过共享数据进行通信;通过沟通来分享数据。

*直到您拨打SendMessage(),此时您会丢失IsDialogMessage()WM_USER(以及可能WM_USER + 1?)。但这只是32,000多条中的三条消息。

答案 1 :(得分:2)

您的问题出在您的消息循环中:

    while (1)
    {
        if (pPayload->Updated == TRUE) {
            UpdateWindow(hWnd);
        }
        if (GetMessage(&msg, NULL, 0, 0) > 0) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

GetMessage是同步的,并且在消息可用于检索之前不会返回。代码位于那里,等待消息大部分时间到达,并且不会评估 pPayload->更新。因此,在消息到达之前不会发生更新。但即使这样,也没有任何反应,因为如果窗口的更新区域不为空,UpdateWindow会向窗口发送" WM_PAINT消息 " UpdateWindow本身并没有做任何有用的事情。 1)

您可以通过重新处理消息循环来解决此问题。需要解决以下问题:

  • 拨打InvalidateRect之前必须先致电UpdateWindow
  • 必须同步对共享数据的访问。
  • GetMessage返回0或-1时,消息循环需要终止。

但这些都不是必要的。你不需要后台线程。单线程应用程序就可以了。只需使用Timer来触发对存储值和可视化表示的更新。以下是实现基于计时器的解决方案的窗口过程的草图:

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    static unsigned __int64 startTime = ::GetTickCount64();
    const UINT_PTR timerId = 1;

    switch (uMsg)
    {
    case WM_CREATE:
        // Start timer when window is created
        ::SetTimer(hWnd, timerId, 1000, nullptr);
        return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
    case WM_TIMER: {
        unsigned __int64 currTime = ::GetTickCount64();
        Counter = (currTime - startTime) / 1000;
        // Value changed -> initiate window update
        ::InvalidateRect(hWnd, nullptr, FALSE);
        // Re-start timer
        ::SetTimer(hWnd, timerId, 1000, nullptr);
        }
        break;
    case WM_DESTROY:
        ::KillTimer(hWnd, timerId);
        ::PostQuitMessage(0);
        break;
    case WM_PAINT: {
        PAINTSTRUCT paintStruct = {0};
        HDC hDC = BeginPaint(hWnd, &paintStruct);
        DisplayData(hDC);
        EndPaint(hWnd, &paintStruct);
        }
        return 0;
    default:
        return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
    }

    return 0;
}

关于实施的一些注释:

  • 计时器使用1000毫秒的固定超时值。这有时会跳过更新。但是,错误不会累积,因为只要计时器到期,就会重新评估计数器。如果您更加稳定更新,请根据计数器 startTime currTime 计算剩余超时值。
  • 计数器是以秒为单位保存当前时间的变量。它是TRANSFER::Counter的替代品,需要从窗口过程和呈现代码中访问。

<小时/> 1) 您的消息循环还有另一个问题:它永远不会终止,您的流程也不会终止。 GUI可能会消失,但该过程仍将显示在任务管理器中。

答案 2 :(得分:-1)

我用评论<<updated标记了我的更改 而不是设置标志更新,我将其更改为hWnd,然后立即直接从线程更新窗口

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <sdkddkver.h>
#include <Windows.h>

#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <strsafe.h>

typedef struct DataTransfer {
    //BOOL Updated;   // << updated
    int Counter;
    HWND hWnd;        // << updated
} TRANSFER, *PTRANSFER;

TRANSFER Payload;
PTRANSFER pPayload;
DWORD dwThreadId;
HANDLE hThread;

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void DisplayData(HDC hDC);
void ErrorHandler(LPTSTR lpszFunction);
DWORD WINAPI Counter(LPVOID lpParam);

int
APIENTRY
wWinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPWSTR lpCmdLine,
    int nShowCmd)
{
    MSG        msg;  

    WNDCLASSEX wcex;
    ZeroMemory(&wcex, sizeof(wcex));

    wcex.cbSize = sizeof(wcex); 
    wcex.style = CS_HREDRAW | CS_VREDRAW; 
    wcex.lpszClassName = TEXT("MYFIRSTWINDOWCLASS"); 
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW); //<< updated
    wcex.lpfnWndProc = WndProc;
    wcex.hInstance = hInstance; 

    if (!RegisterClassEx(&wcex))
        return 1;

    CREATESTRUCT cs;
    ZeroMemory(&cs, sizeof(cs));

    cs.x = 0; 
    cs.y = 0;
    cs.cx = 200;
    cs.cy = 300;
    cs.hInstance = hInstance; 
    cs.lpszClass = wcex.lpszClassName; 
    cs.lpszName = TEXT("Test");

    cs.style = WS_OVERLAPPEDWINDOW;


    HWND hWnd = CreateWindowEx(
        cs.dwExStyle,
        cs.lpszClass,
        cs.lpszName,
        cs.style,
        cs.x,
        cs.y,
        cs.cx,
        cs.cy,
        cs.hwndParent,
        cs.hMenu,
        cs.hInstance,
        cs.lpCreateParams);

    if (!hWnd)
        return 1;

    DWORD dwThreadId;
    HANDLE hThread;

    pPayload = (PTRANSFER)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(TRANSFER));
    if (pPayload == NULL)
        ExitProcess(2);
    //pPayload->Updated = FALSE; //<< updated
    pPayload->hWnd=hWnd;

    pPayload->Counter = 0;

    // Display the window.
    ShowWindow(hWnd, SW_SHOWDEFAULT);
    UpdateWindow(hWnd);

    hThread = CreateThread( NULL,  0, Counter, pPayload,  0, &dwThreadId); 
    if (hThread == NULL)
         ExitProcess(2);


    while (1)
    {
        //    ____[ updated ]_____
         /*
        if (pPayload->Updated == TRUE)
        {

            UpdateWindow(hWnd);
            pPayload->Updated=FALSE;
        }
        */
        int ret=GetMessage(&msg, NULL, 0, 0);//<< updated
        if(ret==0 || ret==-1)
            break;

        TranslateMessage(&msg);
        DispatchMessage(&msg);

   }

    UnregisterClass(wcex.lpszClassName, hInstance);

    return (int)msg.wParam;
}

LRESULT
CALLBACK
WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT paintStruct;
    HDC hDC;

    switch (uMsg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_PAINT:
        hDC = BeginPaint(hWnd, &paintStruct);
        DisplayData(hDC);
        EndPaint(hWnd, &paintStruct);

        break;
    default:
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }

    return 0;
}


void DisplayData(HDC hDC)
{
    char OutputStr[32];

    sprintf_s(OutputStr, sizeof(OutputStr) - 1, "%d", pPayload->Counter);
    TextOut(hDC, 100, 100, OutputStr, strlen(OutputStr));
}

DWORD WINAPI Counter(LPVOID lpParam)
{
    PTRANSFER pTransfer;

    pTransfer = (PTRANSFER)lpParam;

    while (1)
    {
        pTransfer->Counter++;
        InvalidateRect(pTransfer->hWnd,NULL,1);
        Sleep(1000);
    }
}

void ErrorHandler(LPTSTR lpszFunction)
{
    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    DWORD dw = GetLastError();

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&lpMsgBuf,
        0, NULL);

    lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
        (lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR));
    StringCchPrintf((LPTSTR)lpDisplayBuf,
        LocalSize(lpDisplayBuf) / sizeof(TCHAR),
        TEXT("%s failed with error %d: %s"),
        lpszFunction, dw, lpMsgBuf);
    MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);

    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);
}