Click here to Skip to main content
15,886,422 members
Articles / Desktop Programming / Win32
Tip/Trick

Automatically Disappearing Dialog, Used to Provide VERIFY and ASSERT on ReactOS

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
8 Jan 2020CPOL4 min read 3.1K   28   1  
Automatically Disappearing Dialog
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.

Image 1

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):

C++
#ifndef __OGWW_H__
#define __OGWW_H__

#define LIB_VERSION     0.20

/// <summary>Displays the fail in a message box and writes the fail to standard error
/// but the program will not break.</summary>
/// <param name="szExpression">The expression, the assertion failed for.</param>
/// <param name="fLibVersion">The library version.</param>
/// <param name="szFileName">The source code file name.</param>
/// <param name="nFileLine">The source code file line.</param>
int __cdecl OgwwMainFrame_VERIFY_MSGBOX(const char* szExpression, float fLibVersion,
                                        const char* szFileName, int nFileLine);

/// <summary>Displays the fail in a message box and writes the fail to standard error
/// but the program will not break.</summary> 
/// <param name="szExpression">The expression, the verification failed for.</param>
/// <param name="fLibVersion">The library version.</param>
/// <param name="szFileName">The source code file name.</param>
/// <param name="nFileLine">The source code file line.</param>
int __cdecl OgwwMainFrame_ASSERT_MSGBOX(const char* szExpression, float fLibVersion,
                                        const char* szFileName, int nFileLine);

/// <summary>Verifies a condition within code during debug and release builds. If the
/// verification fails then the fail is written to standard error but the program will
/// not break at the line the assertion failed.</summary>
#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

/// <summary>Verifies a condition within code during debug and release builds. If the
/// verification fails then a message box displays the fail and the fail is written to
/// standard error but the program will not break at the line the assertion failed.</summary>
/// <remarks>Use the <c>verify</c> macro instead of the <c>verify</c> macro within message
/// loop processing to avoid a message box blocking.</remarks>
#define VERIFY(expression) ((expression) ? (void)0 : 
 (void)OgwwMainFrame_VERIFY_MSGBOX(#expression, LIB_VERSION, __FILE__, __LINE__))

/// <summary>Asserts a condition within code during debug builds (when _DEBUG is defined).
/// If the assertion fails then a message box displays the fail, the fail is written to
/// standard error and the program will break at the line the assertion failed.</summary>
/// <remarks>Use the <c>assert</c> macro instead of the <c>ASSERT</c> macro within message
/// loop processing to avoid a message box blocking.</remarks>
#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):

C++
// ###########################################################################################
// ###        START       Debugging helper - message dialog based.                        ####
// ###########################################################################################

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";

/// <summary>Displays the fail in a message box and writes the fail to standard error but
/// the program will not break.</summary>
/// <param name="szExpression">The expression, the assertion failed for.</param>
/// <param name="fLibVersion">The library version.</param>
/// <param name="szFileName">The source code file name.</param>
/// <param name="nFileLine">The source code file line.</param>
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) + /* version */ 5
                    + (szFileName   != NULL ? strlen(szFileName  ) : 0) + /* line */    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] = ' ';

        // Unfortunately none of this approaches work:
        // -------------------------------------------
        // ::SetCapture(NULL); -> Doesn't help to release the capture.
        // ::ReleaseCapture();
        // ::EnableWindow(hWndCapture, FALSE);

        // Unfortunately both tips don't work on ReactOS:
        // ----------------------------------------------
        // Siarhei Boika, "How to bring window to top with SetForegroundWindow()"

        // Use an automatically disappearing dialog instead.
        // ------------------------------------------- 
        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);
}

