C ++从异步线程更新Windows窗口

时间:2017-02-18 15:56:04

标签: c++ windows multithreading visual-studio-2017

所以我刚开始使用C ++并且想要创建一个带有按钮的窗口,该按钮启动计数器的异步线程,计数从5到0,表示耗时很长的任务。该数字应显示在窗口上,并在计数器计数时每秒更新一次。为此,子线程必须以任何方式与主窗口线程的消息循环进行通信。 我尝试这样做:

  • 使用主窗口的窗口句柄发送UpdateWindow
  • 使用主窗口的窗口句柄发送PostMessage

但在这两种情况下,窗口都没有得到更新。因此,我怀疑是通过将主窗口中的窗口句柄发送到子线程还是将子窗口中的UpdateWindow消息发送到主线程或两者都是错误的,或者我完全偏离轨道并且每个都是错误的。

也许我的思维方式也错了,我应该以另一种方式做到这一点,但是,我不知道我应该怎么开始。

#include "stdafx.h"
#include "Testproject.h"
#include <iostream>
#include <string>
#include <thread>

#define MAX_LOADSTRING 100

// Global variables:
HINSTANCE hInst;                                // Aktuelle Instanz
WCHAR szTitle[MAX_LOADSTRING];                  // Titelleistentext
WCHAR szWindowClass[MAX_LOADSTRING];            
HWND Button1;
int i = 0;

我的柜台:

void counterr(HWND hWnd)
{
    i = 5;
    while(i>0)
    {

    i -= 1;
    //UpdateWindow(hWnd);
    PostMessage(hWnd, WM_PRINT, NULL, NULL);
    Sleep(1000);

    }
}

VisualStudio2017的标准窗口和消息循环

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_CREATE:
    {
        Button1 =   CreateWindow(L"Button",L"Counter",WS_VISIBLE|WS_CHILD|WS_BORDER,0,40,100,20,hWnd,(HMENU) 1,nullptr,nullptr);

    break;
}
case WM_COMMAND:
    {
        int wmId = LOWORD(wParam);
        // Menüauswahl bearbeiten:
        switch (wmId)
        {
        case IDM_ABOUT:
            DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
            break;
        case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
        case 1:
        {
            std::thread t1(counterr, hWnd);
            t1.detach();
            break;
        }
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
    }
    break;
case WM_PRINT:
case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        //TODO: Zeichencode, der hdc verwendet, hier einfügen...
        RECT rc;
        RECT rc2 = { 0, 0, 0, 0 };
        int spacer = 3;
        GetClientRect(hWnd, &rc);

        SelectObject(hdc, GetStockObject(DEFAULT_GUI_FONT));
        SetBkMode(hdc, TRANSPARENT);
        SetTextColor(hdc, RGB(0, 0, 0));



        std::wstring strOut = std::to_wstring(i); // or wstring if you have unicode set
        DrawText(hdc, strOut.c_str(), strOut.length(), &rc, DT_SINGLELINE);
        DrawText(hdc, strOut.c_str(), strOut.length(), &rc2, DT_CALCRECT);
        rc.left = rc.left + rc2.right + spacer;
        std::wstring strOut2 = L"heya";
        DrawText(hdc, strOut2.c_str(), strOut2.length(), &rc, DT_TOP | DT_LEFT | DT_SINGLELINE);


        EndPaint(hWnd, &ps);
    }
    break;
case WM_DESTROY:
    PostQuitMessage(0);
    break;
default:
    return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
再次

标准内容和代码

结束

1 个答案:

答案 0 :(得分:2)

执行此操作的常用方法是使用自定义消息ID 调用SendMessage()或PostMessage(),以通知UI有关该线程所做的某些更改。

直接从线程更新UI是不好的做法,因为线程应该只是“工作”而不关心UI如何呈现这项工作的结果。

使用PostMessage,您已经走在正确的轨道上。但是,您应该定义一个自定义消息ID,而不是使用WM_PRINT:

const UINT WM_APP_MY_THREAD_UPDATE = WM_APP + 0;

WM_APP到0xBFFF范围内的消息保留供应用程序私人使用,因此您不必担心某些Windows组件已使用您的消息ID。

您的线程函数然后调用:

PostMessage(hWnd, WM_APP_MY_THREAD_UPDATE, 0, 0);

在您的WndProc中,将case WM_PRINT:替换为:

case WM_APP_MY_THREAD_UPDATE:
    // Tell Windows that the window content is no longer valid and 
    // it should update it as soon as possible.
    // If you want to improve performance a little bit, pass a rectangle
    // to InvalidateRect() that defines where the number is painted.    
    InvalidateRect( hWnd, nullptr, TRUE );
    break;

您的代码还有另一个问题:

您的counterr主题功能会更新全局变量i,而不会考虑同步。在WM_PAINT中输出变量的GUI线程可能不会“看到”该变量已被另一个线程更改并仍然输出旧值。例如,它可能已将变量存储在寄存器中,仍然使用寄存器值而不是重新读取内存中的实际值。当线程在多个CPU核心上运行时,事情会变得更糟,每个线程都有自己的缓存。 它可能一直在您自己的机器上工作,但在用户机器上总是或有时会失败!

同步是一个非常复杂的主题,因此我建议使用您最喜欢的搜索引擎查找“C ++线程同步”并准备好进行一些冗长的阅读。 ; - )

代码的一个简单解决方案是将一个局部变量i添加到线程函数中,并且只在线程内操作这个局部变量(无论如何都是个好主意)。发布WM_APP_MY_THREAD_UPDATE消息时,您将传递本地i作为消息的WPARAM或LPARAM的参数。

void counterr(HWND hWnd)
{
    int i = 5;  // <-- create local variable i instead of accessing global
                //     to avoid thread synchronization issues
    while(i>0)
    {
       i -= 1;

       // Pass local variable with the message
       PostMessage(hWnd, WM_APP_MY_THREAD_UPDATE, static_cast<WPARAM>( i ), 0);
       Sleep(1000);
    }
}

为避免混淆,我会在全局i中添加前缀:

int g_i = 0;

然后在WM_APP_MY_THREAD_UPDATE的case分支中,您将从WPARAM参数更新g_i:

case WM_APP_MY_THREAD_UPDATE:
    g_i = static_cast<int>( wParam );
    InvalidateRect( hWnd, nullptr, TRUE );
    break;

当然你也会在WM_PAINT期间使用g_i:

case WM_PAINT:
    // other code here....
    std::wstring strOut = std::to_wstring(g_i);
    // other code here....
    break;