2017-02-18 48 views
2

所以我剛剛開始使用C++,並希望創建一個窗口,其中包含一個按鈕,用於啓動計數器的異步線程,該計數器的計數從5到0,表示耗時很長的任務。該數字應該已經顯示在窗口上,並在計數器計數時每秒更新一次。爲此,子線程必須以任何方式與主窗口線程的消息循環進行通信。 我想這將做到:C++從異步線程更新窗口窗口

  • 發送的UpdateWindow與主窗口
  • 發送的PostMessage的與主窗口

但在這兩種情況下,窗口的windowhandle的windowhandle不會得到更新。所以我懷疑是通過從主線程窗口句柄發送到子線程或發送UpdateWindow消息從子線程到主線程或兩者,或者我完全偏離軌道,everythig是錯誤的。

也許我的思維方式也是錯的,我應該用另一種方式來做,但我不知道我該怎麼開始。

#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; 

我的計數器:從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; 
} 

再次標準的東西

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

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

    } 
} 

標準窗口和消息循環的東西和結束碼的

+0

您不應該從其他線程更新UI線程。它會有未定義的行爲 – Asesh

+0

那麼我應該怎麼做呢? – spaghetticode

+0

此主題將回答您的問題:http://stackoverflow.com/questions/3783713/c-win32-executing-a-method-on-ui-thread-due-to-an-event-on-background-thread – Asesh

回答

2

的常用的方法是使用自定義消息ID來調用SendMessage()或PostMessage()以通知UI該線程所做的一些更改。

直接從線程更新UI是不好的做法,因爲線程應該只做「工作」而不關心UI如何呈現這項工作的結果。

您已經通過使用PostMessage在正確的軌道上。但是,而不是使用WM_PRINT你應該這樣定義自定義消息ID:

const UINT WM_APP_MY_THREAD_UPDATE = WM_APP + 0;

消息通過0xBFFF範圍WM_APP預留給應用程序私用,所以你不必擔心一些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; 

然後在case分行WM_APP_MY_THREAD_UPDATE你會從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; 
+0

謝謝,你的回答非常有幫助,程序現在正在運行。 – spaghetticode