/// <summary>Displays the fail in a message box, writes the fail to standard error and
/// breaks the program.</summary>
/// <param name="szExpression">The expression, the verification failed for.</param>
/// <param name="fLibVersion">The library version.</param>
/// <param name="szFileName">The source code file name.</param>
/// <param name="nFileLine">The source code file line.</param>
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) + /* version */ 5
                    + (szFileName   != NULL ? strlen(szFileName  ) : 0) + /* line */    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] = ' ';

        // Unfortunately none of this approaches work:
        // -------------------------------------------
        // ::SetCapture(NULL); -> Doesn't help to release the capture.
        // ::ReleaseCapture();
        // ::EnableWindow(hWndCapture, FALSE);

        // Unfortunately both tips don't work on ReactOS:
        // ----------------------------------------------
        // Siarhei Boika, "How to bring window to top with SetForegroundWindow()"

        // Use an automatically disappearing dialog instead.
        // ------------------------------------------- 
        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);
    // This is NO alternative to the accurate exit - sending WM_QUIT.
    // This does not clean up resources and might cause memory leaks.
    ExitProcess(1);
    return result;
}

// ###########################################################################################
// ###        END         Debugging helper - message dialog based.                        ####
// ###########################################################################################

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):

C++
#ifndef __AUTODISAPPEARDLG_H__
#define __AUTODISAPPEARDLG_H__

/// <summary>
/// The <see cref="OgwwMessageDlg"> class is designed to act as a message prompting window.
/// </summary>
class OgwwAutoDisappearDlg : public OgwwGenericWindow
{
private:
    /// <summary>    /// Determine whether the windows class for <see cref="OgwwMessageDlg">
    /// has already been registered. </summary>
    static bool                      _bMessageDlgClassRegistered;

    /// <summary>Define the control ID of the single timer associated to this
    /// <see cref="OgwwMessageDlg">. </summary>
    static UINT                      _nTimerID;

    /// <summary>Define the control ID of the static for the icon, included in this
    /// <see cref="OgwwMessageDlg">. </summary>
    static UINT                      _nIconID;

    /// <summary>Define the window handle of the static for the icon, included in this
    /// <see cref="OgwwMessageDlg">. </summary>
    static HWND                      _hWndIcon;

    /// <summary>Define the control ID of the static for the message, included in this
    /// <see cref="OgwwMessageDlg">. </summary>
    static UINT                      _nMessageID;

    /// <summary>Define the window handle of the static for the message, included in this
    /// <see cref="OgwwMessageDlg">. </summary>
    static HWND                      _hWndMessage;

    /// <summary>Define the control ID of the progress bar, included in this
    /// <see cref="OgwwMessageDlg">. </summary>
    static UINT                      _nProgressBarID;

    /// <summary>Define the window handle of the progress bar, included in this
    /// <see cref="OgwwMessageDlg">. </summary>
    static HWND                      _hWndProgressBar;

    /// <summary>Define the initial number of timer events until timer stops and dialog
    /// disappears. </summary>
    static UINT                      _nInitialTimerEvents;

    /// <summary>Define the remaining number of timer events until timer stops and dialog
    /// disappears. </summary>
    static UINT                      _nRemainingTimerEvents;

private:
    HINSTANCE                        _hPrevInst;
    LPCWSTR                          _wszWindowClassName;
    LPCSTR                           _szWindowClassName;

    /// <summary>The windows event loop procedure, processing the actual windows message.
    /// </summary>
    /// <param name="hWnd">The handle of the window, the windows event loop procedure is
    /// called for.</param>
    /// <param name="uiMsg">The message, the <c>WindowProcedure</c> shall process.</param>
    /// <param name="wParam">The <c>WPARAM</c> parameter of the message, the
    /// <c>WindowProcedure</c> shall process.</param>
    /// <param name="lParam">The <c>LPARAM</c> parameter of the message, the
    /// <c>WindowProcedure</c> shall process.</param>
    /// <returns>Returns <c>0</c> on success, or non-zero otherwise.</returns>
    static LRESULT CALLBACK DialogProcedure(HWND hWnd, UINT uiMsg, WPARAM wParam,
                                            LPARAM lParam);

public:
    // ---------------------- Construction and destruction

    /// <summary>Initializes a new instance of the <see cref="OgwwMessageDlg"/> class with
    /// instance handle and previous instance handle. </summary>
    /// <param name="hInst">The application/module/executable handle. Must not be <c>NULL</c>.
    /// </param>
    /// <param name="hPrevInst">The previous application/module/executable handle. Typically
    /// <c>NULL</c>.</param>
    OgwwAutoDisappearDlg(HINSTANCE hInst, HINSTANCE hPrevInst);

    /// <summary>
    /// Cleans up this instance of the <see cref="OgwwWindow"> class.
    /// </summary>
    /// <remark>Must be virtual to enable a successful down-cast for <c>delete</c> operator.
    /// </remark>
    virtual ~OgwwAutoDisappearDlg();

    // ---------------------- Overridden virtual methods

    /// <summary>
    /// Gets the runtime type of this class.
    /// </summary>
    /// <returns>The runtime type of this class instance.</returns>
    int GetType();

    /// <summary>
    /// Gets the registered previous application/module/executable handle.
    /// </summary>
    /// <returns>The registered previous application/module/executable handle. Typically
    /// <c>NULL</c>.</returns>
    HINSTANCE HPrevInst();

    // ---------------------- Message loop methods

    /// <summary>Creates and registers a new window class, creates a new dialog from this
    ///  class and shows the new dialog.</summary>
    /// <param name="wszMessage">The message to display.</param>
    /// <param name="wszWindowTitle">The title of the window to create and show.</param>
    /// <param name="aSize">The size of this dialog.</param>
    /// <param name="nIcon">The icon to display. Aupported are: MB_ICONASTERIC, MB_ICONERROR,
    /// MB_ICONEXCLAMATION, MB_ICONHAND, MB_ICONINFORMATION, MB_ICONQUESTION, MB_ICONWARNING
    /// </param>
    /// <returns>The handle of the window, that has to be created and shown.</returns>
    /// <remarks>This is one of the rare functions, that use ASCII string instead if UNICODE
    /// string internally.</remarks>
    HWND Show(LPCWSTR windowClassName, LPCWSTR wszWindowTitle, SIZE aSize,
              int nIcon = MB_ICONINFORMATION);

    /// <summary>Executes the windows event loop.</summary>
    /// <returns>The <c>wParam</c> member of the last processed window message, that is
    /// equal to the exit code of <c>PostQuitMessage</c>().</returns>
    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):

C++
#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>

// -- C++ std namespace -------------
#include <string>
// -- C++ std namespace -------------

#include "../Ogww.hpp"
#include "../WString.hpp"
#include "../Console.hpp"

#include "../Root.hpp"
#include "../HandledObjectList.hpp"
#include "GenericWindow.hpp"
#include "AutoDisappearDlg.hpp"

// ###########################################################################################
// ###        START       Static member                                                   ####
// ###########################################################################################

#ifdef __cplusplus
extern "C"
{
#endif

/// <summary>The windows event loop procedure, processing the actual windows message.
/// </summary>
/// <param name="hWnd">The handle of the window, the windows event loop procedure is called
/// for.</param>
/// <param name="uiMsg">The message, the <c>WindowProcedure</c> shall process.</param>
/// <param name="wParam">The <c>WPARAM</c> parameter of the message, the
/// <c>WindowProcedure</c> shall process.</param>
/// <param name="lParam">The <c>LPARAM</c> parameter of the message, the
/// <c>WindowProcedure</c> shall process.</param>
/// <returns>Returns <c>0</c> on success, or non-zero otherwise.</returns>
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(/* MIN range value*/ 0, /* MAX range value*/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: //  273
            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: // 16
            // By default, the DefWindowProc function calls the DestroyWindow function
            // to destroy the window.
            //::DestroyWindow(hWnd);
            ::PostQuitMessage(0);
            break;
    }

    return (DefWindowProcW(hWnd, uiMsg, wParam, lParam));
}

#ifdef __cplusplus
}
#endif

/// <summary>Determine whether the windows class for "OgwwMessageDlg" has already been
/// registered. </summary>
bool OgwwAutoDisappearDlg::_bMessageDlgClassRegistered = false;

