Related
I'm not sure if it's the correct wording for my question but the issue revolves around it. I have 2 boxes that both validate on KillFocus. And another method which is called if the user presses the Next button, which calls a method that evaluates if they can continue, which validates these fields.
Due to how old this code base is, modifying this will cause issues elsewhere so I need to find a way around this without changing the way the can continue sequence is called. Here's some scenarios.
The user enters an invalid value in field 1, they press enter, the program fires the kill focus method and shows the error message, the enter key has pressed the next button which in turn validates the it again and shows the error again (different MsgBox same error). Meaning unless they unfocus manually then press enter they will always get two message boxes.
I believe this is due to the above reason as they have pressed enter which killed the focus instead of just calling can next.
Is there a way to stop the entire WM_KEYDOWN trail if it fails within the KillFocus method?
I'm sorry if this is a little bit vague and hazey, this is what I believe is happening.
#DavidHeffernan do you know of any other way to validate fields in the way that WM_KILLFOCUS does?
Allow me to make a suggestion. You can validate edit control's input in EN_CHANGE handler. From the docs:
Sent when the user has taken an action that may have altered text in an edit control.
Each time user types something, you will get this notification, which seems like a good place to validate data.
If data is invalid, you would then disable Next button using EnableWindow and indicate error somehow.
You could use EM_SHOWBALLOONTIP to pop tooltip with error message or simply change the background color of the edit control to red.
Below is the small example that illustrates my point. You should add better error checking of course, but the main idea is there:
#include <windows.h>
#include <CommCtrl.h>
#define IDC_BTN_NEXT 1000
#define IDC_BOX1 2000
#define IDC_BOX2 3000
// enable Visual Styles
#pragma comment( linker, "/manifestdependency:\"type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
language='*'\"")
// link with Common Controls library
#pragma comment( lib, "comctl32.lib")
void onBtnNext()
{
MessageBeep(0);
}
void onKillFocus(HWND box)
{
//==================== these are needed to disable Next button
HWND hwnd = ::GetParent(box);
if (NULL == hwnd) // critical error
return; // TODO: add error handling
HWND btnNext = ::GetDlgItem(hwnd, IDC_BTN_NEXT);
if (NULL == btnNext) // critical error
return; // TODO: add error handling
//==============================================================
int len = ::GetWindowTextLength(box);
if (0 == len) // it is ok, empty text, just return
return;
// if possible, use std::wstring here, I assumed you can't...
wchar_t *txt = new wchar_t[len +1];
if (0 == ::GetWindowText(box, txt, len + 1)) // critical error, according to documentation
{
// TODO: add error handling
delete[] txt;
return;
}
//====== simple validation for illustration only, treat uppercase letter as error
int isTextValid = ::isupper(txt[0]);
for (int i = 1; 0 == isTextValid && i < (len + 1); isTextValid = ::isupper(txt[++i]));
delete[] txt;
//==============================================
if (isTextValid)
{
EDITBALLOONTIP ebt;
ebt.cbStruct = sizeof(EDITBALLOONTIP);
ebt.pszText = L" Tooltip text";
ebt.pszTitle = L" Tooltip title";
ebt.ttiIcon = TTI_ERROR_LARGE;
if (!::SendMessage(box, EM_SHOWBALLOONTIP, 0, (LPARAM)&ebt))
{
//TODO: tooltip won't show, handle error
}
EnableWindow(btnNext, FALSE); // disable Next button
return; // our work is successfully done
}
if (!::SendMessage(box, EM_HIDEBALLOONTIP, 0, 0))
{
//TODO: tooltip won't hide, handle error
}
EnableWindow(btnNext, TRUE); // enable Next button
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
{
HWND hwndBox1 = CreateWindowEx(0, WC_EDIT, L"",
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
20, 20, 250, 20, hwnd, (HMENU)IDC_BOX1,
((LPCREATESTRUCT)lParam)->hInstance, 0);
if (NULL == hwndBox1) // add better error handling, this is for illustration only
return -1;
HWND hwndBox2 = CreateWindowEx(0, WC_EDIT, L"",
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
20, 50, 250, 20, hwnd, (HMENU)IDC_BOX2,
((LPCREATESTRUCT)lParam)->hInstance, 0);
if (NULL == hwndBox2) // add better error handling, this is for illustration only
return -1;
HWND hwndBtnNext = CreateWindowEx(0, WC_BUTTON, L"Next",
WS_CHILD | WS_VISIBLE | BS_CENTER | BS_DEFPUSHBUTTON,
20, 80, 50, 25, hwnd, (HMENU)IDC_BTN_NEXT,
((LPCREATESTRUCT)lParam)->hInstance, 0);
if (NULL == hwndBtnNext) // add better error handling, this is for illustration only
return -1;
}
return 0L;
case WM_COMMAND:
{
switch (HIWORD(wParam))
{
case BN_CLICKED:
{
if (LOWORD(wParam) != IDC_BTN_NEXT)
break;
onBtnNext();
}
break;
case EN_CHANGE:
{
if (LOWORD(wParam) != IDC_BOX1 && (LOWORD(wParam) != IDC_BOX2))
break;
onKillFocus((HWND)lParam);
}
break;
default:
break;
}
}
break;
case WM_CLOSE:
::DestroyWindow(hwnd);
return 0L;
case WM_DESTROY:
{
::PostQuitMessage(0);
}
return 0L;
default:
return ::DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW);
wc.lpszMenuName = NULL;
wc.lpszClassName = L"Main_Window";
wc.hIconSm = LoadIcon(hInstance, IDI_APPLICATION);
if (!RegisterClassEx(&wc))
return 0;
INITCOMMONCONTROLSEX iccex;
iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
iccex.dwICC = ICC_STANDARD_CLASSES;
InitCommonControlsEx(&iccex);
hwnd = CreateWindowEx(0, L"Main_Window", L"Test",
WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION,
50, 50, 305, 160, NULL, NULL, hInstance, 0);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
The issue was solved in a "unconventional" way, but it worked. I noticed through debugging that the program lost focus twice on the box, once when ENTER was pressed and once when the message box popped up.
I used a static bool to avoid my program doing the error checking twice. It looks something like this -
void onKillFocus()
{
static bool isValidated = false;
if(!isValidated)
{
isValidated = true;
if(/*ValidationCheck*/)
{
//messagebox for error
}
}
}
By using this, the validation is only ran once when focus is killed stopping the message box from appearing twice, as the static bool is only alive for as long as the method is ran, meaning it's reset every time killfocus is called.
I have been expanding my library (Physical library of these weird things called 'books'... I know... I know) and I'm reading Beginning DirectX 9.0 by Wendy Jones. As I have gone through some books in the past that are 'outdated' the logic behind them is actually the same, if not more important in the earlier versions (in my experience) of things like C++ books I have read. The issue I am having with this DirectX 9 book is, 10/10 practice codes, don't work, ever. Even the solutions found on here, and MSDN
didn't work for me. (Identical problem).
So I was hoping if you could tell me before I go and purchase a book on DX11, if it might be something to do with my compiler/vs or the fact that vs is updated 2015, and this DX9 is obselete/DX11 standards have been introduced.
//Include the Windows header file that's needed for all Windows applications
#include <Windows.h>
HINSTANCE hInst; // global handle to hold the application instance
HWND wndHandle; // global variable to hold the window handle
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow);
//forward declerations
bool initWindow(HINSTANCE hInstance);
LRESULT CALLBACK WndProc(HWND, UINT WPARAM, LPARAM);
//This is winmain, the main etry point for Windows applications
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
//Initialize the window
if (!initWindow(hInstance))
return false;
//main message loop: (See page 13, "Adding the Windows Code" - Chapter 2
MSG msg;
ZeroMemory(&msg, sizeof(msg));
while (msg.message != WM_QUIT);
{
//Check the message queue
while (GetMessage(&msg, wndHandle, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return(int)msg.wParam;
}
/******************************************************************************
* bool initWindow( HINSTANCE hInstance )
* initWindow registers the window class for the application, creates the window
******************************************************************************/
bool initWindow(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
//Fill in the WNDCLASSEX structure. THis describes how the window will look to the system
wcex.cbSize = sizeof(WNDCLASSEX); // the size of the structure
wcex.style = CS_HREDRAW | CS_VREDRAW; // the class style
wcex.lpfnWndProc = (WNDPROC)WndProc; // the window procedure callback
wcex.cbClsExtra = 0; // extra bytes to allocate for this calss
wcex.cbWndExtra = 0; // extra bytes to allocate for this instance
wcex.hInstance = hInstance; // handle to the application
wcex.hIcon = 0; // icon to associate with the application
wcex.hCursor = LoadCursor(NULL, IDC_ARROW); // the default cursor
wcex.lpszMenuName = NULL; // the resource name for the menu
wcex.lpszClassName = NULL; // the class name being created
wcex.hIconSm = 0;
RegisterClassEx(&wcex);
//Create the window
wndHandle = CreateWindow(
(LPCWSTR)"DirectXExample", // the window class to use
(LPCWSTR)"DirectXExample", // the title bar text
WS_OVERLAPPEDWINDOW, // the window style
CW_USEDEFAULT, // the starting x coordinate
CW_USEDEFAULT, // the starting y coordinate
640, //the pixel width of the window
480, //the pixel height of the window
NULL, // the parent window; NULL for desktop
NULL, // the menu for the application; NULL for none
hInstance, // the handle to the apllication instance
NULL); // no values passed to the window
//make sure that the window handle that is created is valid
if (!wndHandle)
return false;
//Display the window on the screen
ShowWindow(wndHandle, SW_SHOW);
UpdateWindow(wndHandle);
return true;
}
It's perfectly fine to keep using DirectX 9. But your implementation for getting a minimal host window up on the screen has some simple bugs. It also is doing some bad casts between ANSI and wide strings. Let's get you fixed:
Remove the forward declaration of WinMain. This line, just remove it.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow);
In the actual function body for WinMain, change the type for lpCmdLine from LPTSTR parameter to be just LPSTR.
//This is winmain, the main etry point for Windows applications
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
//Initialize the window
if (!initWindow(hInstance))
Your declaration of WndProc is also incorrect. WndProc should be declared as follows:
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wparam, LPARAM lparam);
Once you fix the above declaration of WndProc, you can take out that bad cast operation in the WNDCLASS initialization. Change this:
wcex.lpfnWndProc = (WNDPROC)WndProc; // the window procedure callback
To this:
wcex.lpfnWndProc = WndProc; // the window procedure callback
You are missing a definition of WndProc. You need to implement that function yourself. Here's a minimal implementation:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CLOSE:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
The above will get your code to compile, but it will still have some bugs and won't actually run like it should. Let's fix those.
First, your message pump has an extra ; that is preventing it from actually running and keeping your code in an infinite loop. This line:
while (msg.message != WM_QUIT);
Should be (without the semicolon):
while (msg.message != WM_QUIT)
And while I'm here, your message pump implementation is kind of weird. GetMessage only returns FALSE when msg.message==WM_QUIT So the outer loop is not needed. Change this:
while (msg.message != WM_QUIT)
{
//Check the message queue
while (GetMessage(&msg, wndHandle, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
to be this:
//Check the message queue until WM_QUIT is received
while (GetMessage(&msg, wndHandle, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
When you actually implement your graphics loop for your DX app, you can change the GetMessage call to PeekMessage and then explicitly check for WM_QUIT then.
Your initWindow is failing for many reasons.
You are leaving some garbage values in the WNDCLASSEX variable. Change this line:
WNDCLASSEX wcex;
To be this:
WNDCLASSEX wcex = {};
You are forgetting to set wcex.lpszClassName. Make it this:
wcex.lpszClassName = L"DirectXExample";
And then your casting of ANSI strings to (LPCWSTR) is incorrect. To make it easier, here's a fixed version of your initWindow function.
bool initWindow(HINSTANCE hInstance)
{
WNDCLASSEX wcex = {};
//Fill in the WNDCLASSEX structure. THis describes how the window will look to the system
wcex.cbSize = sizeof(WNDCLASSEX); // the size of the structure
wcex.style = CS_HREDRAW | CS_VREDRAW; // the class style
wcex.lpfnWndProc = (WNDPROC)WndProc; // the window procedure callback
wcex.cbClsExtra = 0; // extra bytes to allocate for this calss
wcex.cbWndExtra = 0; // extra bytes to allocate for this instance
wcex.hInstance = hInstance; // handle to the application
wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION); // icon to associate with the application
wcex.hCursor = LoadCursor(NULL, IDC_ARROW); // the default cursor
wcex.lpszMenuName = NULL; // the resource name for the menu
wcex.lpszClassName = L"DirectXExample"; // the class name being created
wcex.hIconSm = 0;
RegisterClassEx(&wcex);
//Create the window
wndHandle = CreateWindow(
L"DirectXExample", // the window class to use
L"DirectXExample", // the title bar text
WS_OVERLAPPEDWINDOW, // the window style
CW_USEDEFAULT, // the starting x coordinate
CW_USEDEFAULT, // the starting y coordinate
640, //the pixel width of the window
480, //the pixel height of the window
NULL, // the parent window; NULL for desktop
NULL, // the menu for the application; NULL for none
hInstance, // the handle to the apllication instance
NULL); // no values passed to the window
//make sure that the window handle that is created is valid
if (!wndHandle)
return false;
//Display the window on the screen
ShowWindow(wndHandle, SW_SHOW);
UpdateWindow(wndHandle);
return true;
}
And that should do it.
Using Visual Studio 2013
I have an application that could potentially use up to about 20 window classes, but not all at the same time. In order to save on space I decided to Unregister those not needed any more before starting another batch of window classes, but I could not make the UnregisterClass function work.
I called Unregister at WM_DESTROY and/or WM_NCDESTROY but it always returned error message 1412 'Class still has open window'. Perhaps the Unregister call in WM_DESTROY failed because the window had not been destroyed yet, but I did not expect the call in WM_NCDESTROY to fail since this message is sent after destruction of the window.
The only way I could make UnregisterClass work was to call PostQuitMessage at either WM_DESTROY or WM_NCDESTROY. Then UnregisterClass would work after the message loop just before the whole application exits, but I want to start another batch of classes from inside the application, not to have to start it all over.
I am submitting a test program that shows the problem. It is Win32Project7, a program provided by Visual Studio 2013 with two tiny additions by myself - wrapped Messagebox (mbox) and a procedure to call unregister (tryunreg).
One extreme would be to register 20 window classes just to have them ready when needed, another would be to use a single windowclass and multiplex on HWND. Not too keen on any of these.
Questions:
Have I made mistakes or wrong assumptions in this program?
Is there a way to make unregisterclass work without having to close the program?
how much space would a typical windowclass register need? Is it likely to be KB or MB? Any way to experiment to find out?
Have Googled on this. Did not find anything that is not already in the documentation, e.g. like unregister is automatic on exit from application. Stackoverflow has two posts similar to this, but with no answers.
The code:
I placed the code like this:
<pre>
program fragments
</pre>
enclosed between html pre tags
but the post was not sent. Error message said the text was formatted like
a program but the indentation was not 4 spaces. Originally it wasn't but I changed it, but it still was not sent.
Have never sent questions on this forum, so I am doing something wrong. What?
Here is the code I did not know how to send in my original post.
Better late than never.
// Win32Project7.cpp : Defines the entry point for the application.
#include "stdafx.h"
#include "Win32Project7.h"
#define MAX_LOADSTRING 100
// Global Variables:
HINSTANCE hInst;
TCHAR szTitle[MAX_LOADSTRING];
TCHAR szWindowClass[MAX_LOADSTRING];
// Forward declarations of functions included in this code module:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
static void mbox(const wchar_t * msg) // added
{
int errcode;
const wchar_t * caption = L"Info";
int res = MessageBox(NULL, msg, caption, 0);
if (res == 0)
{
errcode = GetLastError();
return; // was setting breakpoint, but never got here
// but mbox does not give any output after postquit
}
}
static void tryunreg(const wchar_t * where) // added
{
int errcode;
wchar_t outmsg[100];
BOOL b = UnregisterClass(szWindowClass, hInst);
if (!b)
{
errcode = GetLastError();
wsprintf(outmsg, L"%s: Unreg failed for classname %s errcode %d",
where, szWindowClass, errcode);
}
else
{
wsprintf(outmsg, L"%s: Unreg worked", where);
}
mbox(outmsg);
}
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: Place code here.
MSG msg;
HACCEL hAccelTable;
// Initialize global strings
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_WIN32PROJECT7, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance,
MAKEINTRESOURCE(IDC_WIN32PROJECT7));
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
tryunreg(L"After message loop" ); // added this
return (int) msg.wParam;
}
//
// FUNCTION: MyRegisterClass()
//
// PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance,
MAKEINTRESOURCE(IDI_WIN32PROJECT7));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_WIN32PROJECT7);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance,
MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
//
// FUNCTION: InitInstance(HINSTANCE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global
// variable and
// create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // Store instance handle in our global variable
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// PURPOSE: Processes messages for the main window.
//
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
BOOL b;
int errcode;
wchar_t msg[100];
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX),
hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
break;
case WM_CLOSE: // added
// mbox(L"#wm_close before destroywindow");
DestroyWindow(hWnd);
break;
case WM_DESTROY:
tryunreg(L"#wm_destroy before postquit"); // added
PostQuitMessage(0); // in original MS code
tryunreg(L"#wm_destroy after postquit"); // added
break;
case WM_NCDESTROY: // added
tryunreg(L"#wm_NCdestroy before postquit"); // added
//PostQuitMessage(0);
tryunreg(L"#wm_NCdestroy after postquit"); // added
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
The time that UnregisterClass is needed is a dynamically loaded DLL that registers a window class. Such a library needs to ensure that the class is unregistered before it unloads, otherwise a CreateWindow for that class would make a call to code that is no longer present.
If you do choose to unregister window classes a delay can be introduced by using QueueUserAPC, however that does require changing the message loop (to one based around MsgWaitForMultipleObjectsEx and an embedded PeekMessage loop). Or you could use a thread message.
I prefer the APC because it allows for decoupling the code to be invoked from the rest of the program. For example in MFC using a thread message would require changing the message map for the thread class (the CWinApp in most cases).
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.
I've been using the win32 api to make a game with sprites. For some reason when I have more than one sprite on screen they flash occasionally as if they are disappearing and returning. When there is only one sprite on screen it displays correctly.
I am using C++, win32 API and working with Visual Studio 08
The following is roughly what I have:
//creates rect based on window client area
GetClientRect(ghwnd, &screenRect);
// Initialises front buffer device context (window)
frontHDC = GetDC(ghwnd);
// sets up Back DC to be compatible with the front
backHDC = CreateCompatibleDC(frontHDC);
// Create another hdc to store the bitmap in before the backbuffer
bitmapHDC = CreateCompatibleDC(frontHDC);
//creates bitmap compatible with the front buffer
theOldFrontBitMap = CreateCompatibleBitmap(frontHDC, screenRect.right, screenRect.bottom);
//creates bitmap compatible with the back buffer
theOldBackBitMap = (HBITMAP)SelectObject(backHDC, theOldFrontBitMap);
HBITMAP originalBitMap = (HBITMAP)SelectObject(bitmapHDC,bitmap);
//Transparency function
TransparentBlt( backHDC,
m_Position.x,
m_Position.y,
m_Size.x,
m_Size.y,
bitmapHDC,
0,
0,
m_Size.x,
m_Size.y,
0x00FFFFFF);
SelectObject(bitmapHDC,originalBitMap);
BitBlt(frontHDC, screenRect.left, screenRect.top,
screenRect.right, screenRect.bottom, backHDC, 0, 0, SRCCOPY);
Am I doing this correctly? and if so where am I going wrong? If I have not given enough information please tell me and I will rectify that.
The problem with creating a Win32 game is that, even if you use double buffering, you have no way to wait for the vertical retrace of the monitor to display the buffer.
Displaying the buffer or sprite while the vertical retrace is in progress can cause tearing or even the disappearing sprite that you experience.
The only real way around this is to use an SDK like OpenGL or DirectX to manage and display the buffers.
Here's a sample program that may help you, use the arrow keys to move the white box on the double buffered background:
#include <Windows.h>
RECT rcSize;
HDC hdcBackBuffer, hdcSprite;
HBITMAP hbmBackBuffer, hbmSprite;
int spriteX = 175, spriteY = 175;
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static PAINTSTRUCT ps;
switch (msg)
{
case WM_CREATE:
{
HDC hdcWindow = GetDC(hWnd);
// make back buffer
GetClientRect(hWnd, &rcSize);
hdcBackBuffer = CreateCompatibleDC(hdcWindow);
hbmBackBuffer = CreateCompatibleBitmap(hdcBackBuffer, rcSize.right - rcSize.left, rcSize.bottom - rcSize.top);
SelectObject(hdcBackBuffer, hbmBackBuffer); // SHOULD SAVE PREVIOUS...
// make sprite
hdcSprite = CreateCompatibleDC(hdcWindow);
hbmSprite = CreateCompatibleBitmap(hdcSprite, 50, 50);
SelectObject(hdcSprite, hbmSprite); // SHOULD SAVE PREVIOUS...
RECT rcSprite;
SetRect(&rcSprite, 0, 0, 50, 50);
FillRect(hdcSprite, &rcSprite, (HBRUSH)GetStockObject(WHITE_BRUSH));
ReleaseDC(hWnd, hdcWindow);
return 0;
}
case WM_KEYDOWN:
{
// SHOULD REALLY USE GetAsyncKeyState for game, but simplified here
switch (wParam)
{
case VK_LEFT:
spriteX--;
break;
case VK_RIGHT:
spriteX++;
break;
case VK_UP:
spriteY--;
break;
case VK_DOWN:
spriteY++;
break;
}
return 0;
}
case WM_ERASEBKGND:
{
return 1; // INDICATE THAT WE ERASED THE BACKGROUND OURSELVES
}
case WM_PAINT:
{
BeginPaint(hWnd, &ps);
// clear back buffer
FillRect(hdcBackBuffer, &rcSize, (HBRUSH)GetStockObject(BLACK_BRUSH));
// render sprite to back buffer
BitBlt(hdcBackBuffer, spriteX, spriteY, 50, 50, hdcSprite, 0, 0, SRCCOPY);
// render back buffer to screen
BitBlt(ps.hdc, 0, 0, rcSize.right - rcSize.left, rcSize.bottom - rcSize.top, hdcBackBuffer, 0, 0, SRCCOPY);
EndPaint(hWnd, &ps);
return 0;
}
case WM_DESTROY:
{
// TODO - DESTROY ALL BITMAPS AND DEVICE CONTEXTS
PostQuitMessage(0);
return 0;
}
default:
{
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
}
int WINAPI WinMain(HINSTANCE hPrevInstance, HINSTANCE hInstance, LPSTR lpCmdLine, int nShowCmd)
{
static TCHAR className[] = TEXT("GameClass");
static TCHAR windowName[] = TEXT("A Game");
WNDCLASSEX wcex;
wcex.cbClsExtra = 0;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.cbWndExtra = 0;
wcex.hbrBackground = NULL;
wcex.hCursor = LoadCursor(hInstance, IDC_ARROW);
wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
wcex.hIconSm = NULL;
wcex.hInstance = hInstance;
wcex.lpfnWndProc = WndProc;
wcex.lpszClassName = className;
wcex.lpszMenuName = NULL;
wcex.style = 0;
if (!RegisterClassEx(&wcex))
return 0;
HWND hWnd = CreateWindow(className, windowName, WS_CAPTION | WS_BORDER | WS_SYSMENU, 0, 0, 400, 400, NULL, NULL, hInstance, NULL);
if (!hWnd)
return 0;
ShowWindow(hWnd, nShowCmd);
UpdateWindow(hWnd);
MSG msg;
for (;;)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
break;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
InvalidateRect(hWnd, NULL, FALSE);
}
return msg.wParam;
}
I think your back buffer implementation is wrong although Im not sure exactly where. Try this implementation of a separate back buffer class. I hope it helps.
Here my back buffer class.
#ifndef BACKBUFFER_H
#define BACKBUFFER_H
#include <Windows.h>
class BackBuffer
{
public:
BackBuffer(HWND hWnd, int width, int height);
~BackBuffer();
HDC getDC();
int width();
int height();
void present();
private:
// Make copy constructor and assignment operator private
// so client cannot copy BackBuffers. We do this because
// this class is not designed to be copied because it
// is not efficient--copying bitmaps is slow (lots of memory).
// In addition, most applications will probably only need one
// BackBuffer anyway.
BackBuffer(const BackBuffer& rhs);
BackBuffer& operator=(const BackBuffer& rhs);
private:
HWND mhWnd;
HDC mhDC;
HBITMAP mhSurface;
HBITMAP mhOldObject;
int mWidth;
int mHeight;
};
#endif //BACKBUFFER_H
Heres the implementation:
BackBuffer::BackBuffer(HWND hWnd, int width, int height)
{
//Save a copy of the main window handle
mhWnd = hWnd;
//Get a handle to the device context associated with
// the window
HDC hWndDC = GetDC(hWnd);
//Save the backbuffer dimensions
mWidth = width;
mHeight = height;
//Create system memory device context that is compatible
//with the window one
mhDC = CreateCompatibleDC(hWndDC);
//Create the backbuffer surface bitmap that is compatible
//with the window device context bitmap format. That is
//the surface we will render onto.
mhSurface = CreateCompatibleBitmap(hWndDC, width, height);
//Done with DC
ReleaseDC(hWnd, hWndDC);
//At this point, the back buffer surface is uninitialized,
//so lets clear it to some non-zero value. Note that it
//needs to be a non-zero. If it is zero then it will mess
//up our sprite blending logic.
//Select the backbuffer bitmap into the DC
mhOldObject = (HBITMAP)SelectObject(mhDC, mhSurface);
//Select a white brush
HBRUSH white = (HBRUSH)GetStockObject(WHITE_BRUSH);
HBRUSH oldBrush = (HBRUSH)SelectObject(mhDC, white);
//Clear the backbuffer rectangle
Rectangle(mhDC, 0, 0, mWidth, mHeight);
//Restore the original brush
SelectObject(mhDC, oldBrush);
}
BackBuffer::~BackBuffer()
{
SelectObject(mhDC, mhOldObject);
DeleteObject(mhSurface);
DeleteDC(mhDC);
}
HDC BackBuffer::getDC()
{
return mhDC;
}
int BackBuffer::width()
{
return mWidth;
}
int BackBuffer::height()
{
return mHeight;
}
void BackBuffer::present()
{
//Get a handle to the device context associated with
//the window
HDC hWndDC = GetDC(mhWnd);
//Copy the backbuffer contents over to the
//window client area
BitBlt(hWndDC, 0, 0, mWidth, mHeight, mhDC, 0, 0, SRCCOPY);
//Free window DC when done
ReleaseDC(mhWnd, hWndDC);
}
Try to work your way through this implementation the comments should help you understand. Hope this helps.