Dialog box freezes and stops responding - c++

I have a very specific problem involving a modeless dialog box in my application.
The dialog box freezes and becomes unresponsive to any messages sent to it by other functions in my application. What is interesting is that my debugging tells me that it freezes when the dialog procedure has received just around 5000 messages that it DID NOT handle. The only explanation I can think of is that the Windows Message Queue may be full and it is more or less confirmed by the fact that the stream of messages going through the dialog box seem to tone down immensely.
Now - I've never used dialog boxes in conjunction with an ordinary main window before, and so I may be making illegal moves. By this I mean that I update the dialog box's controls (static texts and a list box) directly by sending the specific controls messages using SendMessage or SetWindowText functions.
What I think is weird is, that this technique works perfectly until 5000 messages have passed.
The main loop sends messages to the dialog box via the parent window handle and use of IsDialogMessage function.
Both the Main window and the dialog box still receives messages, but the dialog box freezes.
Is there a way for me to empty the message queue manually or check its current volume to check if that is actually the problem? I use PeekMessage function to retrieve my messages, which according to MSDN should remove a message from the bottom of the Message queue.
Here is how I've implemented My main loop ( I am pretty sure it's completely legal ):
while (true) //while there is a message
{
//if there was a windows message
if ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
{
if ( msg.message == WM_QUIT ) //if the message was WM_QUIT
return 0; //Exit the message loop
if ( !IsDialogMessage( m_StatusHwnd, &msg ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}
else
{
advanceFrame();
}
}
I really hope one of you have an idea about what is wrong, because this is REALLY hard hard to debug!
The Dialog procedure is implemented like so: ( Sorry that you have to see my actual code )
First the static dialog procedure redirects the messages to a custom method:
BOOL CALLBACK DXCore::statusDlgProc( HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam )
{
if ( msg == WM_INITDIALOG ) SetWindowLongPtr( hwnd, DWLP_USER, lParam);
DXCore * pCore = reinterpret_cast<DXCore*>( GetWindowLongPtr( hwnd, DWLP_USER ) ) ;
if ( pCore ) return pCore->displayStatusDlgProc( hwnd, msg, wParam, lParam );
//return the message for windows to handle it
return FALSE;
}
Then the actual procedure looks like this:
BOOL DXCore::displayStatusDlgProc( HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam )
{
HBRUSH brush = CreateSolidBrush( COLORREF( RGB( 255, 0, 0 ) ) ); //red
HPEN blackPen = CreatePen( PS_SOLID, 2, COLORREF( RGB(0,0,0 ) ) );
HDC hdc; PAINTSTRUCT ps;
RECT clientArea;
GetClientRect( hwnd, &clientArea );
int gizmoRadius= 5;
m_GismoOrigon.x = clientArea.left + 150;
m_GismoOrigon.y = clientArea.top + 460;
//OutputDebugString( "Dillermand\n" );
dlgProcCounter += 1;
switch ( msg )
{
case WM_INITDIALOG:
m_FPSCount = GetDlgItem( hwnd, IDC_STATIC_FPS );
if ( !m_FPSCount ) MessageBox( NULL, "ghFPSCount", "DAMN", MB_OK );
m_CamPosX = GetDlgItem( hwnd, IDC_CAMPOSX );
if ( !m_CamPosX ) MessageBox( NULL, "ghCamPosX", "DAMN", MB_OK );
m_CamPosY = GetDlgItem( hwnd, IDC_CAMPOSY );
if ( !m_CamPosY ) MessageBox( NULL, "ghCamPosY", "DAMN", MB_OK );
m_CamPosZ = GetDlgItem( hwnd, IDC_CAMPOSZ );
if ( !m_CamPosZ ) MessageBox( NULL, "ghCamPosZ", "DAMN", MB_OK );
m_hStatusMessages = GetDlgItem( hwnd, IDSTATUS_PROGMSG );
if ( !m_hStatusMessages ) MessageBox( NULL, "ghStatusMessages", "DAMN", MB_OK );
else
{
SetParent( m_hStatusMessages, hwnd );
}
m_RunButton = GetDlgItem( hwnd, IDCSTATUS_RUN_BTN );
if ( !m_RunButton ) MessageBox( NULL, "ghRunButton ", "DAMN", MB_OK );
m_PauseButton = GetDlgItem( hwnd, IDSTATUS_PAUSE_BTN );
if ( !m_PauseButton ) MessageBox( NULL, "ghPauseButton", "DAMN", MB_OK );
SetWindowText( m_CamPosX, "0" );
SetWindowText( m_CamPosY, "0" );
SetWindowText( m_CamPosZ, "0" );
return TRUE;
case WM_PAINT:
hdc = BeginPaint( hwnd, &ps );
SelectObject( hdc, brush );
SelectObject( hdc, blackPen );
Ellipse( hdc, m_GismoOrigon.x - gizmoRadius, m_GismoOrigon.y - gizmoRadius, m_GismoOrigon.x + gizmoRadius, m_GismoOrigon.y + gizmoRadius ) ;
EndPaint( hwnd, &ps );
return TRUE;
case WM_COMMAND:
return TRUE;
case WM_NOTIFY:
return TRUE;
case WM_CTLCOLORSTATIC:
return TRUE;
case WM_TIMER:
return TRUE;
case WM_DESTROY:
if ( MessageBox( hwnd, "Exit Program?", "Do Not Want!", MB_YESNO ) == IDYES )
{
PostQuitMessage( 0 );
}
else ShowWindow(m_StatusHwnd, true );
return TRUE;
case WM_CLOSE:
DestroyWindow( m_StatusHwnd );
return TRUE;
default:
string s = std::to_string( dlgProcCounter ) + " Unhandled Dlg message: " + std::to_string( msg ) + "\n";
OutputDebugString( s.c_str( ) );
return (INT_PTR)FALSE;
}
return FALSE;
}

Your dialog procedure is creating two GDI objects, a brush and pen, every time it's called. It never destroys these objects. By default there's a 10,000 per process limit on GDI objects. Once you reach that limit the calls to create the objects will fail. Your code will then try to draw using invalid handle values, making it appear that your window has frozen.
The solution is to only create the objects once when handling the WM_INITDIALOG message. Also always check the return value of the functions you call for errors. If you had checked the return values to CreateSolidBrush and CreatePen you potentially could have figured this out earlier.

Related

WM_TIMER message called but no timer set

I am writing a fairly simple windows C++ application in Visual Studio Express 2013, and at startup have a problem where the window hangs for 5-10 seconds due to a DispatchMessage processing a WM_TIMER message. The problem is, I never created a timer, so I'm not sure why I'm getting the message nor why there is this long hang.
enum KRESULT
{
K_OK,
K_FALSE,
K_QUIT
};
HINSTANCE g_hInstance;
HWND g_hWnd;
char* g_winName;
char* g_name;
KRESULT MessagePump( MSG &msg )
{
if( !PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ))
{
// there weren't any messages
return K_FALSE;
}
else
{
if( msg.message == WM_QUIT )
return K_QUIT;
if( msg.message == WM_CLOSE )
return K_QUIT;
TranslateMessage( &msg );
DispatchMessage( &msg );
}
return K_OK;
}
void Run()
{
MSG msg;
bool done = false;
while( !done )
{
KRESULT msgRes = K_OK;
while( msgRes != K_FALSE )
{
msgRes = MessagePump( msg );
if( msgRes == K_QUIT )
{
done = true;
break;
}
done = !DoFrame();
}
}
}
bool DoFrame()
{
// do a bunch of stuff, disabled for debugging.
// returns false if Esc is pressed
return true;
}
LRESULT CALLBACK MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch( uMsg )
{
// Code managing WM_SIZE, WM_ENTERSIZEMOVE, etc that has to do with changing the size of the window.
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
bool bRunDefault = false;
switch( uMsg )
{
// check if the window is being destroyed.
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
// check if the window is being closed.
case WM_CLOSE:
{
PostQuitMessage(0);
return 0;
}
default:
{
bRunDefault = true;
break;
}
}
}
if( bRunDefault )
return MessageHandler(hWnd, uMsg, wParam, lParam);
return 0;
}
void InitWindows()
{
WNDCLASSEX wc;
DEVMODE dmScreenSettings;
int posX, posY;
// get the instance of this application
g_hInstance = GetModuleHandle(NULL);
// setup the windows class with some default settings
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = g_hInstance;
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wc.hIconSm = wc.hIcon;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_name;
wc.cbSize = sizeof(WNDCLASSEX);
// register the window class
RegisterClassEx(&wc)
// determine the resolution of the client desktop screen
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
int wWidth = 1366;
int wHeight = 768;
DWORD styleFlags;
// place it in the middle of the screen
posX = ( screenWidth - wWidth ) / 2;
posY = ( screenHeight - wHeight ) / 2;
styleFlags = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW;
// create the window with the screen settings and get the handle
g_hWnd = CreateWindowEx( WS_EX_APPWINDOW, g_name, g_winName, styleFlags,
posX, posY, wWidth, wHeight, NULL, NULL,
g_hInstance, NULL );
// bring the window up on the screen and set it as main focus
ShowWindow( g_hWnd, SW_SHOW );
SetForegroundWindow( g_hWnd );
SetFocus( g_hWnd );
// mouse cursor options
ShowCursor( true );
}
Sorry about all the code, I wasn't sure how much of it was relevant. Most of these functions are encapsulated in an app class, and my WinMain function creates an instance of the class, calls InitWindows(), and Run(). The place where it hangs is in the MessagePump function, at DispatchMessage(). The WM_TIMER msg is passed twice, each with the following parameters:
hwnd = 0x00000000 <NULL>
message = 275 (WM_TIMER)
wParam = 18876 (this is random, but varies from around 18000 to 22000 or so)
lParam = 1975757148 (this is always the same)
Then, it is passed once with wParam = 1 and lParam = 0. Always in this order, and this group of three WM_TIMER messages are called every few frames.
The hanging only happens once, however, on the first time the message is passed at the beginning.
The hanging is the worst in debug mode of course, but in Release build, it happens as well.
I can add a handler to the MessagePump function, like so:
if( msg.message == WM_TIMER )
return K_OK;
This works to stop the hanging, but then it creates another problem where, when I move the window, my whole OS freezes for about 5-10 seconds.
I have been programming for a while (by no means an expert) but this baffles me. Any help is appreciated. Thanks!
Edit:
I tried building and running on two other machines. No hanging on both. After several restarts of the original machine, removing the startup processes that I felt I didn't need, the problem finally stopped, and has remained OK even after ~50 runs. I wish it was more satisfying to report which process was causing it, but I still haven't figured it out.
There was no process at the address 75C3A95C when looking at the modules window, strangely.
Something I noticed though: on all machines, the WM_TIMER messages are being passed. In addition, even with the default project you can create in VS2013 Exp, Win32Project1, these 3 WM_TIMER messages are being passed. Also, the Win32Project1 did hang as well, so it really had nothing to do with my code, but I guess... some strange interaction with a process running in my machine?
Thanks for everyone's help.
Perhaps another running program is sending yours the WM_TIMER messages, or perhaps a library you are linking in. Try running on on another, clean machine.