/// <summary>Define the control ID of the single timer associated to this
/// <see cref="OgwwMessageDlg">. </summary>
UINT OgwwAutoDisappearDlg::_nTimerID = 1;

/// <summary>Define the control ID of the static for the icon, included in this
/// <see cref="OgwwMessageDlg">. </summary>
UINT OgwwAutoDisappearDlg::_nIconID = 2;

/// <summary>Define the window handle of the static for the icon, included in this
/// <see cref="OgwwMessageDlg">. </summary>
HWND OgwwAutoDisappearDlg::_hWndIcon = NULL;

/// <summary>Define the control ID of the static for the message, included in this
/// <see cref="OgwwMessageDlg">. </summary>
UINT OgwwAutoDisappearDlg::_nMessageID = 3;

/// <summary>Define the window handle of the static for the message, included in this
/// <see cref="OgwwMessageDlg">. </summary>
HWND OgwwAutoDisappearDlg::_hWndMessage = NULL;

/// <summary>Define the control ID of the progress bar, included in this
/// <see cref="OgwwMessageDlg">. </summary>
UINT OgwwAutoDisappearDlg::_nProgressBarID = 4;

/// <summary>Define the window handle of the progress bar, included in this
/// <see cref="OgwwMessageDlg">. </summary>
HWND OgwwAutoDisappearDlg::_hWndProgressBar = NULL;

/// <summary>Define the initial number of timer events until timer stops and dialog
/// disappears. </summary>
UINT OgwwAutoDisappearDlg::_nInitialTimerEvents = 0;

/// <summary>Define the remaining number of timer events until timer stops and dialog
/// disappears. </summary>
UINT OgwwAutoDisappearDlg::_nRemainingTimerEvents = 0;

// ###########################################################################################
// ###        END         Static member                                                   ####
// ###########################################################################################

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):

C++
// ###########################################################################################
// ###        START       Construction and destruction                                    ####
// ###########################################################################################

/// <summary>Initializes a new instance of the <see cref="OgwwMessageDlg"/> class with
/// instance handle and previous instance handle. </summary>
/// <param name="hInst">The application/module/executable handle. Must not be <c>NULL</c>.
/// </param>
/// <param name="hPrevInst">The previous application/module/executable handle. Typically
/// <c>NULL</c>.</param>
OgwwAutoDisappearDlg::OgwwAutoDisappearDlg(HINSTANCE hInst, HINSTANCE hPrevInst)
{
    if (hInst == NULL)
        throw L"OgwwMessageDlg: Argument 'hInst' must not be NULL!";

    _hHandle         = NULL;
    _hInst           = hInst;
    _hPrevInst       = hPrevInst;
}

/// <summary>
/// Cleans up this instance of the <see cref="OgwwMessageDlg"/> class.
/// </summary>
OgwwAutoDisappearDlg::~OgwwAutoDisappearDlg()
{
    if (_bMessageDlgClassRegistered && _hInst != NULL)
    {
        // All window classes that an application registers are unregistered when it
        // terminates. But no window classes registered by a DLL are unregistered when
        // the DLL is unloaded. To prevent class usage after DLL is unloaded we
        // explicitly unregister the class here.
        ::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);
    }
}

// ###########################################################################################
// ###        END         Construction and destruction                                    ####
// ###########################################################################################

// ###########################################################################################
// ###        START       Overridden virtual methods                                      ####
// ###########################################################################################

/// <summary>
/// Gets the runtime type of this class instance.
/// </summary>
/// <returns>The runtime type of this class instance.</returns>
int OgwwAutoDisappearDlg::GetType()
{
    return Object::OgwwMessageDlgType;
}

/// <summary>Gets the registered previous application/module/executable handle.</summary>
/// <returns>The registered previous application/module/executable handle. Typically
/// <c>NULL</c>.</returns>
HINSTANCE OgwwAutoDisappearDlg::HPrevInst()
{
    return _hPrevInst;
}

// ###########################################################################################
// ###        END         Overridden virtual methods                                      ####
// ###########################################################################################

And now the last two methods of my OgwwAutoDisappearDlg class:

C++
// ###########################################################################################
// ###        START       Methods                                                         ####
// ###########################################################################################

/// <summary>Creates and registers a new window class, creates a new dialog from this class
/// and shows the new dialog. </summary>
/// <param name="wszMessage">The message to display.</param>
/// <param name="wszWindowTitle">The title of the window to create and show.</param>
/// <param name="aSize">The size of this dialog.</param>
/// <param name="nIcon">The icon to display. Aupported are: MB_ICONASTERIC, MB_ICONERROR,
/// MB_ICONEXCLAMATION, MB_ICONHAND, MB_ICONINFORMATION, MB_ICONQUESTION, MB_ICONWARNING
/// </param>
/// <returns>The handle of the window, that has to be created and shown.</returns>
/// <remarks>This is one of the rare functions, that use ASCII string instead if UNICODE
/// string internally.</remarks>
HWND OgwwAutoDisappearDlg::Show(LPCWSTR /* weak */ wszMessage,
                                LPCWSTR /* weak */ 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");

    /* =================================== RESTRICTION ================================== */
    /* While the wide character versions "WNDCLASSW" and "RegisterClassW" work correctly, */
    /* the "CreateWindowW" doesn't process the windowName parameter correctly.            */
    /* =================================== RESTRICTION ================================== */

    if (!_bMessageDlgClassRegistered)
    {
        /* register window class */
        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;
    }

    /* create main window */
    HWND hWnd = ::CreateWindowEx(WS_EX_DLGMODALFRAME | WS_EX_TOPMOST, // no extended styles
#ifdef UNICODE
                                 _wszWindowClassName,                 // name of window class
                                 WString::Copy (wszWindowTitle),      // title of window
#else
                                 _szWindowClassName,                  // name of window class
                                 WString::CopyA(wszWindowTitle),      // title of window
#endif
                                 WS_VISIBLE | WS_SYSMENU | WS_CAPTION,
                                 (GetSystemMetrics(SM_CXSCREEN) - aSize.cx) / 2,
                                 (GetSystemMetrics(SM_CYSCREEN) - aSize.cy) / 2,
                                 aSize.cx, aSize.cy,                  // the size
                                 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);
        }
    }

    // Shared icon - no destruction required!
    HICON hIco = NULL;
    if (nIcon == MB_ICONERROR) // MB_ICONHAND
        hIco        = ::LoadIcon(NULL, MAKEINTRESOURCE(32513));
    else if (nIcon == MB_ICONWARNING) // MB_ICONEXCLAMATION
        hIco        = ::LoadIcon(NULL, MAKEINTRESOURCE(32515));
    else if (nIcon == MB_ICONQUESTION)
        hIco        = ::LoadIcon(NULL, MAKEINTRESOURCE(32514));
    else // MB_ICONINFORMATION // MB_ICONASTERIC
        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;
}

/// <summary>Executes the windows event loop.</summary>
/// <returns>The <c>wParam</c> member of the last processed window message, that is equal
/// to the exit code of <c>PostQuitMessage</c>().</returns>
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)
    {
        /* check for messages */
        if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            /* handle or dispatch messages */
            if (msg.message == WM_QUIT)
            {
                bQuit = TRUE;
            }
            else
            {
                ::TranslateMessage(&msg);
                ::DispatchMessage(&msg);
            }
        }
    }

    return msg.wParam;
}

// ###########################################################################################
// ### END               Methods                                                          ####
// ###########################################################################################

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader Celonis SA
Germany Germany
I am currently the CEO of Symbioworld GmbH and as such responsible for personnel management, information security, data protection and certifications. Furthermore, as a senior programmer, I am responsible for the automatic layout engine, the simulation (Activity Based Costing), the automatic creation of Word/RTF reports and the data transformation in complex migration projects.

The main focus of my work as a programmer is the development of Microsoft Azure Services using C# and Visual Studio.

Privately, I am interested in C++ and Linux in addition to C#. I like the approach of open source software and like to support OSS with own contributions.

Comments and Discussions

 
-- There are no messages in this forum --