What is the best way to set up a window in win32 that has OpenGl(and glsl if the needs extra code to work) integrated into it?
I have done some research and found numerous ways of accomplishing this task i was wondering what the best way is or what way you like the most if the best way doesn't have an answer.
I have looked at nehe`s design and also the one supplied by the OpenGl super bible which both have completely different ways of accomplishing it (also the super bibles one gives me errors :().
any help would be appreciated including tutorials etc.
thanks
All your "different ways" aren't so different. You have to:
create your window (in the usual Win32 way, with RegisterClass(Ex) and CreateWindow(Ex))
create a GDI device context corresponding to your window (GetDC)
pick a pixel format which is supported by the display (optional DescribePixelFormat, then ChoosePixelFormat)
create your OpenGL context (wglCreateContext)
(optional, but required to use GLSL) link OpenGL extension functions (GLee or GLEW helpers, or glGetString(GL_EXTENSIONS) then wglGetProcAddress)
(optional) create an OpenGL 3.x context, free the compatibility context (wglCreateContextAttribs)
make the context active (wglMakeCurrent)
start using OpenGL (set up shader programs, load textures, draw stuff, etc.)
An excerpt of code showing these steps in action (not suitable for copy+paste, a bunch of RAII wrappers are used):
bool Context::attach( HWND hwnd )
{
PIXELFORMATDESCRIPTOR pfd = { sizeof(pfd), 1 };
if (!m_dc) {
scoped_window_hdc(hwnd).swap(m_dc);
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_SUPPORT_COMPOSITION | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cAlphaBits = 8;
pfd.iLayerType = PFD_MAIN_PLANE;
auto format_index = ::ChoosePixelFormat(m_dc.get(), &pfd);
if (!format_index)
return false;
if (!::SetPixelFormat(m_dc.get(), format_index, &pfd))
return false;
}
auto active_format_index = ::GetPixelFormat(m_dc.get());
if (!active_format_index)
return false;
if (!::DescribePixelFormat(m_dc.get(), active_format_index, sizeof pfd, &pfd))
return false;
if ((pfd.dwFlags & PFD_SUPPORT_OPENGL) != PFD_SUPPORT_OPENGL)
return false;
m_render_thread = ::CreateThread(NULL, 0, &RenderThreadProc, this, 0, NULL);
return m_render_thread != NULL;
}
DWORD WINAPI Context::RenderThreadProc( LPVOID param )
{
Context* const ctx = static_cast<Context*>(param);
HDC dc = ctx->m_dc.get();
SIZE canvas_size;
ctx->m_dc.check_resize(&canvas_size);
scoped_hglrc glrc(wglCreateContext(dc));
if (!glrc)
return EXIT_FAILURE;
if (!glrc.make_current(dc))
return EXIT_FAILURE;
if (ctx->m_major_version > 2 && GLEE_WGL_ARB_create_context) {
int const create_attribs[] = {
WGL_CONTEXT_MAJOR_VERSION_ARB, ctx->m_major_version,
WGL_CONTEXT_MINOR_VERSION_ARB, ctx->m_minor_version,
0
};
scoped_hglrc advrc(wglCreateContextAttribsARB(dc, 0, create_attribs));
if (advrc) {
if (!advrc.make_current(dc))
return EXIT_FAILURE;
advrc.swap(glrc);
}
}
{
const char* ver = reinterpret_cast<const char*>(glGetString(GL_VERSION));
if (ver) {
OutputDebugStringA("GL_VERSION = \"");
OutputDebugStringA(ver);
OutputDebugStringA("\"\n");
}
}
glDisable(GL_DITHER);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClearColor(0.f, 0.f, 0.f, 1.f);
if (GLEE_WGL_EXT_swap_control)
wglSwapIntervalEXT(1);
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
while (!::InterlockedExchange(&ctx->m_stop_render, 0)) {
ctx->process_queued_tasks();
if (ctx->m_dc.check_resize(&canvas_size)) {
glViewport(0, 0, canvas_size.cx, canvas_size.cy);
ctx->process_on_resize();
}
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArray(vao);
ctx->process_on_render();
BOOL swapped = ::SwapBuffers(dc);
if (!swapped)
std::cout << "::SwapBuffers failure, GetLastError() returns " << std::hex << ::GetLastError() << std::endl;
}
ctx->m_program_db.clear();
return EXIT_SUCCESS;
}
It also doesn't cover window creation, it enables OpenGL on an existing window.
This pretty much did it for me: http://nehe.gamedev.net/tutorial/creating_an_opengl_window_%28win32%29/13001/
EDIT: Related code:
/*
* This Code Was Created By Jeff Molofee 2000
* A HUGE Thanks To Fredric Echols For Cleaning Up
* And Optimizing This Code, Making It More Flexible!
* If You've Found This Code Useful, Please Let Me Know.
* Visit My Site At nehe.gamedev.net
*/
#include <windows.h> // Header File For Windows
#include <gl\gl.h> // Header File For The OpenGL32 Library
#include <gl\glu.h> // Header File For The GLu32 Library
#include <gl\glaux.h> // Header File For The Glaux Library
HDC hDC=NULL; // Private GDI Device Context
HGLRC hRC=NULL; // Permanent Rendering Context
HWND hWnd=NULL; // Holds Our Window Handle
HINSTANCE hInstance; // Holds The Instance Of The Application
bool keys[256]; // Array Used For The Keyboard Routine
bool active=TRUE; // Window Active Flag Set To TRUE By Default
bool fullscreen=TRUE; // Fullscreen Flag Set To Fullscreen Mode By Default
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Declaration For WndProc
GLvoid ReSizeGLScene(GLsizei width, GLsizei height) // Resize And Initialize The GL Window
{
if (height==0) // Prevent A Divide By Zero By
{
height=1; // Making Height Equal One
}
glViewport(0,0,width,height); // Reset The Current Viewport
glMatrixMode(GL_PROJECTION); // Select The Projection Matrix
glLoadIdentity(); // Reset The Projection Matrix
// Calculate The Aspect Ratio Of The Window
gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
glLoadIdentity(); // Reset The Modelview Matrix
}
int InitGL(GLvoid) // All Setup For OpenGL Goes Here
{
glShadeModel(GL_SMOOTH); // Enable Smooth Shading
glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Black Background
glClearDepth(1.0f); // Depth Buffer Setup
glEnable(GL_DEPTH_TEST); // Enables Depth Testing
glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations
return TRUE; // Initialization Went OK
}
int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer
glLoadIdentity(); // Reset The Current Modelview Matrix
return TRUE; // Everything Went OK
}
GLvoid KillGLWindow(GLvoid) // Properly Kill The Window
{
if (fullscreen) // Are We In Fullscreen Mode?
{
ChangeDisplaySettings(NULL,0); // If So Switch Back To The Desktop
ShowCursor(TRUE); // Show Mouse Pointer
}
if (hRC) // Do We Have A Rendering Context?
{
if (!wglMakeCurrent(NULL,NULL)) // Are We Able To Release The DC And RC Contexts?
{
MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
if (!wglDeleteContext(hRC)) // Are We Able To Delete The RC?
{
MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
hRC=NULL; // Set RC To NULL
}
if (hDC && !ReleaseDC(hWnd,hDC)) // Are We Able To Release The DC
{
MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hDC=NULL; // Set DC To NULL
}
if (hWnd && !DestroyWindow(hWnd)) // Are We Able To Destroy The Window?
{
MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hWnd=NULL; // Set hWnd To NULL
}
if (!UnregisterClass("OpenGL",hInstance)) // Are We Able To Unregister Class
{
MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hInstance=NULL; // Set hInstance To NULL
}
}
/* This Code Creates Our OpenGL Window. Parameters Are: *
* title - Title To Appear At The Top Of The Window *
* width - Width Of The GL Window Or Fullscreen Mode *
* height - Height Of The GL Window Or Fullscreen Mode *
* bits - Number Of Bits To Use For Color (8/16/24/32) *
* fullscreenflag - Use Fullscreen Mode (TRUE) Or Windowed Mode (FALSE) */
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
GLuint PixelFormat; // Holds The Results After Searching For A Match
WNDCLASS wc; // Windows Class Structure
DWORD dwExStyle; // Window Extended Style
DWORD dwStyle; // Window Style
RECT WindowRect; // Grabs Rectangle Upper Left / Lower Right Values
WindowRect.left=(long)0; // Set Left Value To 0
WindowRect.right=(long)width; // Set Right Value To Requested Width
WindowRect.top=(long)0; // Set Top Value To 0
WindowRect.bottom=(long)height; // Set Bottom Value To Requested Height
fullscreen=fullscreenflag; // Set The Global Fullscreen Flag
hInstance = GetModuleHandle(NULL); // Grab An Instance For Our Window
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // Redraw On Size, And Own DC For Window.
wc.lpfnWndProc = (WNDPROC) WndProc; // WndProc Handles Messages
wc.cbClsExtra = 0; // No Extra Window Data
wc.cbWndExtra = 0; // No Extra Window Data
wc.hInstance = hInstance; // Set The Instance
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); // Load The Default Icon
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // Load The Arrow Pointer
wc.hbrBackground = NULL; // No Background Required For GL
wc.lpszMenuName = NULL; // We Don't Want A Menu
wc.lpszClassName = "OpenGL"; // Set The Class Name
if (!RegisterClass(&wc)) // Attempt To Register The Window Class
{
MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
if (fullscreen) // Attempt Fullscreen Mode?
{
DEVMODE dmScreenSettings; // Device Mode
memset(&dmScreenSettings,0,sizeof(dmScreenSettings)); // Makes Sure Memory's Cleared
dmScreenSettings.dmSize=sizeof(dmScreenSettings); // Size Of The Devmode Structure
dmScreenSettings.dmPelsWidth = width; // Selected Screen Width
dmScreenSettings.dmPelsHeight = height; // Selected Screen Height
dmScreenSettings.dmBitsPerPel = bits; // Selected Bits Per Pixel
dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
// Try To Set Selected Mode And Get Results. NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar.
if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
{
// If The Mode Fails, Offer Two Options. Quit Or Use Windowed Mode.
if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
{
fullscreen=FALSE; // Windowed Mode Selected. Fullscreen = FALSE
}
else
{
// Pop Up A Message Box Letting User Know The Program Is Closing.
MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP);
return FALSE; // Return FALSE
}
}
}
if (fullscreen) // Are We Still In Fullscreen Mode?
{
dwExStyle=WS_EX_APPWINDOW; // Window Extended Style
dwStyle=WS_POPUP; // Windows Style
ShowCursor(FALSE); // Hide Mouse Pointer
}
else
{
dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; // Window Extended Style
dwStyle=WS_OVERLAPPEDWINDOW; // Windows Style
}
AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); // Adjust Window To True Requested Size
// Create The Window
if (!(hWnd=CreateWindowEx( dwExStyle, // Extended Style For The Window
"OpenGL", // Class Name
title, // Window Title
dwStyle | // Defined Window Style
WS_CLIPSIBLINGS | // Required Window Style
WS_CLIPCHILDREN, // Required Window Style
0, 0, // Window Position
WindowRect.right-WindowRect.left, // Calculate Window Width
WindowRect.bottom-WindowRect.top, // Calculate Window Height
NULL, // No Parent Window
NULL, // No Menu
hInstance, // Instance
NULL))) // Dont Pass Anything To WM_CREATE
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
static PIXELFORMATDESCRIPTOR pfd= // pfd Tells Windows How We Want Things To Be
{
sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor
1, // Version Number
PFD_DRAW_TO_WINDOW | // Format Must Support Window
PFD_SUPPORT_OPENGL | // Format Must Support OpenGL
PFD_DOUBLEBUFFER, // Must Support Double Buffering
PFD_TYPE_RGBA, // Request An RGBA Format
bits, // Select Our Color Depth
0, 0, 0, 0, 0, 0, // Color Bits Ignored
0, // No Alpha Buffer
0, // Shift Bit Ignored
0, // No Accumulation Buffer
0, 0, 0, 0, // Accumulation Bits Ignored
16, // 16Bit Z-Buffer (Depth Buffer)
0, // No Stencil Buffer
0, // No Auxiliary Buffer
PFD_MAIN_PLANE, // Main Drawing Layer
0, // Reserved
0, 0, 0 // Layer Masks Ignored
};
if (!(hDC=GetDC(hWnd))) // Did We Get A Device Context?
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd))) // Did Windows Find A Matching Pixel Format?
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
if(!SetPixelFormat(hDC,PixelFormat,&pfd)) // Are We Able To Set The Pixel Format?
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
if (!(hRC=wglCreateContext(hDC))) // Are We Able To Get A Rendering Context?
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
if(!wglMakeCurrent(hDC,hRC)) // Try To Activate The Rendering Context
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
ShowWindow(hWnd,SW_SHOW); // Show The Window
SetForegroundWindow(hWnd); // Slightly Higher Priority
SetFocus(hWnd); // Sets Keyboard Focus To The Window
ReSizeGLScene(width, height); // Set Up Our Perspective GL Screen
if (!InitGL()) // Initialize Our Newly Created GL Window
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
return TRUE; // Success
}
LRESULT CALLBACK WndProc( HWND hWnd, // Handle For This Window
UINT uMsg, // Message For This Window
WPARAM wParam, // Additional Message Information
LPARAM lParam) // Additional Message Information
{
switch (uMsg) // Check For Windows Messages
{
case WM_ACTIVATE: // Watch For Window Activate Message
{
if (!HIWORD(wParam)) // Check Minimization State
{
active=TRUE; // Program Is Active
}
else
{
active=FALSE; // Program Is No Longer Active
}
return 0; // Return To The Message Loop
}
case WM_SYSCOMMAND: // Intercept System Commands
{
switch (wParam) // Check System Calls
{
case SC_SCREENSAVE: // Screensaver Trying To Start?
case SC_MONITORPOWER: // Monitor Trying To Enter Powersave?
return 0; // Prevent From Happening
}
break; // Exit
}
case WM_CLOSE: // Did We Receive A Close Message?
{
PostQuitMessage(0); // Send A Quit Message
return 0; // Jump Back
}
case WM_KEYDOWN: // Is A Key Being Held Down?
{
keys[wParam] = TRUE; // If So, Mark It As TRUE
return 0; // Jump Back
}
case WM_KEYUP: // Has A Key Been Released?
{
keys[wParam] = FALSE; // If So, Mark It As FALSE
return 0; // Jump Back
}
case WM_SIZE: // Resize The OpenGL Window
{
ReSizeGLScene(LOWORD(lParam),HIWORD(lParam)); // LoWord=Width, HiWord=Height
return 0; // Jump Back
}
}
// Pass All Unhandled Messages To DefWindowProc
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}
int WINAPI WinMain( HINSTANCE hInstance, // Instance
HINSTANCE hPrevInstance, // Previous Instance
LPSTR lpCmdLine, // Command Line Parameters
int nCmdShow) // Window Show State
{
MSG msg; // Windows Message Structure
BOOL done=FALSE; // Bool Variable To Exit Loop
// Ask The User Which Screen Mode They Prefer
if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
{
fullscreen=FALSE; // Windowed Mode
}
// Create Our OpenGL Window
if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
{
return 0; // Quit If Window Was Not Created
}
while(!done) // Loop That Runs While done=FALSE
{
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // Is There A Message Waiting?
{
if (msg.message==WM_QUIT) // Have We Received A Quit Message?
{
done=TRUE; // If So done=TRUE
}
else // If Not, Deal With Window Messages
{
TranslateMessage(&msg); // Translate The Message
DispatchMessage(&msg); // Dispatch The Message
}
}
else // If There Are No Messages
{
// Draw The Scene. Watch For ESC Key And Quit Messages From DrawGLScene()
if (active) // Program Active?
{
if (keys[VK_ESCAPE]) // Was ESC Pressed?
{
done=TRUE; // ESC Signalled A Quit
}
else // Not Time To Quit, Update Screen
{
DrawGLScene(); // Draw The Scene
SwapBuffers(hDC); // Swap Buffers (Double Buffering)
}
}
if (keys[VK_F1]) // Is F1 Being Pressed?
{
keys[VK_F1]=FALSE; // If So Make Key FALSE
KillGLWindow(); // Kill Our Current Window
fullscreen=!fullscreen; // Toggle Fullscreen / Windowed Mode
// Recreate Our OpenGL Window
if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
{
return 0; // Quit If Window Was Not Created
}
}
}
}
// Shutdown
KillGLWindow(); // Kill The Window
return (msg.wParam); // Exit The Program
}
If you really, really want to do it yourself, you can still have a look at how GLFW does it. In its source directory, you have a win32-specific directory, in which you have a windowing-specic .c file. You should find everything you need in this file.
This page of the OpenGL Wiki could help you, too.
The best way is NOT to do it yourself. OpenGL context creation is a bottomless abyss of complexity. You have lots of tools that can do the dirty work for you, and what's more, it'll work on Linux and Mac too
GLFW, FreeGlut, SDL, SFML are probably the most common.
See GLFW's user guide, for instance, to see how easy these libs make it to create a window :
glfwInit();
glfwOpenWindow( 300,300, 0,0,0,0,0,0, GLFW_WINDOW );
// bam, you're done
Related
I want to detect whether a program is full screen in the expand monitor. I have got the expand monitor position using EnumDisplayMonitors. So if I get the program's position, I can compare it with expand monitor's position and get the result.
So I get the HWND of the program, and then I use ::GetWindowRect(HWND, &rect); But the rect is not correct.
HWND g_HWND = NULL;
BOOL CALLBACK EnumWindowsProcMy(HWND hwnd, LPARAM lParam)
{
DWORD lpdwProcessId;
GetWindowThreadProcessId(hwnd, &lpdwProcessId);
if (lpdwProcessId == lParam)
{
g_HWND = hwnd;
return FALSE;
}
return TRUE;
}
DWORD GetProcessidFromName(CString strName)
{
PROCESSENTRY32 pe;
DWORD id = 0;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
pe.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hSnapshot, &pe))
{
return 0;
}
pe.dwSize = sizeof(PROCESSENTRY32);
while (Process32Next(hSnapshot, &pe) != FALSE)
{
CString strTmp = pe.szExeFile;
if (strName.CompareNoCase(strTmp) == 0)
{
id = pe.th32ProcessID;
break;
}
}
CloseHandle(hSnapshot);
return id;
}
CRect rect[2] = { (0,0,0,0),(0,0,0,0) }; //rect[1] stores the expand monitor's position
BOOL CALLBACK Monitorenumproc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
static BOOL first = FALSE;
MONITORINFO monitorinfo;
monitorinfo.cbSize = sizeof(MONITORINFO);
GetMonitorInfo(hMonitor, &monitorinfo);
if (monitorinfo.dwFlags == MONITORINFOF_PRIMARY)
{
if (!first)
{
first = TRUE;
rect[0] = monitorinfo.rcMonitor;
return TRUE;
}
else
{
first = FALSE;
return FALSE;
}
}
else
{
rect[1] = monitorinfo.rcMonitor;
}
return TRUE;
}
...
//get the position of xxx.exe
EnumDisplayMonitors(NULL, NULL, Monitorenumproc, 0);
EnumWindows(EnumWindowsProcMy, GetProcessidFromName(_T("xxx.exe")));
RECT rect1;
::GetWindowRect(g_HWND, &rect1); //rect1 is not correct!
I'm not sure what exactly you mean by
But the rect is not correct.
but here is what I see may seem incorrect:
Negative values, which indicate the window position relative to the primary display. You have to do the [right-left, bottom-top] calculation to get the size.
Values larger than the display size which means the rect includes the area occupied by the drop shadow.
See the documentation of GetWindowRect
In Windows Vista and later, the Window Rect now includes the area
occupied by the drop shadow.
Calling GetWindowRect will have different behavior depending on
whether the window has ever been shown or not. If the window has not
been shown before, GetWindowRect will not include the area of the drop
shadow.
To get the window bounds excluding the drop shadow, use
DwmGetWindowAttribute, specifying DWMWA_EXTENDED_FRAME_BOUNDS. Note
that unlike the Window Rect, the DWM Extended Frame Bounds are not
adjusted for DPI. Getting the extended frame bounds can only be done
after the window has been shown at least once.
So use the snippet below to get the size without the drop shadow:
RECT dwmExtFrameBounds;
DwmGetWindowAttribute(g_HWND, DWMWINDOWATTRIBUTE::DWMWA_EXTENDED_FRAME_BOUNDS, &dwmExtFrameBounds, sizeof(RECT));
Bonus read: Why does a maximized window have the wrong window rectangle?
I need to add custom menu items as needed. I found OnInitMenuPopup (WM_INITMENUPOPUP) does what I need but I can't get an icon to show next to the text on the menu? I've tried a 16x16 png graphic using m_MyGraphic as a CPngImage, I've tried attaching it to a CBitmap, I've tried saving the graphic as a .bmp and loading as CBitmap. I've tried not setting the graphic on the load, but then trying to do it with SetMenuItemBitmaps(), I've tried a 13x13 graphic, I've tried a 15x15 graphic (which matches GetMenuCheckMarkDimensions()). Never does a graphic show next to the menu item? What am I doing wrong or missing?
TIA!
void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu)
{
// add items
if (pPopupMenu && pPopupMenu->GetMenuItemCount() > 0 && pPopupMenu->GetMenuItemID(0) == ID_MY_EXPECTED_ID) {
// loop though and add menu items
for (UINT i=0; i<theApp.m_MyList.GetCount(); i++) {
CString s;
s.Format(_T("%i: %s"), i, theApp.m_MyList[i].String);
MENUITEMINFO mii={};
mii.cbSize=sizeof(mii);
mii.fMask=MIIM_ID|MIIM_STRING|MIIM_BITMAP;
mii.wID=ID_MY_RANGE_0+i;
mii.dwTypeData=s.GetBuffer();
mii.hbmpItem=(HBITMAP)m_MyBitmap.GetSafeHandle();
pPopupMenu->InsertMenuItem(i+1, &mii, TRUE);
// not working above so tried using this as well but it doesn't work either:
//pPopupMenu->SetMenuItemBitmaps(i+1, MF_BYPOSITION, &m_MyBitmap, &m_MyBitmap);
}
}
CFrameWndEx::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
}
I found a work around. First add CMFCToolBarMenuButton::m_bAlwaysCallOwnerDraw=TRUE; somewhere on initialization.
Then handle drawing it:
BOOL CMainFrame::OnDrawMenuImage(CDC* pDC, const CMFCToolBarMenuButton* pMenuButton, const CRect& rectImage)
{
BOOL result=FALSE;
if (pMenuButton->m_nID>=ID_MY_RANGE_0 && pMenuButton->m_nID<=ID_MY_RANGE_N) {
// size to use on menu
CSize sizemenuimage = CMFCToolBar::GetMenuImageSize();
// get size of our bitmap
BITMAP bitmap;
m_MyBitmap.GetBitmap(&bitmap);
// create dc to attach bitmap to
CDC dcmem;
if (dcmem.CreateCompatibleDC(pDC)) {
// attach bitmap to dc
CBitmap * poldbitmap=dcmem.SelectObject(&m_MyBitmap);
if (poldbitmap) {
// Draw bitmap
result=pDC->StretchBlt(rectImage.left+(rectImage.Width()-sizemenuimage.cx)/2,
rectImage.top+(rectImage.Height()-sizemenuimage.cy)/2,
sizemenuimage.cx, sizemenuimage.cy,
&dcmem, 0, 0, bitmap.bmWidth, bitmap.bmHeight, SRCCOPY);
// Select original object
dcmem.SelectObject(poldbitmap);
}
dcmem.DeleteDC();
}
}
return result;
}
Another possible solution (if you already have the bitmaps for toolbar) is:
void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu)
{
CMDIFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
// TODO: Add your message handler code here
HICON hIcon = AfxGetApp()->LoadIcon(IDR_TESTMETYPE);
pPopupMenu->SetMenuItemBitmaps(ID_FILE_NEW, MF_BYCOMMAND, ConvertIconToBitmap(hIcon), NULL);
...
...
}
where SetMenuItemBitmaps is defined as:
CBitmap* CMainFrame::ConvertIconToBitmap(HICON hIcon)
{
CDC dc;
CBitmap bmp;
CClientDC ClientDC(this);
dc.CreateCompatibleDC(&ClientDC);
bmp.CreateCompatibleBitmap(&ClientDC, 13, 13);
CBitmap* pOldBmp = (CBitmap*)dc.SelectObject(&bmp);
::DrawIconEx(dc.GetSafeHdc(), 0, 0, hIcon, 13, 13, 0, (HBRUSH)RGB(255, 255, 255), DI_NORMAL);
dc.SelectObject(pOldBmp);
dc.DeleteDC();
HBITMAP hBitmap = (HBITMAP)::CopyImage((HANDLE)((HBITMAP)bmp),
IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE);
return CBitmap::FromHandle(hBitmap);
}
I have started to work with SDL2 and am not experienced with it. I am working on a Mac system. Almost everything has been good, but I have a problem that when a resizeable window is resized, while the handle is being dragged, the window turns black, and I can only repaint it after releasing. And I have checked that while the window is being resized, no event is being produced and I have no means to interfere or detect this, as the event loop is just paused. Is there any possible solutions?
Here is the code (Almost replica of a tutorial on handling resize event):
SDL_Event event;
SDL_Rect nativeSize;
SDL_Rect newWindowSize;
float scaleRatioW;//This is to change anything that might rely on something like mouse coords
float scaleRatioH; //(such as a button on screen) over to the new coordinate system scaling would create
SDL_Window * window; //Our beautiful window
SDL_Renderer * renderer; //The renderer for our window
SDL_Texture * backBuffer; //The back buffer that we will be rendering everything to before scaling up
SDL_Texture * ballImage; //A nice picture to demonstrate the scaling;
bool resize;
void InitValues(); //Initialize all the variables needed
void InitSDL(); //Initialize the window, renderer, backBuffer, and image;
bool HandleEvents(); //Handle the window changed size event
void Render(); //Switches the render target back to the window and renders the back buffer, then switches back.
void Resize(); //The important part for stretching. Changes the viewPort, changes the scale ratios
void InitValues()
{
nativeSize.x = 0;
nativeSize.y = 0;
nativeSize.w = 256;
nativeSize.h = 224; //A GameBoy size window width and height
scaleRatioW = 1.0f;
scaleRatioH = 1.0f;
newWindowSize.x = 0;
newWindowSize.y = 0;
newWindowSize.w = nativeSize.w;
newWindowSize.h = nativeSize.h;
window = NULL;
renderer = NULL;
backBuffer = NULL;
ballImage = NULL;
resize = false;
}
void InitSDL()
{
if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
{
//cout << "Failed to initialize SDL" << endl;
printf("%d\r\n", __LINE__);
}
//Set the scaling quality to nearest-pixel
if(SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0") < 0)
{
//cout << "Failed to set Render Scale Quality" << endl;
printf("%d\r\n", __LINE__);
}
//Window needs to be resizable
window = SDL_CreateWindow("Rescaling Windows!",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
256,
224,
SDL_WINDOW_RESIZABLE);
//You must use the SDL_RENDERER_TARGETTEXTURE flag in order to target the backbuffer
renderer = SDL_CreateRenderer(window,
-1,
SDL_RENDERER_ACCELERATED |
SDL_RENDERER_TARGETTEXTURE);
//Set to blue so it's noticeable if it doesn't do right.
SDL_SetRenderDrawColor(renderer, 0, 0, 200, 255);
//Similarly, you must use SDL_TEXTUREACCESS_TARGET when you create the texture
backBuffer = SDL_CreateTexture(renderer,
SDL_GetWindowPixelFormat(window),
SDL_TEXTUREACCESS_TARGET,
nativeSize.w,
nativeSize.h);
//IMPORTANT Set the back buffer as the target
SDL_SetRenderTarget(renderer, backBuffer);
//Load an image yay
SDL_Surface * image = SDL_LoadBMP("Ball.bmp");
ballImage = SDL_CreateTextureFromSurface(renderer, image);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
SDL_FreeSurface(image);
}
bool HandleEvents()
{
while(SDL_PollEvent(&event) )
{
printf("%d\r\n", __LINE__);
if(event.type == SDL_QUIT)
{
printf("%d\r\n", __LINE__);
return true;
}
else if(event.type == SDL_WINDOWEVENT)
{
if(event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
{
resize = true;
printf("%d\r\n", __LINE__);
}
}
return false;
}
return false;
}
void Render()
{
SDL_RenderCopy(renderer, ballImage, NULL, NULL); //Render the entire ballImage to the backBuffer at (0, 0)
printf("%d\r\n", __LINE__);
SDL_SetRenderTarget(renderer, NULL); //Set the target back to the window
if(resize)
{
Resize();
resize = false;
}
printf("%d\r\n", __LINE__);
SDL_RenderCopy(renderer, backBuffer, &nativeSize, &newWindowSize); //Render the backBuffer onto the screen at (0,0)
SDL_RenderPresent(renderer);
SDL_RenderClear(renderer); //Clear the window buffer
SDL_SetRenderTarget(renderer, backBuffer); //Set the target back to the back buffer
SDL_RenderClear(renderer); //Clear the back buffer
printf("%d\r\n", __LINE__);
}
void Resize()
{
int w, h;
printf("%d\r\n", __LINE__);
SDL_GetWindowSize(window, &w, &h);
scaleRatioW = w / nativeSize.w;
scaleRatioH = h / nativeSize.h; //The ratio from the native size to the new size
newWindowSize.w = w;
newWindowSize.h = h;
//In order to do a resize, you must destroy the back buffer. Try without it, it doesn't work
SDL_DestroyTexture(backBuffer);
backBuffer = SDL_CreateTexture(renderer,
SDL_GetWindowPixelFormat(window),
SDL_TEXTUREACCESS_TARGET, //Again, must be created using this
nativeSize.w,
nativeSize.h);
SDL_Rect viewPort;
SDL_RenderGetViewport(renderer, &viewPort);
if(viewPort.w != newWindowSize.w || viewPort.h != newWindowSize.h)
{
//VERY IMPORTANT - Change the viewport over to the new size. It doesn't do this for you.
SDL_RenderSetViewport(renderer, &newWindowSize);
}
}
int main(int argc, char * argv[])
{
InitValues();
InitSDL();
bool quit = false;
printf("%d\r\n", __LINE__);
while(!quit)
{
printf("%d\r\n", __LINE__);
quit = HandleEvents();
Render();
}
return 0;
}
Ok, after fighting a bit with SDL2, I got it working with macOS 10.12.
Here is the problem:
SDL2 catches resize events and resends only the last 3 when you poll events using SDL_PollEvent(&event) for example.
During this time (you pressed left mouse click on resize area and hold the mouse) the SDL_PollEvent is blocking.
Here is the workaround:
Luckily, you can hook into the event handler using SDL_SetEventFilter. This will fire every time a event is being received. So for all resize events as they occur.
So what you can do is to register your own event filter callback that basically allows every event (by returning 1), listens on resize events, and sends them to your draw loop.
Example:
//register this somewhere
int filterEvent(void *userdata, SDL_Event * event) {
if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_RESIZED) {
//convert userdata pointer to yours and trigger your own draw function
//this is called very often now
//IMPORTANT: Might be called from a different thread, see SDL_SetEventFilter docs
((MyApplicationClass *)userdata)->myDrawFunction();
//return 0 if you don't want to handle this event twice
return 0;
}
//important to allow all events, or your SDL_PollEvent doesn't get any event
return 1;
}
///after SDL_Init
SDL_SetEventFilter(filterEvent, this) //this is instance of MyApplicationClass for example
Important: Do not call SDL_PollEvent within your filterEvent callback, as this will result in weird behaviour of stuck events. (resize will not stop sometimes for example)
Turns out that this is not specific to my code, and it is a part of a wider problem with all the OpenGL libraries in MacOSX. Although recent patches in GLFW has fixed it, and in the GLUT version which is provided with XCode itself, it is quite better and you just observe a flicker in the window while resizing.
https://github.com/openframeworks/openFrameworks/issues/2800
https://github.com/openframeworks/openFrameworks/issues/2456
The problem is because of the blocking nature of the OSX window manager, which blocks all the events until the mouse is released.
To solve this, you should manipulate the library you are using and recompile it. You should add these or something similar (depending on your development environment) to the resize handler to bypass the block:
ofNotifyUpdate();
instance->display();
which is disastrous, if you are a novice, and want the ability to use library updates without much difficulty. Another solution is to override the SDL behaviour, by writing another event handler that does that. It is better as it doesn't need editing the SDL code, but adds a pile of platform specific code that I personally don't tend to and would cause a lot of problems that I don't want to spend time fixing.
After 2 days of searching, because I had just started the project and not implemented much relying on SDL, I decided to switch to GLFW which has the smoothest resize handling and I observe no flicker.
I've followed this tutorial and got it all working: http://www.braynzarsoft.net/index.php?p=InitDX11
The result is a window with a constantly changing background color. The trouble is that the color stops changing while the window is being dragged around. I've tried adding the following case statements (in various combinations) to the WndProc callback, but to no avail:
case WM_ENTERSIZEMOVE:
SetTimer(hwnd, 1, USER_TIMER_MINIMUM, NULL);
return 0;
case WM_EXITSIZEMOVE:
KillTimer(hwnd, 1);
return 0;
case WM_TIMER:
RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_INTERNALPAINT);
return 0;
case WM_PAINT:
UpdateScene();
DrawScene();
return 0;
The above causes an exception at d3d11DevCon->ClearRenderTargetView(renderTargetView, bgColor), but I've also tried merging the WM_PAINT case into the WM_TIMER case, and all I got was flickering between the natural window background color and the current color of the DX scene (the color of the DX portion of the flicker never evolved, it stayed constant no matter for how long I dragged the window).
Any tips?
A better option is to just not draw while in a resize. There's not usually a lot of value in having the backbuffer resized over and over again. Just wait until the resize is complete to resize the buffer.
static bool s_in_sizemove = false;
static bool s_in_suspend = false;
static bool s_minimized = false;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
break;
case WM_SIZE:
if (wParam == SIZE_MINIMIZED)
{
if (!s_minimized)
{
s_minimized = true;
if (!s_in_suspend)
OnSuspending();
s_in_suspend = true;
}
}
else if (s_minimized)
{
s_minimized = false;
if (s_in_suspend)
OnResuming();
s_in_suspend = false;
}
else if ( !s_in_sizemove )
OnWindowSizeChanged();
break;
case WM_ENTERSIZEMOVE:
s_in_sizemove = true;
break;
case WM_EXITSIZEMOVE:
s_in_sizemove = false;
OnWindowSizeChanged();
break;
case WM_GETMINMAXINFO:
{
auto info = reinterpret_cast<MINMAXINFO*>(lParam);
info->ptMinTrackSize.x = 320;
info->ptMinTrackSize.y = 200;
}
break;
You have to release all the backbuffer and depth-buffer references and recreate them in OnWindowSizedChange.
The actual rendering is done as part of the message pump for most 'real-time' graphics apps:
// Main message loop
MSG msg = { 0 };
while (WM_QUIT != msg.message)
{
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
Tick();
}
}
Here Tick handles a timer update and the render.
See the Direct3D Win32 Game Visual Studio template for a complete example.
Update: If the 'blank window' bothers you during the resize, but you are fine with the default behavior of DXGI_SCALING_STRETCH during the resize, you can replace the WM_PAINT above with:
case WM_PAINT:
if (s_in_sizemove)
{
game->Tick();
}
else
{
hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
}
break;
I had same problem. I thought solution would be complicated. However, no. That is about mesage pool. DX should use same thread and indeed you use your rendering (Ex: myRender(){..} ) in that loop In my case Frame(); is the bool returnable I use for rendering that contains all operatios :
MSG msg;
bool done, result;
// Initialize the message structure.
ZeroMemory(&msg, sizeof(MSG));
// Loop until there is a quit message from the window or the user.
done = false;
while (!done)
{
// Handle the windows messages.
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// If windows signals to end the application then exit out.
if (msg.message == WM_QUIT)
{
done = true;
}
else
{
// Otherwise do the frame processing.
result = Frame();
if (!result)
{
done = true;
}
}
}
When you resize you can handle some messages WM_SIZE or WM_MOVE message. In your LRESULT CALLBACK handle:
LRESULT CALLBACK WndProc(HWND hwnd, UINT umessage, WPARAM wparam, LPARAM lparam) {
//...OTHERS...
if (umessage == WM_MOVE) {
/*Dont be confused ApplicationHandle is pointer of my SystemClass
which set ApplicationHandle = this in SystemClass.
to create a window/s and Graphics (contains all DirectX related operations).
Here GraphicsClass (contains all DX calls) initialized in
SystemClass as new class and SystemClass initialized as
new class at main.cpp in WINAPI. That makes a lot easier
for handling this kind of issues for me
In your case you will call your rendering loop instead Frame();*/
if (ApplicationHandle -> m_Graphics) {
RECT r;
GetClientRect(ApplicationHandle -> m_hwnd, & r);
ApplicationHandle -> m_Graphics -> clientSize = {
r.right - r.left,
r.bottom - r.top
};
//frame processing.
ApplicationHandle -> m_Graphics -> Frame();
}
if (umessage == WM_SIZING) {
if ((wparam == WMSZ_BOTTOM || wparam == WMSZ_RIGHT || wparam == WMSZ_BOTTOMRIGHT) && ApplicationHandle -> m_Graphics) {
/*IF WE DO NOT HANDLE wparam resize will be very laggy.*/
GetClientRect(ApplicationHandle -> m_hwndOWNER, & clientRect);
ApplicationHandle -> m_Graphics -> clientSize = {
clientRect.right - clientRect.left,
clientRect.bottom - clientRect.top
};
ApplicationHandle -> m_Graphics -> Frame();
}
}
}
//...OTHERS...
}
HERE IS A VIDEO OF RESULT: https://www.youtube.com/watch?v=vN_XPVRHuiw&feature=youtu.be
even if you are drawing custom window using Desktop Window Manager (WDM) you can handle WM_NCHITTEST for other click situations as that will only update if you resize and move but not if you click and hold caption or hold border only.
additionally, I did not get that why you handle WM_PAINT if you are using DirectX already.
How would you go about painting something onto a window in regular intervals.
I have come up with the this (stripped quite a bit for clarity)
#include <windows.h>
void DrawOntoDC (HDC dc) {
pen = CreatePen(...)
penOld = SelectObject(dc, pen)
..... Here is the actual drawing, that
..... should be regurarly called, since
..... the drawn picture changes as time
..... progresses
SelectObject(dc, pen_old);
DeleteObject(pen);
}
LRESULT CALLBACK WindowProc(....) {
switch(Msg) {
case WM_PAINT: {
PAINTSTRUCT ps;
dc = BeginPaint(hWnd, &ps);
..... A Memory DC is created
..... In order to prevent flickering.
HBITMAP PersistenceBitmap;
PersistenceBitmap = CreateCompatibleBitmap(dc, windowHeight, windowHeight);
HDC dcMemory = CreateCompatibleDC(dc);
HBITMAP oldBmp = (HBITMAP) SelectObject(dcMemory, PersistenceBitmap);
DrawOntoDC(dcMemory);
..... "copying" the memory dc in one go unto dhe window dc:
BitBlt ( dc,
0, 0, windowWidth, windowHeight,
dcMemory,
0, 0,
SRCCOPY
);
..... destroy the allocated bitmap and memory DC
..... I have the feeling that this could be implemented
..... better, i.e. without allocating and destroying the memroy dc
..... and bitmap with each WM_PAINT.
SelectObject(dcMemory, oldBmp);
DeleteDC(dcMemory);
DeleteObject(PersistenceBitmap);
EndPaint (hWnd, &ps);
return 0;
}
default:
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
}
DWORD WINAPI Timer(LPVOID p) {
..... The 'thread' that makes sure that the window
..... is regularly painted.
HWND hWnd = (HWND) *((HWND*) p);
while (1) {
Sleep(1000/framesPerSecond);
InvalidateRect(hWnd, 0, TRUE);
}
}
int APIENTRY WinMain(...) {
WNDCLASSEX windowClass;
windowClass.lpfnWndProc = WindowProc;
windowClass.lpszClassName = className;
....
RegisterClassEx(&windowClass);
HWND hwnd = CreateWindowEx(
....
className,
....);
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
DWORD threadId;
HANDLE hTimer = CreateThread(
0, 0,
Timer,
(LPVOID) &hwnd,
0, &threadId );
while( GetMessage(&Msg, NULL, 0, 0) ) {
....
}
return Msg.wParam;
}
I guess there's a lot that could be improved and I'd appreciate any pointer to things I have overlooked.
Doing this kind of thing with a worker thread is not optimal.
Given that the optimal code path for painting is always via a WM_PAINT that leaves two ways to do this:
Simply create a timer on the GUI thread, post WM_TIMER messages to a timerproc, or the window directly, and invoke the OnTick() part of your engine. IF any sprites move, they invalidate their area using InvalidateRect() and windows follows up by automatically posting a WM_PAINT. This has the advantage of having a very low CPU usage if the game is relatively idle.
Most games want stricter timing that can be achieved using a low priority WM_TIMER based timer. In that case, you implement a game loop something like this:
Message Loop:
while(stillRunning)
{
DWORD ret = MsgWaitForMultipleObjects(0,NULL,FALSE,frameIntervalMs,QS_ALLEVENTS);
if(ret == WAIT_OBJECT_0){
while(PeekMessage(&msg,0,0,0,PM_REMOVE)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if(TickGame()) // if TickGame indicates that enough time passed for stuff to change
RedrawWindow(hwndGame,...); // Dispatch a WM_PAINT immediately.
}
The danger with this kind of message loop is, if the ... application goes into any kind of modal state :- the user starts to drag the window / a modal dialog box pops up, then messages are being pumped by the modal loop, so the animation stops. As a result you need to have a fallback timer if you need to mix a high performance message loop with modal operations.
WRT your WM_PAINT implementation - its usually better to (re)create your backbuffer in response to WM_SIZE messages. That way its always the right size, and you don't incurr the rather large cost of recreating a large memory buffer many times per second.
Borrowing bits and pieces from various places, I came up with the following approach for my similar problem. The following is a test application to test the concept.
In this test application I am using an MFC static window which I am updating with a text string by periodically calling the ::SetWindowText() function with the MFC static window's handle. This works fine to display a marching right angle bracket to demonstrate that the animation works.
In the future I intend on using a memory resident bitmap image which is modified in the animation loop which is then posted into a bitmap attached to the static text window. This technique allows for an animated bitmap to be presented with a more elegant indication of something in progress.
Testing this concept was done with an MFC dialog application that contains two static windows for the progress indicators and two additional buttons, Start and Stop, to start and stop the progress indicators. The goal is that when the Start button is pressed a series of greater than signs are written across the static window and then cleared and then once more started. So the animation looks like an LED sign that has arrows marching across from left to right in a horizontal marque.
These two buttons do nothing more than set an indicator in the animation object to be either one (on) or zero (off). The animation object thread which does the actual animation just reads from the m_state variable and does not modify it.
The timer delay quantity is hardcoded for the purposes of this test. It could easily be a parameter.
The dialog is still responsive and even as it is updating I can display the default About Box for the dialog application and move the About Box around. I can also drag the dialog application itself around screen (without the About Box displayed as that is a modal dialog) with the static window still updating.
The Animation class source
The source code for the animation logic is a simple class that starts a thread which then updates the specified dialog control. While the animation loop is a static method of the class, the data used by the loop is specified in the object itself so multiple animations can be done with different objects using the same static loop.
This approach is fairly straightforward and simple. Rather than using a more sophisticated approach with a thread pool and a timer pool, it dedicates a thread and a timer object to a single animation. It appears obvious that this approach will not scale well however for an application with a couple of animations, it works well enough.
class AnimatedImage
{
UINT m_state; // current on/off state of the animation. if off (0) then the window is not updated
UINT m_itemId; // control identifier of the window that we are going to be updating.
HWND m_targetHwnd; // window handle of the parent dialog of the window we are going to be updating
UINT m_i; // position for the next right angle bracket
wchar_t m_buffer[32]; // text buffer containing zero or more angle brackets which we use to update the window
DWORD m_lastError; // result of GetLastError() in case of an error.
HANDLE m_hTimer; // handle for the timer
HANDLE m_hThread; // handle for the thread created.
LARGE_INTEGER m_liDueTime; // time delay between updates
public:
AnimatedImage(UINT itemId = 0, HWND hWnd = NULL) : m_state(0), m_itemId(itemId), m_targetHwnd(hWnd), m_i(0), m_lastError(0), m_hTimer(NULL), m_hThread(NULL) { memset(m_buffer, 0, sizeof(m_buffer)) ; }
~AnimatedImage() { Kill(); CloseHandle(m_hTimer); CloseHandle(m_hThread); } // clean up the timer and thread handle.
static unsigned __stdcall loop(AnimatedImage *p); // animation processing loop
void Run(); // starts the animation thread
void Start(); // starts the animation
void Stop(); // stops the animation
void Kill(); // indicates the thread is to exit.
// Functions used to get the target animation window handle
// and to set the parent window handle and the dialog control identifier.
// This could be simpler by just requiring the target animation window handle
// and letting the user do the GetDlgItem() function themselves.
// That approach would make this class more flexible.
HWND GetImageWindow() { return ::GetDlgItem(m_targetHwnd, m_itemId); }
void SetImageWindow(UINT itemId, HWND hWnd) { m_itemId = itemId; m_targetHwnd = hWnd; }
};
unsigned __stdcall AnimatedImage::loop(AnimatedImage *p)
{
p->m_liDueTime.QuadPart = -10000000LL;
// Create an unnamed waitable timer. We use this approach because
// it makes for a more dependable timing source than if we used WM_TIMER
// or other messages. The timer resolution is much better where as with
// WM_TIMER is may be no better than 50 milliseconds and the reliability
// of getting the messages regularly can vary since WM_TIMER are lower
// in priority than other messages such as mouse messages.
p->m_hTimer = CreateWaitableTimer(NULL, TRUE, NULL);
if (NULL == p->m_hTimer)
{
return 1;
}
for (; ; )
{
// Set a timer to wait for the specified time period.
if (!SetWaitableTimer(p->m_hTimer, &p->m_liDueTime, 0, NULL, NULL, 0))
{
p->m_lastError = GetLastError();
return 2;
}
// Wait for the timer.
if (WaitForSingleObject(p->m_hTimer, INFINITE) != WAIT_OBJECT_0) {
p->m_lastError = GetLastError();
return 3;
}
else {
if (p->m_state < 1) {
p->m_i = 0;
memset(p->m_buffer, 0, sizeof(m_buffer));
::SetWindowText(p->GetImageWindow(), p->m_buffer);
}
else if (p->m_state < 2) {
// if we are updating the window then lets add another angle bracket
// to our text buffer and use SetWindowText() to put it into the
// window area.
p->m_buffer[p->m_i++] = L'>';
::SetWindowText(p->GetImageWindow(), p->m_buffer);
p->m_i %= 6; // for this demo just do a max of 6 brackets before we reset.
if (p->m_i == 0) {
// lets reset our buffer so that the next time through we will start
// over in position zero (first position) with our angle bracket.
memset(p->m_buffer, 0, sizeof(m_buffer));
}
}
else {
// we need to exit our thread so break from the loop and return.
break;
}
}
}
return 0;
}
void AnimatedImage::Run()
{
m_hThread = (HANDLE)_beginthreadex(NULL, 0, (_beginthreadex_proc_type)&AnimatedImage::loop, this, 0, NULL);
}
void AnimatedImage::Start()
{
m_state = 1;
}
void AnimatedImage::Stop()
{
m_state = 0;
}
void AnimatedImage::Kill()
{
m_state = 3;
}
How the class is used
For this simple test dialog application, we just create a couple of global objects for our two animations.
AnimatedImage xxx;
AnimatedImage xx2;
In the OnInitDialog() method of the dialog application the animations are initialized before returning.
// TODO: Add extra initialization here
xxx.SetImageWindow(IDC_IMAGE1, this->m_hWnd);
xxx.Run();
xx2.SetImageWindow(IDC_IMAGE2, this->m_hWnd);
xx2.Run();
return TRUE; // return TRUE unless you set the focus to a control
There are two button click handlers which handle a click on either the Start button or the Stop button.
void CMFCApplication2Dlg::OnBnClickedButton1()
{
// TODO: Add your control notification handler code here
xxx.Start();
xx2.Start();
}
void CMFCApplication2Dlg::OnBnClickedButton2()
{
// TODO: Add your control notification handler code here
xxx.Stop();
xx2.Stop();
}
The dialog application main dialog resource is defined as follows in the resource file.
IDD_MFCAPPLICATION2_DIALOG DIALOGEX 0, 0, 320, 200
STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
DEFPUSHBUTTON "OK",IDOK,209,179,50,14
PUSHBUTTON "Cancel",IDCANCEL,263,179,50,14
CTEXT "TODO: Place dialog controls here.",IDC_STATIC,10,96,300,8
LTEXT "Static",IDC_IMAGE1,7,7,110,21
LTEXT "Static",IDC_IMAGE2,64,43,112,27
PUSHBUTTON "Start",IDC_BUTTON1,252,16,50,19
PUSHBUTTON "Stop",IDC_BUTTON2,248,50,57,21
END