How to properly handle a win32 GUI message loop?

I'm making a simple text editor win32 application for fun. I'm having a peculiar problem with my program. It seems that my program is not returning zero when it exits. Instead, it is returning 1385929. When my main GUI window is destroyed, I use PostQuitMessage( 0 ), but it seems that is not what is being returned in my main function's message.wParam. Here is my code thus far,
#define WIDTH 500
#define HEIGHT 400
#define EDIT_ID 10
LRESULT CALLBACK windowProc( HWND window, UINT message, WPARAM wParam, LPARAM lParam )
{
static HDC deviceContext = INVALID_HANDLE_VALUE;
static HWND editControl = INVALID_HANDLE_VALUE;
switch ( message )
{
case WM_CREATE :
deviceContext = GetDC( window );
if ( !deviceContext )
{
showWindowsError( "Creating Device Context", FALSE );
DestroyWindow( window );
}
editControl = CreateWindow(
"EDIT",
NULL,
WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_LEFT |
ES_MULTILINE | ES_AUTOVSCROLL | ES_NOHIDESEL,
0,
0,
0,
0,
window,
( HMENU )EDIT_ID,
( HINSTANCE )GetWindowLong( window, GWL_HINSTANCE ),
NULL
);
if ( !editControl )
{
showWindowsError( "Creating Edit Control", TRUE );
DestroyWindow( window );
}
return 0;
break;
case WM_COMMAND :
switch ( wParam )
{
case WM_UNDO :
SendMessage( editControl, WM_UNDO, 0, 0 );
break;
case WM_CUT :
SendMessage( editControl, WM_CUT, 0, 0 );
break;
case WM_COPY :
SendMessage( editControl, WM_COPY, 0, 0 );
break;
case WM_PASTE :
SendMessage( editControl, WM_PASTE, 0, 0 );
break;
case WM_CLEAR :
SendMessage( editControl, WM_CLEAR, 0, 0 );
break;
default:
return DefWindowProc( window, message, wParam, lParam );
}
case WM_SIZE :
MoveWindow( editControl, 0, 0, LOWORD( lParam ), HIWORD( lParam ), TRUE );
return 0;
break;
case WM_DESTROY :
ReleaseDC( window, deviceContext );
DestroyWindow( editControl );
PostQuitMessage( 0 );
return 0;
break;
}
return DefWindowProc( window, message, wParam, lParam );
}
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR CmdArgs, int nCmdShow )
{
WNDCLASSEX windowClass = { 0 };
HWND window = INVALID_HANDLE_VALUE;
MSG message = { 0 };
HBRUSH windowColor = CreateSolidBrush( GetSysColor( COLOR_WINDOW ) );
windowClass.cbSize = sizeof( windowClass );
windowClass.style = CS_HREDRAW | CS_VREDRAW;
windowClass.lpfnWndProc = windowProc;
windowClass.hInstance = hInstance;
windowClass.hCursor = LoadCursor( NULL, IDC_ARROW );
windowClass.hIcon = LoadIcon( NULL, IDI_APPLICATION );
windowClass.hbrBackground = windowColor;
windowClass.lpszClassName = "TextEditorWindow";
if ( !RegisterClassEx( &windowClass ) )
{
DeleteObject( windowColor );
showWindowsError( "Registering Windows Class", TRUE );
}
window = CreateWindow(
"TextEditorWindow",
"Text Editor",
WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
CW_USEDEFAULT,
CW_USEDEFAULT,
WIDTH,
HEIGHT,
NULL,
NULL,
hInstance,
NULL
);
if ( !window )
{
DeleteObject( windowColor );
showWindowsError( "Creating GUI", TRUE );
}
ShowWindow( window, SW_SHOW );
UpdateWindow( window );
do
{
TranslateMessage( &message );
DispatchMessage( &message );
} while ( GetMessage( &message, window, 0, 0 ) > 0 );
DeleteObject( windowColor );
return message.wParam;
}
Your call to GetMessage() has the 2nd parameter set to your window's HWND, which limits the messages received only to those sent to that window - see the documentation of the API function.
WM_QUIT is, on the other hand, a message sent to the thread running the message pump, without any particular window. Because of that filter, you don't receive it and message.wParam is never set to that 0.
But why does the loop end and the program shuts down anyway? Because that HWND in window gets invalid after the window is closed, therefore GetMessage() ends with an error and returns -1.
Additionally, you are currently calling TranslateMessage() and DispatchMessage() with message before it is filled with any correct data, on the first iteration. The loop should rather be like this:
while ( GetMessage( &message, NULL, 0, 0 ) > 0 )
{
TranslateMessage( &message );
DispatchMessage( &message );
}

