Поэтому я только начал с C ++ и хотел создать окно с кнопкой, которая запускает асинхронный поток для счетчика со счетчиком от 5 до 0, представляющего длительную задачу. Номер должен был отображаться в окне и обновляться каждую секунду, пока счетчик считает. Для этого дочерний поток должен каким-либо образом связываться с циклом сообщений потока главного окна.
Я пытался сделать это путем:
Но в обоих случаях окно не обновляется. Так что я подозреваю ошибку, либо отправляя дескриптор окна из основного потока в дочерний поток, либо отправляя сообщение 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;
}
снова стандартный материал и конец кода
Обычный способ сделать это, это либо вызвать SendMessage () или PostMessage () с пользовательский идентификатор сообщения уведомить пользовательский интерфейс о некоторых изменениях, внесенных потоком.
Обновление пользовательского интерфейса непосредственно из потока является плохой практикой, поскольку поток должен выполнять только «работу» и не заботиться о том, как результаты этой работы будут представлены в пользовательском интерфейсе.
Вы уже были на правильном пути, используя PostMessage. Но вместо использования WM_PRINT вы должны определить собственный идентификатор сообщения, например:
const UINT WM_APP_MY_THREAD_UPDATE = WM_APP + 0;
Сообщения в диапазоне от WM_APP до 0xBFFF зарезервированы для частного использования приложением, поэтому вам не нужно беспокоиться о том, что какой-то компонент Windows уже использует ваш идентификатор сообщения.
Ваша функция потока тогда вызывает:
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
без принятия синхронизация в учетную запись. Поток GUI, который выводит переменную в WM_PAINT, может не «видеть», что переменная была изменена другим потоком, и все равно выводить старое значение. Например, он мог сохранить переменную в регистре и все еще использовать значение регистра вместо перечитывания фактического значения из памяти. Ситуация ухудшается, когда потоки работают на нескольких ядрах ЦП, где каждый поток имеет свой собственный кэш.
Он может работать все время на вашей собственной машине, но всегда или иногда не работать на компьютерах пользователей!
Синхронизация — очень сложная тема, поэтому я предлагаю поискать «Синхронизация потоков 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 вы бы обновили g_i из параметра WPARAM:
case WM_APP_MY_THREAD_UPDATE:
g_i = static_cast<int>( wParam );
InvalidateRect( hWnd, nullptr, TRUE );
break;
Конечно, вы также будете использовать g_i во время WM_PAINT:
case WM_PAINT:
// other code here....
std::wstring strOut = std::to_wstring(g_i);
// other code here....
break;
Других решений пока нет …