In the following code
Draw();
while (WM_QUIT != msg.message)
{
msg = PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE);
if (msg)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Draw();
}
why draw was called inside and outside the event loop?
That's a typical game loop. It constantly draw frames, the PeekMessage() call ensures Windows messages are dispatched as normal, typically mouse and keyboard input to make the game interactive.
It looks like it's called to do an initial draw before it starts the message pump. If there are no messages, it will have drawn something.
Related
I'm trying to mimic SFML's PollEvent(Event &event) function in Windows. It seems far more complicated that I imagined. Note that I already encapsulated the window procedure function in my class.
There could be many "window events" in my program - WindowMoved, WindowResized etc.
My first attempt was to have a private data member in the class, defined as WindowEvent *_lastWindowEvent. This variable will be set if PeekMessage() returns a non-zero value, just before DispatchMessage() is called. Then, winProc() will edit _lastWindowEvent, depending on the message it will receive.
The drawback here is that I noticed that winProc() may be called with a MSG parameter regardless of DispatchMessage(), like with the WM_SETCURSOR message.
Then I thought about having instead a std::queue<WindowEvent> in my class, when winProc() continuously pushes WindowEvents to it. The problem here is that sometimes the window procedure function keeps getting messages and won't return. This happens when I drag-move the window (then the WM_MOVING message is continuously called, along with other messages). The code after DispatchMessage() will not run until I release my mouse. This also happens when resizing the window.
Did I grasp anything wrong? How do you think such PollEvent function can be implemented?
Given that PollEvent is primarily for a game loop style design, you can probably poll for what you need while simultaneously servicing the Windows event loop:
class Window
{
HWND _hwnd; // Win32 handle to the window
RECT _lastWindowSize; // last known window size
POINT _lastMousePos; // last known mouse position on window
BYTE _lastKeyboardState[256]; // last known key state
std::list<Event> _events; // unprocessed events
public:
bool PollEvent(Event* pEvent);
};
bool Window::PollEvent(Event* pEvent)
{
// return any previously queued events
if (_events.size() > 0)
{
*pEvent = _events.pop_front();
return true;
}
// process 1 windows event message
if (PeekMessage(&msg, _hWnd, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
if (msg.msg == WM_QUIT)
{
*pEvent = EXIT_EVENT; // special handling for WM_QUIT
return true;
}
}
// -----------------------------------------
// poll keyboard state
BYTE kbState[256];
GetKeyboardState(kbState);
bool isKeyboardEvent = false;
if (memcmp(_lastKeyboardState, kbState, 256) != 0)
{
// not shown
// compute diff of kbState and _lastKeyboardState
// generate a keyboard event and add to the queue
Event kbevt;
kbevt.type = KeyEvent;
kbevt.code = <computed code based on diff above>
_events.push_back(kbevt);
}
memcpy(_lastKeyboardState, kbState, 256);
// -----------------------------------------
// -----------------------------------------
// poll window size changes
RECT rectWindowSize;
int width, height, oldwidth, oldheight;
GetClientRect(&rectWindowSize);
width = rectWindowSize.right - rectWindowSize.left;
height = rectWindowSize.bottom - rectWindowSize.top;
oldwidth = _lastWindowSize.right - _lastWindowSize.left;
oldheight = _lastWindowSize.bottom - _lastWindowSize.top;
if ((width != oldwidth) || (height != oldheight))
{
Event sizeEvent;
sizeEvent.type = SizeEvent;
sizeEvent.width = width;
sizeEvent.height = height;
_events.push_back(kbevt);
}
_lastWindowSize = rectWindowSize;
// -----------------------------------------
// not shown - computing mouse position, joystick position, text stuff
// if at least one event was queued - return it now
if (_events.size() > 0)
{
*pEvent = _events.pop_front();
return true;
}
return false;
}
In a traditional Windows program that uses GDI for graphics, you would have to worry about only drawing the area of the window that needs to be redrawn; this is the "update rect" and is accessed either by PAINTSTRUCT.rcPaint or by a call to GetUpdateRect(). (This is also available as an HRGN through other means.)
Do I need to do the same thing with Direct2D? All the examples on MSDN just draw the entire client area indiscriminately and searching online hasn't turned up anything else.
Or in other words, would anything bad happen to parts outside the update rect if I only draw within the update rect, for instance either manually or with PushAxisAlignedClip() or PushLayer()?
Furthermore, the documentation for ID2D1HwndRenderTarget::Resize() says
After this method is called, the contents of the render target's back-buffer are not defined, even if the D2D1_PRESENT_OPTIONS_RETAIN_CONTENTS option was specified when the render target was created.
Does this mean that whatever update region would be caused by resizing (such as shown by this picture from this page) is invalid and I should redraw the whole window (for instance, by calling InvalidateRect(NULL)) on a resize?
Thanks.
Yes. Use PushAxisAlignedClip with D2D1_ANTIALIAS_MODE_ALIASED.
Call ID2D1HwndRenderTarget::Resize when the window is resized. Do pay attention to the HRESULT it returns. It can return D2DERR_RECREATE_TARGET, but you may not know that it can also return D2DERR_DISPLAY_STATE_INVALID (which can also be returned by EndDraw, btw). And yes, call InvalidateRect(NULL) after that.
I also recommend using D2D1_PRESENT_OPTIONS_RETAIN_CONTENTS because otherwise you'll run into nasty bugs on some stupid driver/hardware configurations and in other events. Don't ask me why -- it just doesn't behave well without this. You'll get bug reports that your whole rendering area just becomes filled with black. It took me months to figure out that all I had to do was to use that flag. I was never able to repro the problem locally.
No. You have to do near the same thing as normal with gdi. Instead of using a HBITMAP backbuffer, you have to use d2d bitmap. In wm_size, you resize and redraw your d2d bitmap, and in wm_paint instead of bitblt a hbitmap you have to use the render drawbitmap method. and render just the part from the paintstruct rect member (hope this help you) :
Global or class members :
ID2D1Factory* g_pD2DFactory = NULL;
ID2D1HwndRenderTarget* g_pRenderTarget = NULL;
ID2D1SolidColorBrush* g_pBlackBrush = NULL;
ID2D1SolidColorBrush* g_pWhiteBrush = NULL;
ID2D1BitmapRenderTarget* g_bitmapRenderTarget = NULL; //for d2d bitmap
The global or class static wndproc:
case WM_CREATE: {
if (FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &g_pD2DFactory))) {
throw;
}
LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam;
HRESULT hr = g_pD2DFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(hWnd, D2D1::SizeU(lpcs->cx, lpcs->cy)),
&g_pRenderTarget
);
if (FAILED(hr)) {
throw;
}
if (FAILED(g_pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &g_pBlackBrush))) {
throw;
}
if (FAILED(g_pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), &g_pWhiteBrush))) {
throw;
}
break;
}
case WM_SIZE: {
if(FAILED(g_pRenderTarget->Resize(D2D1::SizeU(LOWORD(lParam), HIWORD(lParam))))) {
throw;
}
D2D_SAFE_RELEASE(g_bitmapRenderTarget)
g_pRenderTarget->CreateCompatibleRenderTarget(&g_bitmapRenderTarget);
g_bitmapRenderTarget->BeginDraw();
g_bitmapRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::AliceBlue));
g_bitmapRenderTarget->DrawEllipse(D2D1::Ellipse(D2D1::Point2F(100,100), 50,50), g_pBlackBrush);
g_bitmapRenderTarget->EndDraw();
break;
}
case WM_PAINT: {
HDC hDc;
PAINTSTRUCT ps;
LPCRECT lpRect;
ID2D1Bitmap* bitmap;
D2D1_RECT_F d2d1Rect;
hDc = BeginPaint(hWnd, &ps);
lpRect = &ps.rcPaint;
d2d1Rect = D2D1::RectF(lpRect->left, lpRect->top, lpRect->right, lpRect->bottom);
g_bitmapRenderTarget->GetBitmap(&bitmap);
g_pRenderTarget->BeginDraw();
g_pRenderTarget->DrawBitmap(
bitmap, d2d1Rect, 1.0f, D2D1_BITMAP_INTERPOLATION_MODE::D2D1_BITMAP_INTERPOLATION_MODE_LINEAR,
d2d1Rect
);
g_pRenderTarget->EndDraw();
EndPaint(hWnd, &ps);
return 0;
}
I have application with GUI but it hangs, when i'am clicking the button that actually does all work. What to do to make it run normally and displaying gui changes "online"?
If you don't want to work with background threads and it's a loop of some kind taking so long, you could add this member function to your UI code and call it in the loop:
void CMyDlg::PumpMessages()
{
// Must call Create() before using the dialog
ASSERT(m_hWnd!=NULL);
MSG msg;
// Handle dialog messages
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if(!IsDialogMessage(&msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
I am trying to make a dialog based MFC application , where two dialogs needs to be shown sequentially.
What that means is , once the first dialog(modal) is shown and dismissed (by pressing OK), the second dialog needs to be brought up.My requirement is the second dialog should be modeless.
But what I observe is the second dialog is shown but none of the message handling function are being called in response of user messages.I think the message map itself is not working, while the overridden functions(like OnInitdialog) are being called. I tried replacing this modeless dialog with a modal one , and alas, the doModal() itself fails.
Here is the little code:
CFirstDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
CSecondDlg *dlgModeLess = new CSecondDlg();
dlgModeLess->Create(CSecondDlg::IDD,NULL);
m_pMainWnd = dlgModeLess;
dlgModeLess->ShowWindow(SW_SHOW);
dlgModeLess->UpdateWindow();
}
Here is the message map of the second dialog:
BEGIN_MESSAGE_MAP(CSecondDlg, CDialog)
ON_MESSAGE(TRAY_MESSAGE,OnTrayMessage)
ON_BN_CLICKED(IDOK, &CSecongDlg::OnBnClickedOk)
ON_BN_CLICKED(IDC_RADIO1, &CSecondDlg::OnBnClickedRadio1)
END_MESSAGE_MAP()
I think I am doing something conceptually wrong. Kindly share your thoughts on what need to be done to tackle such a scenario.
As I mentioned in a previous post, it is not necessary that the second dialog be non modal.
Just do something like this:
BOOL CMyTestApp::InitInstance()
{
CMyTestDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
COtherDlg Dlg ;
m_pMainWnd = &dlg;
if (Dlg.DoModal() == IDCANCEL)
{
...
}
}
else if (nResponse == IDCANCEL)
{
...
}
return FALSE;
}
When you create a modeless dialog box, control will return to your calling function right away, so you need to declare variable dlgModeLess at a global scope and make sure your program/scope is still active until the dialog box finishes
I have solved this problem and this turned out to be interesting.
It seems that Cdialog::Create() itself is not sufficient for creating a fully operative modeless dialog box. We have to supply a win32 style message loop to it.
So this effectively makes two message loops in the program, one provide my MFC framework and the one that I wrote after returning from IDOK. Here is the modified code.
CSecondDlg *dlgModeLess = new CSecondDlg();
dlgModeLess->Create(CSecondDlg::IDD,NULL);
CTrayIconDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
MSG leftmsg;
PeekMessage(&leftmsg,m_pMainWnd->m_hWnd,0,0,PM_REMOVE);
MSG msg;
BOOL bRet;
while ((bRet = GetMessage(&msg, dlgModeLess->m_hWnd, 0, 0)) != 0)
{
if (bRet == -1)
{
// Handle the error and possibly exit
}
else if (!IsWindow(hWnd) || !IsDialogMessage(hWnd, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
This code works as expected, The interesting thing to note here is the PeekMessage function which removes a WM_QUIT message that is inserted in the thread's message queue once the first dialog is dismissed as we do not want to quite at that point of time. This i believe is done by the MFC framework.
I am currently programming a graphics application with OpenGL and the Windows API in C++. Unfortunately the image freezes under certain conditions, such as when I'm resizing the window, and/or when my mouse isn't moving. Is there some sort of mechanism I can use in Win32 to ensure that the frames are constantly being processed?
Here's some pseudocode describing the basic flow of my program
Main Loop
while(running)
{
if (PeekMessage(&Msg,NULL,0,0,PM_REMOVE))
{
if (Msg.message==WM_QUIT)
{
SetRunning(false);
}
else
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
else
{
SwapBuffers(deviceContext);
}
}
WndProc
switch(msg)
{
case WM_CLOSE:
{
PostQuitMessage(0);
break;
}
case WM_SIZE:
{
ResizeScreen(LOWORD(lParam),HIWORD(lParam));
break;
}
}
return DefWindowProc(hwnd, msg, wParam, lParam);
EDIT: I read the tutorial Kol linked to and made some edits, and now the frame rate is consistent even when the mouse is not moving. However the image still freezes when I'm moving or resizing the window, so I'd appreciate help on that.
Read the NeHe site to learn the basics of OpenGL with Win32. There are detailed explanations about how the message loop should look like, what the WM_SIZE handler should do etc.
EDIT
The code which draws the scene and the buffer swapping should be put into the message loop, in an else branch after the if (PeekMessage(...)) branch. See where the DrawGLScene() call is in the above mentioned NeHe example.
EDIT2
The problems were the followings:
The scene renderer function was not called in the WM_SIZE and WM_MOVE handlers.
The scene was drawn only once a second.