Locale aware edit control subclassing for decimal numbers ( format [sign] [xxx...] [decimal separator] [yy...] )

INTRODUCTION AND RELEVANT INFORMATION:
I have an edit control that should accept only signed decimal numbers-something like -123.456. Also, it should be locale aware, since decimal separator is not the same for every country-in US dot is used, while in Europe it is comma and so on.
MY EFFORTS TO SOLVE THIS:
So far I have used subclassing to implement this. Here is my logic for implementing the subclassing, expressed through pseudo code:
if ( ( character is not a [ digit,separator, or CTRL/Shift... ] OR
( char is separator and we already have one ) )
{
discard the character;
}
First I have made a helper function that determines if the char array already has a decimal separator, like this:
bool HasDecimalSeparator( wchar_t *test )
{
// get the decimal separator
wchar_t szBuffer[5];
GetLocaleInfo ( LOCALE_USER_DEFAULT,
LOCALE_SDECIMAL,
szBuffer,
sizeof(szBuffer) / sizeof(szBuffer[0] ) );
bool p = false; // text already has decimal separator?
size_t i = 0; // needed for while loop-iterator
// go through entire array and calculate the value of the p
while( !( p = ( test[i] == szBuffer[0] ) ) && ( i++ < wcslen(test) ) );
return p;
}
And here is the subclassing procedure-I haven't taken minus sign into account:
LRESULT CALLBACK Decimalni( HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass,
DWORD_PTR dwRefData )
{
switch (message)
{
case WM_CHAR:
{
// get decimal separator
wchar_t szBuffer[5];
GetLocaleInfo ( LOCALE_USER_DEFAULT,
LOCALE_SDECIMAL,
szBuffer,
sizeof(szBuffer) / sizeof(szBuffer[0] ) );
wchar_t t[50]; // here we store edit control's current text
memset( &t, L'\0', sizeof(t) );
// get edit control's current text
GetWindowText( hwnd, t, 50 );
// if ( ( is Not a ( digit,separator, or CTRL/Shift... )
// || ( char is separator and we already have one ) )
// discard the character
if( ( !( isdigit(wParam) || ( wParam == szBuffer[0] ) )
&& ( wParam >= L' ' ) ) // digit/separator/... ?
|| ( HasDecimalSeparator(t) // has separator?
&& ( wParam == szBuffer[0] ) ) )
{
return 0;
}
}
break;
}
return DefSubclassProc( hwnd, message, wParam, lParam);
}
One important note: I am able to load current user locale settings in my application, thanks to the answers to this question.
QUESTION:
Is there a better way to implement an edit control that accepts signed decimal numbers only, and is locale aware?
If subclassing is the only way, can my code be further improved/optimized ?
Thank you for your time and help.
Best regards.
APPENDIX:
To help you even further, here is a small demo application that creates an edit control and subclasses it to accept only decimal numbers-again, I haven't implemented the part for the minus sign:
#include <windows.h>
#include <commctrl.h>
#include <stdlib.h>
#include <locale.h>
#pragma comment( lib, "comctl32.lib")
const wchar_t g_szClassName[] = L"myWindowClass";
bool HasDecimalSeparator( wchar_t *test )
{
// get the decimal separator
wchar_t szBuffer[5];
GetLocaleInfo ( LOCALE_USER_DEFAULT,
LOCALE_SDECIMAL,
szBuffer,
sizeof(szBuffer) / sizeof(szBuffer[0] ) );
bool p = false; // text already has decimal separator?
size_t i = 0; // needed for while loop-iterator
// go through entire array and calculate the value of the p
while( !( p = ( test[i] == szBuffer[0] ) ) && ( i++ < wcslen(test) ) );
return p;
}
LRESULT CALLBACK Decimalni( HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass,
DWORD_PTR dwRefData )
{
switch (message)
{
case WM_CHAR:
{
// get decimal separator
wchar_t szBuffer[5];
GetLocaleInfo ( LOCALE_USER_DEFAULT,
LOCALE_SDECIMAL,
szBuffer,
sizeof(szBuffer) / sizeof(szBuffer[0] ) );
wchar_t t[50]; // here we store edit control's current text
memset( &t, L'\0', sizeof(t) );
// get edit control's current text
GetWindowText( hwnd, t, 50 );
// if ( ( is Not a ( digit,separator, or CTRL/Shift... )
// || ( char is separator and we already have one ) )
// discard the character
if( ( !( isdigit(wParam) || ( wParam == szBuffer[0] ) )
&& ( wParam >= L' ' ) ) // digit/separator/... ?
|| ( HasDecimalSeparator(t) // has separator?
&& ( wParam == szBuffer[0] ) ) )
{
return 0;
}
}
break;
}
return DefSubclassProc( hwnd, message, wParam, lParam);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
{
/************* load current locale settings *************/
// max. len: language, country, code page
wchar_t lpszLocale[64+64+16+3] = L"";
wchar_t lpszVal[128];
LCID nLCID = ::GetUserDefaultLCID(); // current LCID for user
if ( ::GetLocaleInfo( nLCID, LOCALE_SENGLANGUAGE, lpszVal, 128 ) )
{
wcscat_s( lpszLocale, 147, lpszVal ); // language
if ( ::GetLocaleInfo( nLCID, LOCALE_SENGCOUNTRY, lpszVal, 128 ) )
{
wcscat_s( lpszLocale, 147, L"_" ); // append country/region
wcscat_s( lpszLocale, 147, lpszVal );
if ( ::GetLocaleInfo( nLCID,
LOCALE_IDEFAULTANSICODEPAGE, lpszVal, 128 ) )
{
// missing code page or page number 0 is no error
// (e.g. with Unicode)
int nCPNum = _wtoi(lpszVal);
if (nCPNum >= 10)
{
wcscat_s( lpszLocale, 147, L"." ); // append code page
wcscat_s( lpszLocale, 147, lpszVal );
}
}
}
}
// set locale and LCID
_wsetlocale( LC_ALL, lpszLocale );
::SetThreadLocale(nLCID);
/*************************************************/
HWND hEdit1;
hEdit1 = CreateWindowEx(0, L"EDIT", L"",
WS_BORDER | WS_CHILD | WS_VISIBLE | ES_AUTOVSCROLL | ES_AUTOHSCROLL,
50, 100, 100, 20,
hwnd, (HMENU)8001, GetModuleHandle(NULL), NULL);
SetWindowSubclass( hEdit1, Decimalni, 0, 0);
}
break;
case WM_SETTINGCHANGE:
if( !wParam && !wcscmp( (wchar_t*)lParam, L"intl" ) )
{
// max. len: language, country, code page
wchar_t lpszLocale[64+64+16+3] = L"";
wchar_t lpszVal[128];
LCID nLCID = ::GetUserDefaultLCID(); // current LCID for user
if ( ::GetLocaleInfo( nLCID, LOCALE_SENGLANGUAGE, lpszVal, 128 ) )
{
wcscat_s( lpszLocale, 147, lpszVal ); // language
if ( ::GetLocaleInfo( nLCID, LOCALE_SENGCOUNTRY, lpszVal, 128 ) )
{
wcscat_s( lpszLocale, 147, L"_" ); // append country/region
wcscat_s( lpszLocale, 147, lpszVal );
if ( ::GetLocaleInfo( nLCID,
LOCALE_IDEFAULTANSICODEPAGE, lpszVal, 128 ) )
{
// missing code page or page number 0 is no error
// (e.g. with Unicode)
int nCPNum = _wtoi(lpszVal);
if (nCPNum >= 10)
{
wcscat_s( lpszLocale, 147, L"." ); // append code page
wcscat_s( lpszLocale, 147, lpszVal );
}
}
}
}
// set locale and LCID
_wsetlocale( LC_ALL, lpszLocale );
::SetThreadLocale(nLCID);
return 0L;
}
else
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
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(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_szClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, L"Window Registration Failed!", L"Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
hwnd = CreateWindowEx(
0,
g_szClassName,
L"theForger's Tutorial Application",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 480, 320,
NULL, NULL, hInstance, NULL);
if(hwnd == NULL)
{
MessageBox(NULL, L"Window Creation Failed!", L"Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
Taking into consideration locale-specific settings
You certainly can do everything yourself, however you have an option to use VarI4FromStr or similar API which does dirty stuff for you. You put string in, you get LONG out. Locale aware.
"Should accept only"
You don't specify how the control should enforce this exactly. What if the input string is not valid? Control should be still accepting it because, for example, string is just not yet valid and user is still typing. If you are validating the input in external handler, such as when OK button is pressed, then you don't even need to subclass. If you want to check input every time it changes, you don't need to subclass either since you have EN_CHANGE notifications on parent. You might want to subclass for other reasons though.
It is user-friendly to accept any input and then indicate validity somehow (such as underlining with red if invalid) either on text change or on input validation.
After taking into account code from Advice required to insert one string into another once obtaining text from clipboard, I was able to put up a subclassing procedure that meets the requirement.
The point of my solution is to simulate edit control's behavior as mentioned in that post and then validate the resulting text.
When handling VK_DELETE, selected text is deleted and the result is then parsed to check if valid decimal format is left. If everything is OK message is passed to the default procedure else is discarded. The same method is performed for WM_CUT, WM_CLEAR, and for backspace in the WM_CHAR handler ( here we must protect ourself from crashing the app by accessing the element of the string with the ordinal -1, that is the reason why I have added the line if ( start > 0 ) ).
When handling WM_PASTE we merge the edit control's text with the clipboard text and then we parse the resulting string to check its validity. Again, if all is OK we pass the message else we discard it.
The same thing applies for WM_CHAR except we here insert character in the selected part of the edit control's text and then we perform the validity check.
Since the inputted text will be always correct this way, we do not have to handle WM_UNDO.
Finaly, here is the code:
LRESULT CALLBACK Decimalni( HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
switch (message)
{
case WM_KEYDOWN:
{
if( wParam == VK_DELETE )
{
DWORD start, end;
int len = GetWindowTextLength(hwnd);
std::wstring buffer( len, 0 );
// get current window text
if( len > 0 )
GetWindowText( hwnd, &buffer[0], len + 1 );
// get current selection
SendMessage( hwnd, EM_GETSEL, (WPARAM)&start, (LPARAM)&end );
if( end > start )
buffer.erase( start, end - start );
else
buffer.erase( start, 1 );
if( buffer.empty() )
return ::DefSubclassProc( hwnd, message, wParam, lParam);
bool IsTextValid = true; // indicates validity of inputed text
// TODO: parse buffer
if( IsTextValid )
return ::DefSubclassProc( hwnd, message, wParam, lParam);
else
{
// TODO: indicate error
return FALSE;
}
}
}
return ::DefSubclassProc( hwnd, message, wParam, lParam);;
break;
case WM_CLEAR:
case WM_CUT:
{
DWORD start, end;
int len = GetWindowTextLength(hwnd);
std::wstring buffer( len, 0 );
// get current window text
if( len > 0 )
GetWindowText( hwnd, &buffer[0], len + 1 );
// get current selection
SendMessage( hwnd, EM_GETSEL, (WPARAM)&start, (LPARAM)&end );
if( end > start )
buffer.erase( start, end - start );
if( buffer.empty() )
return ::DefSubclassProc( hwnd, message, wParam, lParam);
// TODO: parse buffer
bool IsTextValid = true;
if( IsTextValid )
return ::DefSubclassProc( hwnd, message, wParam, lParam);
else
{
// TODO: Indicate error
return FALSE;
}
}
break;
case WM_PASTE:
{
int len = GetWindowTextLength(hwnd);
std::wstring clipboard, wndtxt( len, 0 );
if( len > 0 )
GetWindowText( hwnd, &wndtxt[0], len + 1 );
if( !OpenClipboard(hwnd) )
return FALSE;
HANDLE hClipboardData;
if( hClipboardData = GetClipboardData(CF_UNICODETEXT) )
{
clipboard = (wchar_t*)GlobalLock(hClipboardData);
GlobalUnlock(hClipboardData);
}
CloseClipboard();
if( clipboard.empty() )
return FALSE;
DWORD start, end;
SendMessage( hwnd, EM_GETSEL, (WPARAM)&start, (LPARAM)&end );
// merge strings into one
if( end > start )
wndtxt.replace( start, end - start, clipboard );
else
wndtxt.insert( start, clipboard );
// TODO: parse the text
bool ITextValid = true;
// process the result
if( IsTextValid )
return ::DefSubclassProc( hwnd, message, wParam, lParam);
else
{
// TODO: indicate error
return FALSE;
}
}
break;
case WM_CHAR:
{
DWORD start, end;
int len = GetWindowTextLength(hwnd);
std::wstring buffer( len, 0 );
// get current window text
if( len > 0 )
GetWindowText( hwnd, &buffer[0], len + 1 );
// get current selection
SendMessage( hwnd, EM_GETSEL, (WPARAM)&start, (LPARAM)&end );
// allow copy/paste but leave backspace for special handler
if( ( wParam < 0x020 ) && ( wParam != 0x08 ) )
return ::DefSubclassProc( hwnd, message, wParam, lParam);}
// process backspace
if( wParam == 0x08 )
{
if( end > start )
buffer.erase( start, end - start );
else
if( start > 0 ) // it is safe to move back one place
buffer.erase( start - 1, 1 );
else // start-1 < 0 , can't access buffer[-1] !!
return FALSE;
if( buffer.empty() )
return ::DefSubclassProc( hwnd, message, wParam, lParam);
// TODO: parse buffer
// process the result
if( IsTextValid )
return ::DefSubclassProc( hwnd, message, wParam, lParam);
else
{
//TODO: indicate error
return FALSE;
}
}
// insert character and parse text
if( end > start )
buffer.replace( start, end - start, 1, (wchar_t)wParam );
else
buffer.insert( start, 1, (wchar_t)wParam );
// TODO: parse text
// process the result
if( IsTextValid )
return ::DefSubclassProc( hwnd, message, wParam, lParam);
else
{
//TODO: indicate error
return FALSE;
}
}
break;
case WM_NCDESTROY:
::RemoveWindowSubclass( hwnd, Decimalni, 0 );
break;
}
return ::DefSubclassProc( hwnd, message, wParam, lParam);
}

tabs don't work when I am losing focus

I have a GUI consists of 3 tab control
when I click on each tab the controls on that tab appears (there is a dialog for each tab that I show when that tab clicked)
the application has another dialog, when I change the focus on that dialog or any other window or program and return back to the main program I just can see the tab I left before and when I click the other tabs they don't show up.
I initialize tabs in WM_INITDIALOG and I show when each one is clicked in WM_NOTIFY like this:
case WM_NOTIFY:
switch (((LPNMHDR)lParam)->code)
{
case TCN_SELCHANGING:
{
// Return FALSE to allow the selection to change.
return FALSE;
}
break;
case TCN_SELCHANGE:
{
if( TabCtrl_GetCurSel( ( ( LPNMHDR ) lParam) -> hwndFrom ) == 0 ) {
ShowWindow( hwndTimeFrame, SW_HIDE );
ShowWindow( hwndAR, SW_HIDE );
ShowWindow( hwndInsFeed, SW_SHOW );
}
if( TabCtrl_GetCurSel( ( ( LPNMHDR ) lParam) -> hwndFrom ) == 1 )
{
ShowWindow( hwndInsFeed, SW_HIDE );
ShowWindow( hwndAR, SW_HIDE );
ShowWindow( hwndTimeFrame, SW_SHOW );
}
if( TabCtrl_GetCurSel( ( ( LPNMHDR ) lParam) -> hwndFrom ) == 2 )
{
ShowWindow( hwndInsFeed, SW_HIDE );
ShowWindow( hwndTimeFrame, SW_HIDE );
ShowWindow( hwndAR, SW_SHOW );
}
}
break;
}
break;
any suggestion ?
I think I figured out this.
I initialized the tab in WM_INITDIALOG which turned out to be the problem, because each time the application showing the tab dialogs it send this message and initialize them again. I remove this part from initdialog to initialization of the application before entering the message loop

Notify Icon Receives WM_LBUTTONDBLCLK But Not WM_CONTEXTMENU

I added a notification icon to my dialog based application, and it received WM_LBUTTONDBLCLK when the icon is double clicked on, but it is not receiving WM_CONTEXTMENU when the icon is right clicked or when the icon is highlighted with the keyboard and the context menu key is pressed. I based my usage of the notification icon on the example in the Windows 7.1 SDK Samples. So, I have no idea where I'm going wrong or why this isn't working.
Note: If I change WM_CONTEXTMENU to WM_RBUTTONUP, it receives the event, but the cursor coordinates are wrong.
/******************************************************************************/
/* Menu Resource */
/******************************************************************************/
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
IDR_TRAYMENU MENU
{
POPUP ""
{
MENUITEM "&Show Status Window", IDM__SHOW_STATUS_WINDOW
MENUITEM "&About", IDM__ABOUT
MENUITEM SEPARATOR
MENUITEM "&Exit", IDM__EXIT
}
}
/******************************************************************************/
/* WinMain() */
/******************************************************************************/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
// ... code unrelated to icon
// Enable Visual Styles
InitCommonControls();
// create the main dialog
if( NULL == (hWnd=CreateDialog(hInstance,MAKEINTRESOURCE(IDD_MAINDLG),NULL,(DLGPROC)WndProc)) )
{
MessageBox( NULL, "Error creating the main dialog!", NULL, MB_OK | MB_ICONERROR );
return -1;
}
// ... code unrelated to icon
MSG msg;
while( GetMessage(&msg,NULL,0,0) && IsWindow(hWnd) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
/******************************************************************************/
/* WndProc() */
/******************************************************************************/
BOOL CALLBACK WndProc(HWND hWndDlg, UINT Message, WPARAM wParam, LPARAM lParam)
{
switch(Message)
{
case WM_INITDIALOG:
{
// ... code unrelated to icon
hIcon = (HICON)LoadImage( GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_DDCMP), IMAGE_ICON, 16, 16, LR_DEFAULTSIZE );
// Setup the system tray icon
memset( &nid, 0, sizeof(NOTIFYICONDATA) );
nid.cbSize = sizeof(NOTIFYICONDATA);
nid.hWnd = hWndDlg;
nid.uID = 0xDDC;
nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP;
nid.uCallbackMessage = WM_APP + 0xDDC;
nid.hIcon = hIcon;
strcpy( nid.szTip, "DDCMP Driver" );
Shell_NotifyIcon( NIM_ADD, &nid );
// ... code unrelated to icon
return true;
} break;
case WM_APP + 0xDDC:
{
switch( LOWORD(lParam) )
{
case WM_CONTEXTMENU:
{
MessageBox( hWndDlg, "This message box never shows up.", NULL, MB_OK | MB_SYSTEMMODAL );
HMENU hMenu = LoadMenu(GetModuleHandle(NULL),MAKEINTRESOURCE(IDR_TRAYMENU));
if( hMenu )
{
HMENU hSubMenu = GetSubMenu(hMenu,0);
if( hSubMenu )
{
SetForegroundWindow( hWndDlg );
POINT pt = { LOWORD(wParam), HIWORD(wParam) };
UINT uFlags = TPM_RIGHTBUTTON;
if( 0 != GetSystemMetrics(SM_MENUDROPALIGNMENT) )
uFlags |= TPM_RIGHTALIGN;
else
uFlags |= TPM_LEFTALIGN;
TrackPopupMenuEx( hSubMenu, uFlags, pt.x, pt.y, hWndDlg, NULL );
}
DestroyMenu( hMenu );
}
} break;
case WM_LBUTTONDBLCLK:
if( IsWindowVisible(hWndDlg) )
ShowWindow( hWnd, SW_HIDE );
else
ShowWindow( hWnd, SW_SHOW );
break;
}
return true;
} break;
case WM_CLOSE:
ShowWindow( hWndDlg, SW_HIDE );
break;
case WM_DESTROY:
case WM_QUIT:
{
Shell_NotifyIcon( NIM_DELETE, &nid );
// ... code unrelated to icon
return true;
} break;
}
return false;
}
This is the WndProc from the Windows 7.1 SDK Sample
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HWND s_hwndFlyout = NULL;
static BOOL s_fCanShowFlyout = TRUE;
switch (message)
{
case WM_CREATE:
// add the notification icon
if (!AddNotificationIcon(hwnd))
{
MessageBox(hwnd,
L"Please read the ReadMe.txt file for troubleshooting",
L"Error adding icon", MB_OK);
return -1;
}
break;
case WM_COMMAND:
{
int const wmId = LOWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_LOWINK:
ShowLowInkBalloon();
break;
case IDM_NOINK:
ShowNoInkBalloon();
break;
case IDM_PRINTJOB:
ShowPrintJobBalloon();
break;
case IDM_OPTIONS:
// placeholder for an options dialog
MessageBox(hwnd, L"Display the options dialog here.", L"Options", MB_OK);
break;
case IDM_EXIT:
DestroyWindow(hwnd);
break;
case IDM_FLYOUT:
s_hwndFlyout = ShowFlyout(hwnd);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
break;
case WMAPP_NOTIFYCALLBACK:
switch (LOWORD(lParam))
{
case NIN_SELECT:
// for NOTIFYICON_VERSION_4 clients, NIN_SELECT is prerable to listening to mouse clicks and key presses
// directly.
if (IsWindowVisible(s_hwndFlyout))
{
HideFlyout(hwnd, s_hwndFlyout);
s_hwndFlyout = NULL;
s_fCanShowFlyout = FALSE;
}
else if (s_fCanShowFlyout)
{
s_hwndFlyout = ShowFlyout(hwnd);
}
break;
case NIN_BALLOONTIMEOUT:
RestoreTooltip();
break;
case NIN_BALLOONUSERCLICK:
RestoreTooltip();
// placeholder for the user clicking on the balloon.
MessageBox(hwnd, L"The user clicked on the balloon.", L"User click", MB_OK);
break;
//
//
// As you can very plainly see, the Windows SDK Sample ONLY used WM_CONTEXTMNEU
//
//
case WM_CONTEXTMENU:
{
POINT const pt = { LOWORD(wParam), HIWORD(wParam) };
ShowContextMenu(hwnd, pt);
}
break;
}
break;
case WMAPP_HIDEFLYOUT:
HideFlyout(hwnd, s_hwndFlyout);
s_hwndFlyout = NULL;
s_fCanShowFlyout = FALSE;
break;
case WM_TIMER:
if (wParam == HIDEFLYOUT_TIMER_ID)
{
// please see the comment in HideFlyout() for an explanation of this code.
KillTimer(hwnd, HIDEFLYOUT_TIMER_ID);
s_fCanShowFlyout = TRUE;
}
break;
case WM_DESTROY:
DeleteNotificationIcon();
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
I think you should try changing the uVersion member of the NOTIFYICONDATA structure to NOTIFYICON_VERSION_4, the documentation states that this members value will tell how the uCallbackMessage parameters will be interpreted when passed on to your callback function.
You can also have a look at this: Basic use of Shell_NotifyIcon in Win32
I did a bit of research and the following should work for you:
memset(&nid, 0, sizeof(NOTIFYICONDATA));
nid.cbSize = sizeof(NOTIFYICONDATA);
nid.hWnd = hWndDlg;
nid.uID = 0xDDC;
nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP;
nid.uCallbackMessage = WM_APP + 0xDDC;
nid.hIcon = hIcon;
nid.uVersion = NOTIFYICON_VERSION_4;
strcpy(nid.szTip, "DDCMP Driver");
Shell_NotifyIcon(NIM_ADD, &nid);
Shell_NotifyIcon(NIM_SETVERSION, &nid);
NIM_SETVERSION (MSDN):
Shell32.dll version 5.0 and later only. Instructs the notification
area to behave according to the version number specified in the
uVersion member of the structure pointed to by lpdata. The version
number specifies which members are recognized.
The notify icon has changed behaviour over the years. For reasons of compatibility with pre-existing code, you must opt-in to the new behaviour. If you don't opt-in then you don't get sent WM_CONTEXTMENU messages. Instead you have to respond to WM_RBUTTONUP. Even if you invoke the context menu from the keyboard, the system still sends WM_RBUTTONUP. You have to obtain the cursor position, in order to know where to show the menu, by calling GetCursorPos.
You can opt in to the new behaviour (and WM_CONTEXTMENU) as described in the documentation, by calling Shell_NotifyIcon passing NIM_SETVERSION after the NIM_ADD call. Presumably the SDK sample you are looking at does this somewhere. My guess is that is what is missing from your code.
The key extract from the documentation is in the remarks section:
As of Windows 2000 (Shell32.dll version 5.0), Shell_NotifyIcon mouse and keyboard events are handled differently than in earlier Shell versions found on Microsoft Windows NT 4.0, Windows 95, and Windows 98. The differences include the following:
If a user selects a notify icon's shortcut menu with the keyboard, the Shell now sends the associated application a WM_CONTEXTMENU message. Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages.
If a user selects a notify icon with the keyboard and activates it with the SPACEBAR or ENTER key, the version 5.0 Shell sends the associated application an NIN_KEYSELECT notification. Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages.
If a user selects a notify icon with the mouse and activates it with the ENTER key, the Shell now sends the associated application an NIN_SELECT notification. Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages.