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 have a Window procedure which I have defined in a class and I need to be able to access the class' members using the this pointer.
The Window procedure:
static LRESULT CALLBACK TextWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
MyCustomClass* self =
reinterpret_cast<MyCustomClass*>(
GetWindowLongPtr(hWnd, GWLP_USERDATA) // Not recieving the pointer!
);
switch(message) {
case WM_DESTROY: {
SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)self->origWndProc); // I am subclassing btw.
break;
}
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
HBRUSH hBrush = (HBRUSH)GetStockObject(NULL_BRUSH);
FillRect(hdc, &ps.rcPaint, hBrush);
SetBkMode(hdc, TRANSPARENT);
DrawText(hdc, L"OK", -1, &ps.rcPaint, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
SetBkMode(hdc, OPAQUE);
EndPaint(hWnd, &ps);
DeleteObject(hBrush);
break;
}
case WM_LBUTTONDOWN: {
printf("%d", self->x); // Crashes the program
break;
}
}
return CallWindowProc(self->origWndProc, hWnd, message, wParam, lParam); // Subclassing!
}
Now after creating the window (a text control in my case) I am immediately setting the pointer to the class using SetWindowLongPtr, like this:
// Variable in the class that holds the `HWND` data to the text control.
this->textControl = CreateWindow(
L"STATIC",
L"Hello World!",
WS_CHILD | WS_VISIBLE | SS_NOTIFY,
this->x, this->y, this->width, this->height, // Position and size, members defined in the class
this->parentWindow, // Parent window.
0,
NULL,
NULL
);
SetWindowLongPtr(this->textControl, GWLP_USERDATA, (LONG_PTR)this); // Setting the pointer
this->origWndProc = (WNDPROC)SetWindowLongPtr(this->textControl, GWLP_WNDPROC, (LONG_PTR)this->TextWndProc); // Setting the window procedure for "Subclassing"!
But the problem is I am not receiving the this pointer when I am calling GetWindowLongPtr as seen in the Window procedure.
Am I calling it wrong? I don't know what is wrong.
Any help would be greatly appreciated! Thank you in advance!
How can I delete or hide the current textout to add new text? currently, as he adds another textout, the text overlaps the text.
I tried to use InvalidateRect(hWnd, NULL, TRUE); but I don't see any difference.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
TextOut(hdc, 5, 5, text.c_str(), _tcslen(_T(text.c_str())));
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
break;
}
return 0;
}
Your call to TextOut is in your WM_PAINT handler. This means that the text will always be drawn on each WM_PAINT, making your call to InvalidateRect practically useless.
One way to fix this would be to have a boolean (drawText) to indicate whether you want to draw the text or not. Then in your function to clear the text:
drawText = FALSE;
InvalidateRect(hWnd, NULL, TRUE);
And in your WndProc:
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
if(drawText)
TextOut(hdc, 5, 5, text.c_str(), _tcslen(_T(text.c_str())));
EndPaint(hWnd, &ps);
}
break;
In your case, the InvalidateRect call will trigger a WM_PAINT message, causing TextOut() to be called again. The answer of #mnistic is a good solution. But I think you should really put TextOut method into the real event handling (such as OnButtonClickEvent) instead of putting it into WM_PAINT.
Another technique is to re-draw the text with the background colour, something like:
HDC hdc = ::GetDC(this->m_hWnd);
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, clrBackground);
TextOut(hdc, 5, 5, text.c_str(), _tcslen(_T(text.c_str())));
UpdateTextSomewhere(&text);
SetTextColor(hdc, clrText);
TextOut(hdc, 5, 5, text.c_str(), _tcslen(_T(text.c_str())));
::ReleaseDC(this->m_hWnd, hdc);
clrX are of type COLORREF. They might be set up in the form constructor orOnInitDialog e.g. clrText = RGB(0, 0, 0);.
This technique only works if the form background is a single, solid colour.
I am trying to build software with 3d rendering capabilities without using external libraries. I'm a little confused with GetDC() and ReleaseDC(). If I write the following code after my LRESULT() loop, the program runs for several seconds and then freezes. (I have created two dc's for double buffering with BitBlt())
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc = GetDC(hWnd);
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP membmp = CreateCompatibleBitmap(hdc, width, height);
SelectObject(memdc, membmp);
...
switch (message) {
...
}
}
However, according to this linkabout SetPixel() crashing after a time, I should move GetDC() outside of the loop. However, I must be doing it wrong, because if I do that...
HDC hdc = GetDC(hWnd);
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP membmp = CreateCompatibleBitmap(hdc, width, height);
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
TCHAR greeting[] = _T("program_name");
PAINTSTRUCT ps;
SelectObject(memdc, membmp);
...
}
... I realize that I cannot SelectObject() outside of the LRESULT loop. In fact, visual studio isn't even going to live with GetDC(hWnd) before the LRESULT loop, I expect because it is created in the first line of the loop. So, woefully, my only option seems to be to replace hWnd with NULL.
HDC hdc = GetDC(NULL);
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP membmp = CreateCompatibleBitmap(hdc, width, height);
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
TCHAR greeting[] = _T("program_name");
PAINTSTRUCT ps;
SelectObject(memdc, membmp);
...
}
Now I can no longer clear the window, as that relies on InvalidateRect(hWnd, NULL, TRUE). So it runs indefinitely, yet draws outside of the window and cannot erase.
I am new to programming and if anyone can point me in a direction or let me know what's going on, I would appreciate it. :)
If you're new to programming and c++ you should first learn the basics of the language and not start with using the WinAPI.
Also your "LRESULT() loop" is not a loop but rather a callback function called by your message loop.
Concerning your question: Your application should draw its windows content when it receives a WM_PAINT-message.
Your code is setup all wrong. Try this instead:
HDC hmemdc = NULL;
HBITMAP membmp = NULL;
HBITMAP oldbmp = NULL;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_CREATE: {
HDC hdc = GetDC(hWnd);
membmp = CreateCompatibleBitmap(hdc, width, height);
memdc = CreateCompatibleDC(hdc);
oldbmp = (HBITMAP) SelectObject(memdc, membmp);
ReleaseDC(hWnd, hdc);
break;
}
case WM_DESTROY: {
SelectObject(memdc, oldbmp);
DeleteDC(memdc); memdc = NULL;
DeleteObject(membmp); membmp = NULL;
break;
}
case WM_ERASEBKGND: {
return 1;
}
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
BitBlt(hdc, 0, 0, width, height, memdc, 0, 0, SRCCOPY);
EndPaint(hWnd, &ps);
break;
}
...
}
return DefWindowProcc(hWnd, message, wParam, lParam);
}
Then, you can simply draw your 3D images into hmemdc/membmp as needed, and call InvalidateRect() whenever you want to signal to the OS that the window needs to be redrawn with the latest bitmap.
Though, if you are going to use a memory bitmap for your window, you might consider using UpdateLayeredWindow() instead, and get rid of the WM_PAINT/WM_ERASEBKGND handlers completely.
I'm just trying to draw an ellipse:
case WM_PAINT:
hdc = BeginPaint(parentWindow, &ps);
Ellipse(hdc, x, y, width, height);
EndPaint(parentWindow, &ps);
, and then erase it with drawing a new ellipse with some new parameters every second using timer:
case WM_CREATE:
SetTimer(hWnd, 1, 1000, NULL);
break;
case WM_TIMER:
x += 5;
InvalidateRect(hWnd, NULL, TRUE);
break;
But ellipses are not erased and layered:
However, i tried to trace WM_ERASEBKGND and it really is sent every InvalidateRect.
Full code:
#include <Windows.h>
#include <windowsx.h>
#include <tchar.h>
#include <iostream>
TCHAR szWindowClass[] = TEXT("CreateThreadWindow");
TCHAR szAppName[] = TEXT("CreateThreadExample");
BOOL InitWindow(HINSTANCE, int);
ATOM MyRegisterClass(HINSTANCE);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HWND parentWindow;
MSG msg;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MyRegisterClass(hInstance);
if (!InitWindow(hInstance, nCmdShow))
return FALSE;
BOOL bRet;
while ((bRet = GetMessage(&msg, (HWND)NULL, 0, 0)) != 0)
{
if (bRet == -1)
return FALSE;
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASS wndClass;
memset(&wndClass, 0, sizeof(wndClass));
wndClass.lpfnWndProc = WndProc;
wndClass.hInstance = hInstance;
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = szWindowClass;
return RegisterClass(&wndClass);
}
BOOL InitWindow(HINSTANCE hInstance, int nCmdShow)
{
parentWindow = CreateWindow(szWindowClass, szAppName, WS_OVERLAPPEDWINDOW,
300, 0, 600, 600, NULL, NULL, hInstance, NULL);
ShowWindow(parentWindow, nCmdShow);
return TRUE;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wparam, LPARAM lparam)
{
PAINTSTRUCT ps;
HDC hdc;
static int x = 0, y = 0, width = 200, height = 100;
switch (message) {
case WM_ERASEBKGND:
_RPT1(0, "%s\n", "erase");
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
Ellipse(hdc, x, y, width, height);
EndPaint(hWnd, &ps);
break;
case WM_CREATE:
SetTimer(hWnd, 1, 1000, NULL);
break;
case WM_TIMER:
x += 5;
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, message, wparam, lparam);
}
}
Your code isn't erasing anything. It's just drawing an ellipse at the specified coordinates. The previously-drawn ellipse is still there.
You mention the WM_ERASEBKGND message, but there are two reasons why that isn't working for you:
In your window procedure (WndProc), you handle the WM_ERASEBKGND message explicitly, which means that it doesn't get passed to the default window procedure (DefWindowProc). A better way to write your window procedure would be the following:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wparam, LPARAM lparam)
{
static int x = 0, y = 0, width = 200, height = 100;
switch (message) {
case WM_ERASEBKGND:
{
_RPT1(0, "%s\n", "erase");
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
Ellipse(hdc, x, y, width, height);
EndPaint(hWnd, &ps);
return 0;
}
case WM_CREATE:
{
SetTimer(hWnd, 1, 1000, NULL);
break;
}
case WM_TIMER:
{
x += 5;
InvalidateRect(hWnd, NULL, TRUE);
return 0;
}
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
default:
break;
}
return DefWindowProc(hWnd, message, wparam, lparam);
}
Now, the default window procedure gets called every time, unless you explicitly return from inside of a case label.
When you register your window class (inside of MyRegisterClass), you zero all fields of the WNDCLASS structure and then explicitly initialize a couple of them. You don't explicitly initialize the hbrBackground field, so it is being set to 0. And when hbrBackground is 0,
When this member is NULL, an application must paint its own background whenever it is requested to paint in its client area. To determine whether the background must be painted, an application can either process the WM_ERASEBKGND message or test the fErase member of the PAINTSTRUCT structure filled by the BeginPaint function.
This means that the default window procedure isn't doing anything in response to the WM_ERASEBKGND message because you didn't give your window a background brush.
You will either need to set hbrBackground to something like COLOR_WINDOW + 1, or you will need to add code to your WM_ERASEBKGND message handler to erase the window's background yourself.
Or, perhaps an even better option would be to forget about the WM_ERASEBKGND message altogether, as many Windows programmers do, because this two-step erase-and-paint approach tends to cause flicker. Leave the hbrBackground field set to NULL, don't do anything in response to the WM_ERASEBKGND message, and do your erasing at the top of the WM_PAINT handler:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// Erase background of entire client area.
RECT rcClient;
GetClientRect(hWnd, &rcClient);
FillRect(hdc, &rcClient, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
// Do normal drawing.
Ellipse(hdc, x, y, width, height);
EndPaint(hWnd, &ps);
return 0;
}