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.
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
I am trying to load a bitmap in a Win32 application, but for some strange reason the bitmap does not load. Here is what I have so far:
HANDLE hImg = LoadImageW(
NULL,
L"img.bmp",
IMAGE_BITMAP,
0,
0,
LR_LOADFROMFILE
);
if (hImg == NULL) {
std::cout << GetLastError();
}
Compiled on GCC 8.1.0 with -Wall -municode.
Nothing is output to the console, so there are no errors. However, the image never shows up. These questions seem to address a similar issue, but I have had a look at them and couldn't find a solution:
I cannot load image from folder using win32
Win32 application. HBITMAP LoadImage fails to load anything
Where could the problem be?
Full code:
#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
#include <iostream>
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) {
const wchar_t CLASS_NAME[] = L"Window Class";
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
HWND hwnd = CreateWindowEx(
0,
CLASS_NAME,
L"My Application",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL
);
if (hwnd == NULL) {
return 0;
}
ShowWindow(hwnd, nCmdShow);
HANDLE hImg = LoadImageW(
NULL,
L"img.bmp",
IMAGE_BITMAP,
0,
0,
LR_LOADFROMFILE
);
if (hImg == NULL) {
std::cout << GetLastError();
}
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
EndPaint(hwnd, &ps);
break;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
You are merely loading the bitmap into memory, but you are not actually displaying it anywhere. You need to either:
create a child STATIC control on your window, giving it the SS_BITMAP window style, and then send it a STM_SETIMAGE message.
have your window's WM_PAINT message handler select the bitmap into an in-memory HDC using CreateCompatibleDC() and SelectObject(), and then copy that HDC onto your window's HDC (returned by BeginPaint()) using BitBlt() or StretchBlt().
You just need to change the window procedure to do all the drawing in WM_PAINT. Once the image is loaded successfully, create a memory DC, select the bitmap into the memory DC, and draw the memory DC onto the target window DC.
When the bitmap handle from LoadImage is no longer needed, it should be deleted DeleteObject (or DestroyCursor/DestroyIcon depending on what resource was loaded)
HANDLE hImg = NULL;
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
if (!hImg)
hImg = LoadImage(NULL, L"img.bmp", IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
if (!hImg) { DWORD err = GetLastError(); std::cout << err; }
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
if (hImg) {
BITMAP bm;
GetObject(hImg, sizeof(bm), &bm);//get bitmap dimension
auto memdc = CreateCompatibleDC(hdc);
auto oldbmp = SelectObject(memdc, (HBITMAP)hImg);
BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, memdc, 0, 0, SRCCOPY);
SelectObject(memdc, oldbmp);//restore memdc
DeleteDC(memdc);//delete memdc, we don't need it anymore
}
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY:
if (hImg) DeleteObject(hImg);//release resource
hImg = NULL;
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
When using GetLastError, make sure the function is called immediately after failure. Example:
if (hImg == NULL)
{
DWORD err = GetLastError();
std::cout << err << '\n';
}
If UNICODE is already defined, we can use LoadImage instead of LoadImageW
I have a timer that is responsible for displaying the GIF image frame by frame. I noticed that when I right-clicked and hold the titilebar the timer pause for I think 1 second and when I left-clicked and hold the titlebar`, the timer pause until I released the mouse.
LRESULT CALLBACK GDIHelper::StaticControlProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) {
switch(uMsg) {
case WM_TIMER:
{
OnTimer(); // Do something on timer.
InvalidateRect(staticControl, NULL, FALSE);
return 0;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
Graphics g(hdc);
g.DrawImage(m_pImage, 0, 0, width, height);
EndPaint(hwnd, &ps);
return TRUE;
}
case WM_DESTROY:
{
return 0;
}
}
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch(message) {
case WM_CREATE: {
staticControl = CreateWindowEx(0, L"STATIC", NULL, WS_CHILD | WS_VISIBLE | SS_OWNERDRAW, xPosition, yPosition, width, height, hWnd, NULL, NULL, NULL); //create the static control.
SetWindowSubclass(staticControl, &StaticControlProc, unique_id, 0);
gdiHelper.AnimateGIF();
break;
}
case WM_PAINT:
{
HDC hdc;
PAINTSTRUCT ps;
hdc = BeginPaint(hWnd, &ps);
//Paint other images and text here...
EndPaint(hWnd, &ps);
break;
}
case WM_DESTROY:
{
gdiHelper.Destroy();
PostQuitMessage(0);
break;
}
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
and here are the functions responsible for creating the timer.
void GDIHelper::OnTimer() {
if(isPlayable) {
GUID Guid = FrameDimensionTime;
m_pImage->SelectActiveFrame(&Guid, m_iCurrentFrame);
m_iCurrentFrame = (++m_iCurrentFrame) % m_FrameCount;
}
}
void GDIHelper::AnimateGIF() {
if(m_bIsPlaying == TRUE) {
return;
}
m_iCurrentFrame = 0;
GUID Guid = FrameDimensionTime;
m_pImage->SelectActiveFrame(&Guid, m_iCurrentFrame);
SetTimer(staticControl, 120, ((UINT*)m_pItem[0].value)[m_iCurrentFrame] * 10, NULL);
++m_iCurrentFrame;
m_bIsPlaying = TRUE;
}
How to possibly prevent that?
The InvalidateRect() will not immediately redraw your control. It will simply schedule a future redraw for a specific rectangular area of the window. Making the animation freeze when you clicked or hold on the titlebar. Use InvalidateRect() followed by UpdateWindow(), this will forcefully perform immediate redrawing of the specified area of the window.
But it won't just solve the issue. Forget about your timer and use a non-blocking thread instead together with InvalidateRect() and UpdateWindow().
int animation_duration = 0;
void GDIHelper::TheAnimation() { //This function should be static.
if(m_bIsPlaying == TRUE) {
return;
}
m_iCurrentFrame = 0;
GUID Guid = FrameDimensionTime;
m_pImage->SelectActiveFrame(&Guid, m_iCurrentFrame);
++m_iCurrentFrame;
m_bIsPlaying = TRUE;
animation_duration = ((UINT*)m_pItem[0].value)[m_iCurrentFrame] * 10;
while(isPlayable) { //Make sure to set isPlayable to false on destroy to stop this thread.
std::this_thread::sleep_for(std::chrono::milliseconds(animation_duration)); //instead of timer, use sleep.
m_pImage->SelectActiveFrame(&Guid, m_iCurrentFrame);
m_iCurrentFrame = (++m_iCurrentFrame) % m_FrameCount;
InvalidateRect(staticControl, NULL, FALSE);
UpdateWindow(staticControl); //perform immediate redrawing
}
}
LRESULT CALLBACK GDIHelper::StaticControlProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) {
switch(uMsg) {
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
Graphics g(hdc);
g.DrawImage(m_pImage, 0, 0, width, height); //draw your image.
EndPaint(hwnd, &ps);
return TRUE;
}
....
}
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}
Then just start the thread and use detach() for non-blocking UI.
std::thread t(TheAnimation);
t.detach(); // this will be non-blocking thread.
Read the comments in the code for some clarification and don't forget to set isPlayable to false on destroy to stop the thread.
i have two rectangle in my WM_PAINT and i wanted to draw Frame Rect on it once WM_MOUSE CLICK EVENT is triggered that toggle on each rectangle. is this even possible?
See #RemyLebeau's comment above regarding your mouse clicks. Then, in your WndProc, something like:
switch (uMsg)
{
// ...
case WM_PAINT:
{
PaintStruct ps;
HDC hDC = BeginPaint (hWnd, &ps);
HBRUSH hBrush = (HBRUSH) GetStockObject (LTGRAY_BRUSH); // say
if (draw_first_rectangle)
FrameRect (hDC, &my_first_rectangle, hBrush);
if (draw_second_rectangle)
FrameRect (hDC, &my_second_rectangle, hBrush);
EndPaint (hWnd, &ps);
return 0;
}
// ...
}
return DefWindowProc (hWnd, uMsg, wParam, lParam);
I'm sure you can fill in the blanks.
I want the text change as time changes like a clock, however, it doesn't change. I found that the text will change when I minimize or maximize the window.
I guess I should redraw the window, but I am new to windows api, anyone good advice?
This is the main.cpp codeļ¼
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
//....
}
void Paint(HWND hwnd, LPCTSTR txt)
{
UpdateWindow(hwnd);
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
DrawText(hdc, txt, -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
}
// Thread function
DWORD WINAPI ThreadFun(LPVOID lpParameter)
{
HWND hwnd = (HWND)lpParameter;
while (1)
{
string dateStr = Ticker::GetCurrentTimeStr();
Paint(hwnd, dateStr.c_str());
}
return 0;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
{
CreateThread(NULL, 0, ThreadFun, hwnd, 0, NULL);
}
return 0;
case WM_PAINT:
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
You need to call InvalidateRect to tell the system the drawing area has changed.
Edit
Instead of creating a new thread, you can create a timer with SetTimer (see example) and respond to WM_TIMER message. Call InvalidateRect in response to WM_TIMER, to repaint the window every second.
Do all of the painting in response to WM_PAINT.
Use BeginPaint/EndPaint only in response to WM_PAINT, don't use BeginPaint/EndPaint elsewhere.
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;
}