So I just started with C++ and wanted to create a window with a button that starts an asynchronous thread for a counter that counts from 5 to 0, representing a long time consuming task. The number should've been shown on the Window and get updated every second while the counter is counting. For that the child thread has to communicate in any way with the Message Loop of the main window thread.
I tried to do this by:
Sending an UpdateWindow with the windowhandle of the main window
Sending an PostMessage with the windowhandle of the main window
But in both cases, the window does not get updatet. So I'm suspecting an error by either sending the window handle from the main thread to the child thread or sending the UpdateWindow message from the child thread to the main thread or both or I'm completely off track and everythig is wrong.
Maybe my way of thinking is also wrong and i should do that on another way, still, i don t know how i even should start.
#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;
My Counter:
void counterr(HWND hWnd)
{
i = 5;
while(i>0)
{
i -= 1;
//UpdateWindow(hWnd);
PostMessage(hWnd, WM_PRINT, NULL, NULL);
Sleep(1000);
}
}
standard window and message loop things from 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;
}
standard stuff again and end of Code
The usual way to do this, is to either call SendMessage() or PostMessage() with a custom message ID to notify the UI about some changes made by the thread.
Updating the UI directly from the thread is bad practice, because the thread should only do "work" and not be concerned about how the results of this work will be presented by the UI.
You already were on the right track by using PostMessage. But instead of using WM_PRINT you should define a custom message ID like this:
const UINT WM_APP_MY_THREAD_UPDATE = WM_APP + 0;
Messages in the range WM_APP through 0xBFFF are reserved for private use by the application, so you don't have to worry that some Windows component already uses your message ID.
Your thread function then calls:
PostMessage(hWnd, WM_APP_MY_THREAD_UPDATE, 0, 0);
In your WndProc replace the case WM_PRINT: by:
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;
There is another issue with your code:
Your counterr thread function updates the global variable i without taking synchronization into account. The GUI thread who outputs the variable in WM_PAINT may not "see" that the variable has been changed by the other thread and still output the old value. For instance, it may have stored the variable in a register and still uses the register value instead of rereading the actual value from memory. Matters become worse when threads run on multiple CPU cores, where each thread has its own cache.
It may work all the time on your own machine but always or sometimes fail on users machines!
Synchronization is a very complex topic so I suggest looking up "C++ thread synchronization" using your favorite search engine and be prepared for some lengthy reading. ;-)
A simple solution for your code would be to add a local variable i to the thread function and only operate on this local variable from within the thread (a good idea anyway). When you post the WM_APP_MY_THREAD_UPDATE message, you would pass the local i as the argument for the WPARAM or LPARAM of the message.
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);
}
}
To avoid confusion i would add a prefix to the global i:
int g_i = 0;
Then in the case branch for WM_APP_MY_THREAD_UPDATE you would update g_i from the WPARAM parameter:
case WM_APP_MY_THREAD_UPDATE:
g_i = static_cast<int>( wParam );
InvalidateRect( hWnd, nullptr, TRUE );
break;
Of course you would also use g_i during WM_PAINT:
case WM_PAINT:
// other code here....
std::wstring strOut = std::to_wstring(g_i);
// other code here....
break;
Related
I've a popup window that displays a picture. Each time it displays a picture, the content is changed by another code to make it look like a motion picture.
Without using a while loop, it just displays the picture and the window doesn't hang, it responds perfectly well.
But I'm unable to achieve what I want without a while loop. when I use the while loop to create the motion picture. It works, but after a while the window stops responding.
Here's an example of the window procedure:
LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp){
HDC hdc;
PAINTSTRUCT ps;
switch(msg){
case WM_PAINT:{
hdc = BeginPaint(hWnd, &ps);
Gdiplus::Graphics graph(hdc);
while(true){
Sleep(100);
Image img(L"Test.png");
graph.DrawImage(&img, 0, 0, 1000, 700);
}
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProcW(hWnd,msg,wp,lp);
}
}
As #RetiredNinja said in comments, this code really needs a timer, not a while loop. If you block the window procedure, the window will stop responding to messages. You must return flow to the thread's message loop after each message is processed.
At startup, load your initial image, start the timer, and invalidate the window. Done.
Whenever the timer fires, update the image as needed and invalidate the window to trigger a repaint. Done.
Every time the window procedure receives a WM_PAINT message, draw the current image as-is onto the window. Done.
That is all you need to do. No threads are needed. And the app remains responsive at all times, because no single message is blocked for more than a few milliseconds.
Try something more like this instead:
Image *img = nullptr;
LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp){
switch(msg){
case WM_CREATE:
img = Image::fromFile(L"Test.png", FALSE);
SetTimer(hWnd, 1, 100, NULL);
break;
case WM_DESTROY:
KillTimer(hWnd, 1);
delete img;
PostQuitMessage(0);
break;
case WM_TIMER:{
// update img as needed...
InvalidateRect(hWnd, NULL, TRUE);
break;
}
case WM_PAINT:{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
Gdiplus::Graphics graph(hdc);
graph.DrawImage(img, 0, 0, 1000, 700);
EndPaint(hWnd, &ps);
break;
}
default:
return DefWindowProcW(hWnd, msg, wp, lp);
}
return 0;
}
I am trying to create an animation loop through the use of timers. I want to call a different SetRenderParams(int) after approximately 5-10 seconds. I don't know how timers work and the documentation I can find online is VERY minimal.
I tried if and while statements to break the timer, but I have come to understand it does not work as a loop, so it cannot check counts. I am never able to move to IDT_TIMER2. I am thinking I might not need multiple timers, since I do not mind keeping the same animation speed (which is what the timer essentially accomplices), but a way to alternate SetRenderParams(int) without breaking from the timer, maybe(?).
I checked timerqueues as well, but while I was checking them I got more and more confused... The link I followed to come this far was the following, however this one does not show a way to input multiple animations in sequence, only one. To be clear, I do not want to have multiple animations at the same time. I want animation after animation, distinct from each other. http://www.winprog.org/tutorial/animation.html
The code I posted is clear from all the experiments I tried.
LRESULT Framework::MsgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// Set two timers.
const int IDT_TIMER1 = 1;
const int IDT_TIMER2 = 2;
SetTimer(hWnd, // handle to main window
IDT_TIMER1, // timer identifier
50, // 10-second interval
NULL); // no timer callback
SetTimer(hWnd, // handle to main window
IDT_TIMER2, // timer identifier
20000, // five-minute interval
NULL); // no timer callback
switch (message)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
//=== Add code for Rendering Models);
RenderScene(hdc);
EndPaint(hWnd, &ps);
}
break;
case WM_TIMER:
{
int count = 0;
RECT rcClient;
GetClientRect(hWnd, &rcClient);
switch (wParam)
{
case IDT_TIMER1:
SetRenderParams(13);
InvalidateRect(hWnd, NULL, TRUE);
ReleaseDC(hWnd, hdc);
break;
case IDT_TIMER2:
SetRenderParams(14);
InvalidateRect(hWnd, NULL, TRUE);
ReleaseDC(hWnd, hdc);
break;
}
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
edit: forgot to add the initialized variables.
edit: providing a link to the .sln file https://www.mediafire.com/file/x42hy751n89z5bf/Rasteriser.zip/file
I came up with a "solution" but it is really grindy code. I create an Update method with clocks in it and then I call in inside the WM_TIMER . I have to manually input all the clock commands and since I have around 20 SetRenderParams, we are talking about very unoptimized program. Here is the code:
void Framework::UpdateCube(HWND hWnd)
{
clock_t t;
t = clock();
if (t < 10000.0f)
{
SetRenderParams(13);
InvalidateRect(hWnd, NULL, TRUE);
t = clock();
}
if (t >= 10000.0f && t < 10100.0f)
{
SetRenderParams(10);
InvalidateRect(hWnd, NULL, TRUE);
}
if (t >= 10100.0f && t < 20100.0f)
{
SetRenderParams(15);
InvalidateRect(hWnd, NULL, TRUE);
}
if (t >= 20100.0f && t < 20200.0f)
{
SetRenderParams(10);
InvalidateRect(hWnd, NULL, TRUE);
}
}
.
.
.
case WM_TIMER:
{
RECT rcClient;
GetClientRect(hWnd, &rcClient);
UpdateCube(hWnd);
}
break;
I've just created multiple edit boxes (11x11 controls) based on this article:
https://msdn.microsoft.com/en-us/library/windows/desktop/hh298433%28v=vs.85%29.aspx
Well, not exactly same, but I used the code in case WM_CREATE: block to create huge number of controls.
I use this dialog process on the parent window:
INT_PTR CALLBACK StartupDialogProc(HWND dialog, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg){
case WM_INITDIALOG:
Init_Startup(dialog);
return 1;
/*
case EN_CHANGE:
case WM_CTLCOLOREDIT:
{
HDC hdC = (HDC)wParam;
COLORREF crColorBackground = RGB(255,0,0);
if (crColorBackground)
SetBkColor(hdC, crColorBackground);
SetTextColor( hdC, RGB(12,112,212) );
SetBkMode( hdC, TRANSPARENT );
RECT rect;
GetClientRect( (HWND)lParam, &rect );
HBRUSH hBrush = CreateSolidBrush( RGB(209,209,209) );
//FrameRect( hdC, &rect, hBrush );
Rectangle( hdC, (int)rect.left, (int)rect.top, (int)rect.right, (int)rect.bottom );
DeleteObject( hBrush );
LOGBRUSH lb;
lb.lbStyle = BS_SOLID;
lb.lbColor = RGB(249,249,249);
lb.lbHatch = 0;
CreateBrushIndirect(&lb); // LRESULT
// GetStockObject(NULL_BRUSH);
return 1;
}
break;
*/
case WM_DESTROY:
setts.options.page = GetDlgItemInt(dialog, IDC_O_STARTUP_PAGE, NULL, FALSE);
setts.options.recent = GetDlgItemInt(dialog, IDC_O_STARTUP_RECENT, NULL, FALSE);
break;
case WM_CLOSE:
EndDialog(dialog, FALSE);
break;
case WM_COMMAND:
if (wParam == IDOK) {
EndDialog(dialog, TRUE);
return 0;
}
}
return 0;
}
There is few things unclear to me:
1) if I would like to change color of border for all edit controls from id 5001 to id 5121, how to do that? To me, the commented code does not work (when would it be uncommented). It looks like I have this in incorrect place.
2) how correctly create the dialog processes to all the controls? Because there is big number and could be yet few times higher, should I just call a loop from 5001 to id 5121 and call the function:
INT_PTR CALLBACK EditDlgProc(HWND dialog, UINT msg, WPARAM wParam, LPARAM lParam) - that won't work, because every function would need to have different name.
To change the border color of edit control, you have to subclass the edit control and override WM_NCPAINT. That's a little advanced, and you don't really need it. You can just use WS_EX_CLIENTEDGE flag:
CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT" ...
Also make sure project manifest is setup so you get modern window's look.
This would be an error if it had not been commented out:
case EN_CHANGE:
case WM_CTLCOLOREDIT:
Each case should end in break; or return 0;
Moreover, WM_CTLCOLOREDIT should return a brush which was created on heap. It should not return 1. See documentation :
There are also other errors in that section, you should just get rid of that. See this example for painting.
i am trying to get used to WinApi and decided to make a GUI for a sudoku-generator i programmed.
It should adjust dynamicly to the windowsize the user chooses.
So far everything works as inteded, but if the WM_PAINT-msg is sent too often in a short window of time (eg changing the size of the window) the program crashes.
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
stringstream ss; //not used
RECT rect;
int w;
int h;
HBRUSH coluns=CreateSolidBrush(RGB(50,120,180));
HBRUSH colsel=CreateSolidBrush(RGB(80,150,220));
HBRUSH colmso=CreateSolidBrush(RGB(50,70,190));
switch (message)
{
case WM_SIZE: //
{
GetWindowRect(hwnd,&rect);
menu.wndw=rect.right-rect.left; //menu is a class to store important information
menu.wndh=rect.bottom-rect.top;
h=menu.wndh;
w=menu.wndw;
for(int i=1;i<10;i++)
{
for(int j=1;j<10;j++)
{
menu.feld[i][j].SetSpace((w/4)+((i-1)*(w/20))+i+(2*((i-1)/3)),(h/4)+((j-1)*(h/20))+j+(2*((j-1)/3)),(w/4)+((i)*(w/20))+i+(2*((i-1)/3)),(h/4)+((j)*(h/20))+j+(2*((j-1)/3)));
}
} //feld is a class wich exists in a 10x10 array with the 0s not being used
InvalidateRect(hwnd,NULL, TRUE);
}
break;
case WM_PAINT:
{
RECT re;
w=menu.wndw;
h=menu.wndh;
hdc = BeginPaint(hwnd,&ps);
re.left=(w/4)-4;
re.top=(h/4)-4;
re.right=(w/4)+9*(w/20)+18;
re.bottom=(h/4)+9*(h/20)+18;
FillRect(hdc,&re,CreateSolidBrush(RGB(0,0,0)));
for(int i=1;i<10;i++)
{
for(int j=1;j<10;j++)
{
re=menu.feld[i][j].GetSpace();
if(menu.feld[i][j].GetSelect()==uns)
if(FillRect(hdc,&re,coluns)==0)
MessageBox(hwnd, "fail","fail",0);
if(menu.feld[i][j].GetSelect()==mso)
if(FillRect(hdc,&re,colmso)==0)
MessageBox(hwnd, "fail","fail",0);
if(menu.feld[i][j].GetSelect()==sel)
if(FillRect(hdc,&re,colsel)==0)
MessageBox(hwnd, "fail","fail",0);
}
}
EndPaint(hwnd, &ps);
}
break;
http://www.pic-upload.de/view-22113118/Unbenannt.png.html
here is a picture of what the executed program looks like.
Now as described earlier the program will crash if u change the windowsize in a lot of small steps. After calling the MW_PAINT msg for ~10 times the window will just freeze with 1 of the rects being white instead of the desired color (random one, different every time).
my assumption is that i need to release some kind of resources because mby a stack will overflow or smth, but i have really no idea where i could have a leak in my program.
i would be very grateful if anyone could help me.
You create three brush handles every single time your window procedure executes. These handles are never tidied up. And then inside the WM_PAINT handler, you create a brush which you pass to FillRect and so can never destroy it.
So you leak three handles every time the window procedure executes (which happens a lot), and one more every time it handles WM_PAINT. Simply put, your program leaks like a sieve!
You should consider creating these brushes when the window is created, and destroying them when the window is destroyed. Or perhaps creating them inside the WM_PAINT handler, and destroying them as soon as you have finished using them. But since they have constant colors it is probably best to create 4 brushes up front, once and for all.
You are leaking GDI resources as member David Heffernan said.
Here is the example of how to properly use brushes in your application-pay attention to WM_COMMAND handler in that example.
If you do not use stock GDI objects you must delete them after you are done working with them.
Here is the simple example that fills window with red brush in WM_PAINT handler:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPain( hdc, &ps );
HBRUSH hbrRedBrush = CreateSolidBrush( RGB( 255, 0, 0 ) );
RECT r;
GetClientRect( hWnd, &r );
FillRect( hdc, &r, hbrRedBrush );
DeleteObject( hbrRedBrush ); //you must delete GDI object!
EndPaint( hWnd, &ps );
}
return 0L;
In your case, I would make 4 static brushes and rework my code a little, adding the proper cleanup in WM_CLOSE handler. Below are the suggested changes:
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
// add static before HBRUSH
static HBRUSH coluns=CreateSolidBrush(RGB(50,120,180));
static HBRUSH colsel=CreateSolidBrush(RGB(80,150,220));
static HBRUSH colmso=CreateSolidBrush(RGB(50,70,190));
static HBRUSH BlackBrush = CreateSolidBrush(RGB(0,0,0));
switch (message)
{
// this is the problematic handler
case WM_PAINT:
{
//the changed part
FillRect( hdc, &re, BlackBrush );
}
break;
case WM_CLOSE:
{
DeleteObject( BlackBrush );
DeleteObject( coluns );
DeleteObject( colsel );
DeleteObject( colmso );
// other clean up code
}
break;
IMPORTANT NOTE:
This time you used FillRect API, but next time you might load bitmaps and other stuff that require from you to restore HDC into original state after you are done with drawing.
You do that like this:
HBITMAP bmpOld = (HBITMAP)SelectObject( hdc, myBitmap );
// bmpOld stores the original state of the device context
// you do something with myBitmap
// then you return device context into original state
// by selecting the original value, bmpOld, back into device context
SelectObject( hdc, oldBmp );
DeleteObject( myBitmap );
Again, pay attention to WM_COMMAND handler in the above MSDN example to see how they did it.
Here is the link to a great Win32 API tutorial for beginners-give it a go.
In the end I recommend you this tool for detecting GDI leaks.
If you have further questions leave a comment and I will reply as soon as possible.
Best regards and good luck!
I used DrawText function within WM_TIMER, but it dont work. How to fix this? Thank you!
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_CREATE:
SetTimer(hwnd,23, 1000,NULL);
break;
//case WM_TIMER: ***** dont work *****
case WM_PAINT: // ***** work, but used 25% CPU *****
{
RECT rect;
HFONT hFont;
hdc = BeginPaint(hwnd, &ps);
hFont = CreateFontA(16,0,0,0,FW_NORMAL,FALSE,FALSE,FALSE,DEFAULT_CHARSET,OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS,ANTIALIASED_QUALITY, VARIABLE_PITCH,TEXT("Arial"));
SelectObject(hdc,hFont);
SetRect(&rect, 3, 3, 90, 50);
SetTextColor(hdc, RGB(0,0,255));
time_t rawtime;
struct tm * timeinfo;
char buffer [80];
time ( &rawtime );
timeinfo = localtime ( &rawtime );
strftime (buffer,80,"%I:%M:%S %p\n%m/%d/%Y",timeinfo);
wchar_t wtext[30];
mbstowcs(wtext, buffer, strlen(buffer)+1);//Plus null
LPWSTR ptr = wtext;
DrawTextW(hdc, ptr, -1,&rect, DT_NOCLIP | DT_CENTER);
DeleteObject(hFont);
InvalidateRect(hwnd, &rect, TRUE);
UpdateWindow(hwnd);
EndPaint(hwnd, &ps);
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wparam, lparam);
}
return 0;
}
Do not call InvalidateRect() or UpdateWindow() from WM_PAINT, or you will create an infinite loop of repaints.
Do not paint from the WM_TIMER. It can be done (with GetWindowDC() instead of BeginPaint() but it isn't such a good idea.
Instead put the InvalidateRect() in the WM_TIMER and leave the drawing code in WM_PAINT. You can optimize, as #typ1232 said in the comments, by creating the font only once, but that's not strictly necessary.
The UpdateWindow() call should not generally be necessary, unless you are in a tight CPU loop and need to show the window just now: if the invalidation is done in a timer and the timeout is not too short you won't need it. But if your timeout is very short you can force the redraw calling UpdateWindow() just after InvalidateRect().
Your WM_TIMER code should prepare the string to be drawn, save it and then call InvalidateRect. The WM_TIMER code can not draw directly, and one reason is that BeginPaint will not work properly during a WM_TIMER message. BeginPaint is only defined during a WM_PAINT message. So WM_TIMER can prepare the data to be drawn, but then use InvalidateRect to request that a WM_PAINT be generated.
You must also remove the InvalidateRect and UpdateWindow calls from the WM_PAINT code. They will cause an infinite loop of painting.