In this article, you will learn about a universal automatic disappearing dialog that can be used during mouse capture, designed for implementing VERIFY and ASSERT on ReactOS, running on any version of Windows.
Introduction
ReactOS is an open source alternative to the Windows operation system. Even if the first version of ReactOS dates back to 1998, there is still no 'stable' version of ReactOS. May be, the most important reason is a lack of attention.
The motivation for this tip comes from the fact that:
- I want to use the
ASSERT
and VERIFY
macros on ReactOS in the same way as on Windows - but a captured mouse cannot be released and thus the
MessageBox
cannot be used either.
For details about the ASSERT
and VERIFY
macros, see the CODE PROJECT article Useful Debugging Macros by William E. Kempf.
Background
The ASSERT
and VERIFY
macros start a dialog that reports the failed assertion or verification. Probably this dialog is a MessageBox
. This could be done for ReactOS as well, but there is a problem in the particular case of mouse capture. There are essentially two situations in which the mouse is captured:
- Between
WM_INITMENUPOPUP
and WM_UNINITMENUPOPUP
and - during a drag and drop operation (see the Code Project article Capturing the Mouse by Chris Becke).
Unfortunately both solutions, which are presented in the CODE PROJECT article, How to bring window to top with SetForegroundWindow() by Siarhei Boika, do not work on ReactOS.
After trying for some time to find any combination of SetCapture()
, ReleaseCapture()
and EnableWindow()
with other API calls (that are available on ReactOS - e.g., AllowSetForegroundWindow()
is not available on ReactOS) to release the mouse, I gave up and thought about: Which solutions are possible when the mouse is still captured? It seemed to me to be the best solution, in case the mouse is not available at all, to have a dialog that disappears automatically. Therefore, I developed the C++ class AutoDisappearDlg
, which is universally applicable and can also be used under Windows. The implementation is based on Win32
, not on MFC or WTL.
Using the Code
All source code presented here is part of my Ogww
library and is fully included in the attachment to this article. I have introduced the Ogww
library with my CODE PROJECT article, More about OpenGL with C/C++ and C# on ReactOS (or Windows) and enhanced with my Code Project article, A basic icon editor running on ReactOS.
I will start with my macro declarations (Ogww.hpp):
#ifndef __OGWW_H__
#define __OGWW_H__
#define LIB_VERSION 0.20
int __cdecl OgwwMainFrame_VERIFY_MSGBOX(const char* szExpression, float fLibVersion,
const char* szFileName, int nFileLine);
int __cdecl OgwwMainFrame_ASSERT_MSGBOX(const char* szExpression, float fLibVersion,
const char* szFileName, int nFileLine);
#ifndef verify
#define verify(expression) ((expression) ? (void)0 : (void)fprintf(stderr,
"Failed to verify '%s' in library version %3.2f file '%s' in line %d.\n" ,
#expression, LIB_VERSION, __FILE__, __LINE__))
#endif // verify
#define VERIFY(expression) ((expression) ? (void)0 :
(void)OgwwMainFrame_VERIFY_MSGBOX(#expression, LIB_VERSION, __FILE__, __LINE__))
#define ASSERT(expression) ((expression) ? (void)0 :
(void)OgwwMainFrame_ASSERT_MSGBOX(#expression, LIB_VERSION, __FILE__, __LINE__))
#endif // __OGWW_H__
OgwwMainFrame_VERIFY_MSGBOX()
and OgwwMainFrame_ASSERT_MSGBOX()
are function prototypes, that are implemented within OgwwMainFrame.cpp and used by my ASSERT
and VERIFY
macros (forward declaration).
The assert
macro is already defined by C++, so I only need to add the verify
macro. To become compatible to the Windows programming standard, I finally defined the ASSERT
and VERIFY
macros.
Now I can choose whether to use the assert
und verify
macros (without dialog prompt) or the ASSERT
and VERIFY
macros (with dialog prompt) in the source code of my application.
The implementation of OgwwMainFrame_VERIFY_MSGBOX()
and OgwwMainFrame_ASSERT_MSGBOX()
are looking like that (OgwwMainFrame.cpp):
const char* VERIFY_FORMAT =
"Failed to verify '%s'~in OGWW library version %3.2f~file '%s'~line %d.\n";
const char* ASSERT_FORMAT =
"Failed to assert '%s'~in OGWW library version %3.2f~file '%s'~line %d.\n";
int __cdecl OgwwMainFrame_VERIFY_MSGBOX(const char* szExpression, float fLibVersion,
const char* szFileName, int nFileLine)
{
size_t nLen = strlen(VERIFY_FORMAT)
+ (szExpression != NULL ? strlen(szExpression) : 0) + 5
+ (szFileName != NULL ? strlen(szFileName ) : 0) + 6;
char* szBuffer = new char[nLen];
memset(szBuffer, 0, nLen);
sprintf(szBuffer, VERIFY_FORMAT, szExpression, fLibVersion, szFileName, nFileLine);
OgwwHandledObjectList* inst = OgwwMainFrame::InstanceList();
OgwwMainFrame* pWnd = (OgwwMainFrame*)(inst != NULL && inst->FirstItem() != NULL ?
inst->FirstItem()->HandledObj : NULL);
if (GetCapture() == NULL)
{
for (size_t nPos = 0; nPos < nLen; nPos++)
if (szBuffer[nPos] == '~') szBuffer[nPos] = ' ';
HWND hWnd = (pWnd != NULL ? pWnd->HWnd() : ::GetDesktopWindow());
::MessageBoxA(hWnd, szBuffer, "OGWW debugging - VERIFY failed",
MB_OK | MB_ICONERROR | MB_SYSTEMMODAL);
}
else
{
WString strMessage;
strMessage.SetA(szBuffer);
strMessage.Replace((WCHAR)'\n', (WCHAR)' ');
strMessage.Replace((WCHAR)'~', (WCHAR)'\n');
for (size_t nPos = 0; nPos < nLen; nPos++)
if (szBuffer[nPos] == '~') szBuffer[nPos] = ' ';
OgwwAutoDisappearDlg* pMsgDlg = new OgwwAutoDisappearDlg(pWnd->HInst(),
pWnd->HPrevInst());
pMsgDlg->Show(strMessage.Value(), L"OGWW debugging - VERIFY failed",
SET_SIZE(460, 120), MB_ICONHAND);
pMsgDlg->Run();
}
delete szBuffer;
return fprintf(stderr, VERIFY_FORMAT, szExpression,
fLibVersion, szFileName, nFileLine);
}
int __cdecl OgwwMainFrame_ASSERT_MSGBOX(const char* szExpression, float fLibVersion,
const char* szFileName, int nFileLine)
{
size_t nLen = strlen(ASSERT_FORMAT)
+ (szExpression != NULL ? strlen(szExpression) : 0) + 5
+ (szFileName != NULL ? strlen(szFileName ) : 0) + 6;
char* szBuffer = new char[nLen];
memset(szBuffer, 0, nLen);
sprintf(szBuffer, ASSERT_FORMAT, szExpression, fLibVersion, szFileName, nFileLine);
OgwwHandledObjectList* inst = OgwwMainFrame::InstanceList();
OgwwMainFrame* pWnd = (OgwwMainFrame*)(inst != NULL && inst->FirstItem() != NULL ?
inst->FirstItem()->HandledObj : NULL);
if (GetCapture() == NULL)
{
for (size_t nPos = 0; nPos < nLen; nPos++)
if (szBuffer[nPos] == '~') szBuffer[nPos] = ' ';
HWND hWnd = (pWnd != NULL ? pWnd->HWnd() : ::GetDesktopWindow());
::MessageBoxA(hWnd, szBuffer, "OGWW debugging - ASSERT failed",
MB_OK | MB_ICONERROR | MB_SYSTEMMODAL);
}
else
{
WString strMessage;
strMessage.SetA(szBuffer);
strMessage.Replace((WCHAR)'\n', (WCHAR)' ');
strMessage.Replace((WCHAR)'~', (WCHAR)'\n');
for (size_t nPos = 0; nPos < nLen; nPos++)
if (szBuffer[nPos] == '~') szBuffer[nPos] = ' ';
OgwwAutoDisappearDlg* pMsgDlg = new OgwwAutoDisappearDlg(pWnd->HInst(),
pWnd->HPrevInst());
pMsgDlg->Show(strMessage.Value(), L"OGWW debugging - ASSERT failed",
SET_SIZE(460, 120), MB_ICONHAND);
pMsgDlg->Run();
}
delete szBuffer;
int result = fprintf(stderr, ASSERT_FORMAT, szExpression, fLibVersion,
szFileName, nFileLine);
ExitProcess(1);
return result;
}
The implementation of the class OgwwAutoDisappearDlg
is located within the files AutoDisappearDlg.hpp and AutoDisappearDlg.cpp. I assumed that the OgwwAutoDisappearDlg
dialog can never call itself - means there can only be one instance of the dialog at a time. Thus, for simplicity, many member fields are declared static
(AutoDisappearDlg.hpp):
#ifndef __AUTODISAPPEARDLG_H__
#define __AUTODISAPPEARDLG_H__
class OgwwAutoDisappearDlg : public OgwwGenericWindow
{
private:
static bool _bMessageDlgClassRegistered;
static UINT _nTimerID;
static UINT _nIconID;
static HWND _hWndIcon;
static UINT _nMessageID;
static HWND _hWndMessage;
static UINT _nProgressBarID;
static HWND _hWndProgressBar;
static UINT _nInitialTimerEvents;
static UINT _nRemainingTimerEvents;
private:
HINSTANCE _hPrevInst;
LPCWSTR _wszWindowClassName;
LPCSTR _szWindowClassName;
static LRESULT CALLBACK DialogProcedure(HWND hWnd, UINT uiMsg, WPARAM wParam,
LPARAM lParam);
public:
OgwwAutoDisappearDlg(HINSTANCE hInst, HINSTANCE hPrevInst);
virtual ~OgwwAutoDisappearDlg();
int GetType();
HINSTANCE HPrevInst();
HWND Show(LPCWSTR windowClassName, LPCWSTR wszWindowTitle, SIZE aSize,
int nIcon = MB_ICONINFORMATION);
int Run();
};
#endif // __AUTODISAPPEARDLG_H__
These static
member fields are directly accessible by the DialogProcedure
, that must be static
to meet the prototype and calling conditions (AutoDisappearDlg.cpp):
#if defined(UNICODE) && !defined(_UNICODE)
#define _UNICODE
#elif defined(_UNICODE) && !defined(UNICODE)
#define UNICODE
#endif
#include <tchar.h>
#include <windows.h>
#include <commctrl.h>
#include <assert.h>
#include <string>
#include "../Ogww.hpp"
#include "../WString.hpp"
#include "../Console.hpp"
#include "../Root.hpp"
#include "../HandledObjectList.hpp"
#include "GenericWindow.hpp"
#include "AutoDisappearDlg.hpp"
#ifdef __cplusplus
extern "C"
{
#endif
LRESULT CALLBACK OgwwAutoDisappearDlg::DialogProcedure(HWND hWnd, UINT uiMsg, WPARAM wParam,
LPARAM lParam)
{
int nPos = 0;
switch(uiMsg)
{
case WM_CREATE:
RECT rcDlgFrame;
::GetWindowRect(hWnd, &rcDlgFrame);
OgwwAutoDisappearDlg::_hWndIcon =
::CreateWindowExW(0, WC_STATIC, (LPCWSTR)NULL, SS_CENTERIMAGE | SS_ICON |
SS_CENTER | SS_REALSIZEIMAGE | WS_VISIBLE | WS_CHILD,
8, 8, 32, 32, hWnd,
(HMENU)OgwwAutoDisappearDlg::_nIconID, NULL, NULL);
OgwwAutoDisappearDlg::_hWndMessage =
::CreateWindowExW(0, WC_STATIC, (LPCWSTR)NULL, WS_VISIBLE | WS_CHILD,
48, 8, rcDlgFrame.right - rcDlgFrame.left - 64,
rcDlgFrame.bottom - rcDlgFrame.top - 66, hWnd,
(HMENU)OgwwAutoDisappearDlg::_nMessageID, NULL, NULL);
OgwwAutoDisappearDlg::_hWndProgressBar =
::CreateWindowExW(0, PROGRESS_CLASS, (LPCWSTR)NULL, WS_VISIBLE | WS_CHILD,
(rcDlgFrame.right - rcDlgFrame.left) / 2 - 100,
rcDlgFrame.bottom - rcDlgFrame.top - 50, 200, 15, hWnd,
(HMENU)OgwwAutoDisappearDlg::_nProgressBarID, NULL, NULL);
::SendMessage(OgwwAutoDisappearDlg::_hWndProgressBar, PBM_SETRANGE, 0,
MAKELPARAM( 0, 100));
::SendMessage(OgwwAutoDisappearDlg::_hWndProgressBar, PBM_SETSTEP, 1, (LPARAM)0);
nPos = (OgwwAutoDisappearDlg::_nInitialTimerEvents != 0 ?
(int)(100.0 * OgwwAutoDisappearDlg::_nRemainingTimerEvents /
OgwwAutoDisappearDlg::_nInitialTimerEvents) :
0);
::SendMessage(OgwwAutoDisappearDlg::_hWndProgressBar, PBM_SETPOS, nPos,
(LPARAM)0);
break;
case WM_COMMAND:
DestroyWindow(hWnd);
break;
case WM_TIMER:
if (OgwwAutoDisappearDlg::_nRemainingTimerEvents > 0)
{
OgwwAutoDisappearDlg::_nRemainingTimerEvents--;
nPos = (OgwwAutoDisappearDlg::_nInitialTimerEvents != 0 ?
(int)(100.0 * OgwwAutoDisappearDlg::_nRemainingTimerEvents /
OgwwAutoDisappearDlg::_nInitialTimerEvents) :
0);
::SendMessage(OgwwAutoDisappearDlg::_hWndProgressBar, PBM_SETPOS, nPos,
(LPARAM)0);
}
else
{
::KillTimer(hWnd, _nTimerID);
::PostMessage(hWnd, WM_CLOSE, (WPARAM)0, (LPARAM)0);
}
return (LRESULT)0;
case WM_CLOSE:
::PostQuitMessage(0);
break;
}
return (DefWindowProcW(hWnd, uiMsg, wParam, lParam));
}
#ifdef __cplusplus
}
#endif
bool OgwwAutoDisappearDlg::_bMessageDlgClassRegistered = false;
UINT OgwwAutoDisappearDlg::_nTimerID = 1;
UINT OgwwAutoDisappearDlg::_nIconID = 2;
HWND OgwwAutoDisappearDlg::_hWndIcon = NULL;
UINT OgwwAutoDisappearDlg::_nMessageID = 3;
HWND OgwwAutoDisappearDlg::_hWndMessage = NULL;
UINT OgwwAutoDisappearDlg::_nProgressBarID = 4;
HWND OgwwAutoDisappearDlg::_hWndProgressBar = NULL;
UINT OgwwAutoDisappearDlg::_nInitialTimerEvents = 0;
UINT OgwwAutoDisappearDlg::_nRemainingTimerEvents = 0;
The creation of the controls, used to display icon, message and progress within the dialog, is done during WM_CREATE
. The auto disappearing feature is implemented by a timer, that decrements the remaining time during WM_TIMER
.
The class OgwwAutoDisappearDlg
is part of my Ogww class hierarchy:
Destructable
-> Object
-> HandledObject
-> OgwwGenericWindow
-> OgwwAutoDisappearDlg
- The
Destructable
class is designed to act as a common root of reference types (types, that provide a constructor/destructor). - The
Object
class is designed to act as a common root of the class hierarchy. It uses the binding confirmation of a constructor/destructor derived from its base class Destructable
and adds run time type information. - The
HandledObject
class is designed to act as a common root of the classes, that are associated to a handle. It uses the binding confirmation of a constructor/destructor and the run time type information derived from its base class Object
and it adds a handle associated to its data structure. - The
OgwwGenericWindow
class is designed to act as a common root of the classes, that are independent windows. It uses the binding confirmation of a constructor/destructor, the run time type information and the handle associated to it's data structure derived from it's base class HandledObject
and adds a handle to the application instance and a handle to it's parent window.
That's why my OgwwAutoDisappearDlg
class overrides some inherited methods and uses some inherited fields (e.g., _hHandle
and _hInst
):
OgwwAutoDisappearDlg::OgwwAutoDisappearDlg(HINSTANCE hInst, HINSTANCE hPrevInst)
{
if (hInst == NULL)
throw L"OgwwMessageDlg: Argument 'hInst' must not be NULL!";
_hHandle = NULL;
_hInst = hInst;
_hPrevInst = hPrevInst;
}
OgwwAutoDisappearDlg::~OgwwAutoDisappearDlg()
{
if (_bMessageDlgClassRegistered && _hInst != NULL)
{
::UnregisterClassA(_szWindowClassName, _hInst);
_bMessageDlgClassRegistered = false;
if (LOG_LEVEL >= LOG_VERBOSE)
{
WString className; className.SetA(_szWindowClassName);
Console::WriteText(Console::__VERBOSE,
L"De-registration of WNDCLASSEX '%s' succeeded.\n", className.Value());
}
}
::GlobalFree((HGLOBAL)_wszWindowClassName);
_wszWindowClassName = NULL;
::GlobalFree((HGLOBAL)_szWindowClassName);
_szWindowClassName = NULL;
OgwwAutoDisappearDlg::_hWndIcon = NULL;
OgwwAutoDisappearDlg::_hWndMessage = NULL;
OgwwAutoDisappearDlg::_hWndProgressBar = NULL;
OgwwAutoDisappearDlg::_nInitialTimerEvents = 0;
OgwwAutoDisappearDlg::_nRemainingTimerEvents = 0;
if (LOG_LEVEL >= LOG_INFORMATION)
{
Console::WriteText(Console::__INFORMATION,
L"OgwwMessageDlg: Destruction of window with 'hWnd' %d succeeded.\n",
(int)_hHandle);
}
}
int OgwwAutoDisappearDlg::GetType()
{
return Object::OgwwMessageDlgType;
}
HINSTANCE OgwwAutoDisappearDlg::HPrevInst()
{
return _hPrevInst;
}
And now the last two methods of my OgwwAutoDisappearDlg
class:
HWND OgwwAutoDisappearDlg::Show(LPCWSTR wszMessage,
LPCWSTR wszWindowTitle, SIZE aSize, int nIcon)
{
if (wszMessage == NULL || wcslen(wszMessage) == 0)
throw L"OgwwMessageDlg: Argument 'windowClassName' must not be NULL or empty!";
_wszWindowClassName = WString::Copy(L"AutoDisappearDlg");
_szWindowClassName = WString::CopyA(L"AutoDisappearDlg");
if (!_bMessageDlgClassRegistered)
{
WNDCLASSEX wcex = {0};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = _hInst;
wcex.hIcon = ::LoadIcon(NULL, IDI_APPLICATION);
wcex.hIconSm = ::LoadIcon(NULL, IDI_APPLICATION);
wcex.hCursor = ::LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)::GetSysColorBrush(COLOR_3DFACE);
wcex.lpszMenuName = NULL;
#ifdef UNICODE
wcex.lpszClassName = _wszWindowClassName;
#else
wcex.lpszClassName = _szWindowClassName;
#endif
wcex.lpfnWndProc = OgwwAutoDisappearDlg::DialogProcedure;
if (!::RegisterClassEx(&wcex))
{
Console::WriteText(Console::__ERROR,
L"OgwwMessageDlg::Show: Registration of WNDCLASSEX '%s' failed!\n",
_wszWindowClassName);
return 0;
}
else
{
if (LOG_LEVEL >= LOG_INFORMATION)
Console::WriteText(Console::__INFORMATION,
L"OgwwMessageDlg::Show: Registration of WNDCLASSEX '%s' succeeded.\n",
_wszWindowClassName);
}
_bMessageDlgClassRegistered = true;
}
HWND hWnd = ::CreateWindowEx(WS_EX_DLGMODALFRAME | WS_EX_TOPMOST,
#ifdef UNICODE
_wszWindowClassName,
WString::Copy (wszWindowTitle),
#else
_szWindowClassName,
WString::CopyA(wszWindowTitle),
#endif
WS_VISIBLE | WS_SYSMENU | WS_CAPTION,
(GetSystemMetrics(SM_CXSCREEN) - aSize.cx) / 2,
(GetSystemMetrics(SM_CYSCREEN) - aSize.cy) / 2,
aSize.cx, aSize.cy,
NULL,
NULL,
_hInst,
NULL);
if (_hHandle == NULL)
_hHandle = (HANDLE)hWnd;
else if (_hHandle != (HANDLE)hWnd)
Console::WriteText(Console::__ERROR,
L"OgwwMessageDlg::Show: Missmatching 'hWnd' %d!\n", (int)_hHandle);
if (_hHandle == NULL)
{
Console::WriteErrorMessageFromID(Console::__ERROR,
L"OgwwMessageDlg::Show: Window creation not successful! Error code is: %d\n",
::GetLastError());
return 0;
}
else
{
if (LOG_LEVEL >= LOG_INFORMATION)
{
Console::WriteText(Console::__INFORMATION,
L"OgwwMessageDlg::Show: Creation of window with 'hWnd' %d succeeded.\n",
(int)_hHandle);
}
}
HICON hIco = NULL;
if (nIcon == MB_ICONERROR)
hIco = ::LoadIcon(NULL, MAKEINTRESOURCE(32513));
else if (nIcon == MB_ICONWARNING)
hIco = ::LoadIcon(NULL, MAKEINTRESOURCE(32515));
else if (nIcon == MB_ICONQUESTION)
hIco = ::LoadIcon(NULL, MAKEINTRESOURCE(32514));
else
hIco = ::LoadIcon(NULL, MAKEINTRESOURCE(32516));
::SendMessageW(OgwwAutoDisappearDlg::_hWndIcon, STM_SETICON, (WPARAM)hIco, (LPARAM)0);
HFONT hFont = (HFONT)::GetStockObject(DEFAULT_GUI_FONT);
::SendMessage(OgwwAutoDisappearDlg::_hWndMessage, WM_SETFONT, (WPARAM)hFont,
(LPARAM)MAKELONG(TRUE, 0));
::SetWindowTextW(OgwwAutoDisappearDlg::_hWndMessage, wszMessage);
::ShowWindow((HWND)_hHandle, SW_SHOW);
return (HWND)_hHandle;
}
int OgwwAutoDisappearDlg::Run()
{
OgwwAutoDisappearDlg::_nInitialTimerEvents = 50;
OgwwAutoDisappearDlg::_nRemainingTimerEvents = 50;
::SetTimer((HWND)_hHandle, (UINT_PTR)_nTimerID, 100, NULL);
BOOL bQuit = FALSE;
MSG msg = {0};
while (!bQuit)
{
if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
bQuit = TRUE;
}
else
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
}
return msg.wParam;
}
That's it! I hope that the OgwwAutoDisappearDlg
dialog is a good inspiration to solve similar problems. To follow the progress of the Ogww library, I recommend a look at my Code Project article, A basic icon editor running on ReactOS.
History
- 8th January, 2020: Initial version