Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Win32++ Programmatically Created Menus with Code::Blocks on ReactOS

5.00/5 (4 votes)
24 Jan 2020CPOL2 min read 8.3K  
The Win32++ class library sample collection does not contain a sample, that creates the frame menu via API instead of via resources. Here is the missing part.

Introduction

The Win32++ class library is a great piece of work. I am currently trying to replace my own class library with the Win32++ class library for my project A basic icon editor running on ReactOS. In this context, I am testing the possibilities of Win32++ to provide appealing menus, that are dynamically created based on the API (not resource-generated).

But also for other use cases, it might be interesting to create appealing menus with Win32++ dynamically based on the API.

Background

I have the following requirements for the menu:

  • work on ReactOS
  • support transparent bitmaps or icons
  • can be created dynamically based on the API

And this is how the result finally looks like on ReactOS:

Using the Code

To separate everything properly, I have added the method OnCreateFrameMenu to my CFrame derived class CMainFrame.

C++
// OnCreate controls the way the frame is created.
int CMainFrame::OnCreate(CREATESTRUCT& cs)
{
    // OnCreate controls the way the frame is created.
    // Overriding CFrame::OnCreate is optional.

    // A menu is added if the IDW_MAIN menu resource is defined.
    // Frames have all options enabled by default.
    // Use the following functions to disable options.

    // UseIndicatorStatus(FALSE);    // Don't show keyboard indicators in the StatusBar
    // UseMenuStatus(FALSE);         // Don't show menu descriptions in the StatusBar
    // UseReBar(FALSE);              // Don't use a ReBar
    // UseStatusBar(FALSE);          // Don't use a StatusBar
    UseThemes(FALSE);                // Don't use themes
    UseToolBar(FALSE);               // Don't use a ToolBar

    // Create the complete frame-menu before layout calculation (done in CFrame::OnCreate()).
    OnCreateFrameMenu();

    // call the base class function
    return CFrame::OnCreate(cs);
}

// OnCreateFrameMenu controls the main (frame) menu creation.
void CMainFrame::OnCreateFrameMenu()
{
    m_frameMenu.CreateMenu();
    m_filePopupMenu.CreateMenu();
    if (m_frameMenu.AppendMenu(MF_POPUP, (UINT_PTR)m_filePopupMenu.GetHandle(),
        _T("&File")) != FALSE)
    {
        HICON hIcon = NULL;

        m_filePopupMenu.AppendMenu(MF_STRING, IDM_FILE_NEW, _T("&New"));
        hIcon = (HICON)::LoadImage(NULL, _T("Images\\New2_16.ico"),
                                   IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
        AddMenuIcon(IDM_FILE_NEW, hIcon, 16);

        m_filePopupMenu.AppendMenu(MF_STRING, IDM_FILE_OPEN, _T("&Open"));
        hIcon = (HICON)::LoadImage(NULL, _T("Images\\Open2_16.ico"),
                                   IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
        AddMenuIcon(IDM_FILE_OPEN, hIcon, 16);

        m_filePopupMenu.AppendMenu(MF_SEPARATOR, 0, (LPCTSTR)NULL);
        m_filePopupMenu.AppendMenu(MF_STRING, IDM_FILE_SAVE, _T("&Save"));
        hIcon = (HICON)::LoadImage(NULL, _T("Images\\Save_16.ico"),
                                   IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
        AddMenuIcon(IDM_FILE_SAVE, hIcon, 16);

        m_filePopupMenu.AppendMenu(MF_STRING, IDM_FILE_SAVEAS, _T("Save &As"));
        hIcon = (HICON)::LoadImage(NULL, _T("Images\\SaveAs_16.ico"),
                                   IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
        AddMenuIcon(IDM_FILE_SAVEAS, hIcon, 16);

        m_filePopupMenu.AppendMenu(MF_SEPARATOR, 0, (LPCTSTR)NULL);
        m_filePopupMenu.AppendMenu(MF_STRING, IDM_FILE_PRINT, _T("&Print"));
        hIcon = (HICON)::LoadImage(NULL, _T("Images\\Win32Print.ico"),
                                   IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
        AddMenuIcon(IDM_FILE_PRINT, hIcon, 16);

        m_filePopupMenu.AppendMenu(MF_SEPARATOR, 0, (LPCTSTR)NULL);
        m_filePopupMenu.AppendMenu(MF_STRING, IDM_FILE_CLOSE, _T("&Close"));
        hIcon = (HICON)::LoadImage(NULL, _T("Images\\Close_16.ico"),
                                   IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
        AddMenuIcon(IDM_FILE_CLOSE, hIcon, 16);

        m_filePopupMenu.AppendMenu(MF_SEPARATOR, 0, (LPCTSTR)NULL);
        m_filePopupMenu.AppendMenu(MF_STRING, IDM_FILE_EXIT, _T("&Exit"));
        hIcon = (HICON)::LoadImage(NULL, _T("Images\\Exit_16.ico"),
                                   IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
        AddMenuIcon(IDM_FILE_EXIT, hIcon, 16);
    }
    m_editPopupMenu.CreateMenu();
    if (m_frameMenu.AppendMenu(MF_POPUP, (UINT_PTR)m_editPopupMenu.GetHandle(),
        _T("&Edit")) != FALSE)
    {
        HICON hIcon = NULL;

        m_editPopupMenu.AppendMenu(MF_STRING, IDM_EDIT_UNDO, _T("&Undo"));
        hIcon = (HICON)::LoadImage(NULL, _T("Images\\Undo_16.ico"),
                                   IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
        AddMenuIcon(IDM_EDIT_UNDO, hIcon, 16);

        m_editPopupMenu.AppendMenu(MF_STRING, IDM_EDIT_REDO, _T("&Redo"));
        hIcon = (HICON)::LoadImage(NULL, _T("Images\\Redo_16.ico"),
                                   IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
        AddMenuIcon(IDM_EDIT_REDO, hIcon, 16);

        m_editPopupMenu.AppendMenu(MF_SEPARATOR, 0, (LPCTSTR)NULL);
        m_editPopupMenu.AppendMenu(MF_STRING, IDM_EDIT_CUT, _T("&Cut"));
        hIcon = (HICON)::LoadImage(NULL, _T("Images\\Win32Cut.ico"),
                                   IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
        AddMenuIcon(IDM_EDIT_CUT, hIcon, 16);

        m_editPopupMenu.AppendMenu(MF_STRING, IDM_EDIT_COPY, _T("Cop&y"));
        hIcon = (HICON)::LoadImage(NULL, _T("Images\\Win32Copy.ico"),
                                   IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
        AddMenuIcon(IDM_EDIT_COPY, hIcon, 16);

        m_editPopupMenu.AppendMenu(MF_STRING, IDM_EDIT_PASTE, _T("&Paste"));
        hIcon = (HICON)::LoadImage(NULL, _T("Images\\Win32Paste.ico"),
                                   IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
        AddMenuIcon(IDM_EDIT_PASTE, hIcon, 16);

        m_editPopupMenu.AppendMenu(MF_SEPARATOR, 0, (LPCTSTR)NULL);
        m_editPopupMenu.AppendMenu(MF_STRING, IDM_EDIT_DELETE, _T("&Delete"));
        hIcon = (HICON)::LoadImage(NULL, _T("Images\\Delete_16.ico"),
                                   IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
        AddMenuIcon(IDM_EDIT_DELETE, hIcon, 16);
    }
    m_helpPopupMenu.CreateMenu();
    if (m_frameMenu.AppendMenu(MF_POPUP, (UINT_PTR)m_helpPopupMenu.GetHandle(),
        _T("&?")) != FALSE)
    {
        HICON hIcon = NULL;

        m_helpPopupMenu.AppendMenu(MF_STRING, IDM_HELP_ABOUT, _T("&About"));
        m_helpPopupMenu.AppendMenu(MF_STRING, IDM_HELP_HELP, _T("&Help"));
        hIcon = (HICON)::LoadImage(NULL, _T("Images\\Help_16.ico"),
                                   IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
        AddMenuIcon(IDM_HELP_HELP, hIcon, 16);
    }
    SetFrameMenu(m_frameMenu.GetHandle());
}

Besides the methods CreateMenu, AppendMenu and SetFrameMenu, I mainly use the two methods, ::LoadImage and AddMenuIcon to create the appealing menus, firstly because I already have the required icons and secondly because the icons support transparency. (And thirdly, because the SetMenuItemBitmaps method seems to be just a thin wrapper around the Windows API and seems not to set the bitmaps in a way that Win32++ menu items can handle them.)

As you can easily see, the menu items are painted OWNERDRAW in the Aero theme and contain a fluent rectangle, even though the ReactOS system does not have an Aero theme installed and the current visual style is configured to be "Classic Theme". But I think we can accept that.

Drawbacks

However, there is a sporadic problem with ReactOS: OWNERDRAW menu items do not always have the correct font pre-selected. And this is what it looked like on my system after hovering around:

To avoid this, I use the same technique that I have described already in the tip, Yet another fully functional ownerdraw menu: I select the menu font explicitly. To achieve this, I have to modify the Win32++ file wxx_frame.h a bit, which is no problem, because Win32++ is open source.

C++
//////////////////////////////////
// CFrameT is the base class for all frame in Win32++
// The template parameter T is typically either CWnd or CDocker
template <class T>
class CFrameT : public T
{
...

#ifdef OGWW
    HFONT m_menuFontNormal;             // The standard/default/normal menu font buffer.
#endif // OGWW
};     // class CFrameT

...

///////////////////////////////////
// Definitions for the CFrame class
//
template <class T>
inline CFrameT<T>::CFrameT() : m_aboutDialog(IDW_ABOUT), m_accel(0), m_pView(NULL),
                               m_maxMRU(0), m_oldFocus(0), m_drawArrowBkgrnd(FALSE),
                               m_kbdHook(0), m_useIndicatorStatus(TRUE), m_useMenuStatus(TRUE),
                               m_useStatusBar(TRUE), m_useThemes(TRUE), m_useToolBar(TRUE)
{

...

#ifdef OGWW
    m_menuFontNormal = NULL;
#endif // OGWW
}

template <class T>
inline CFrameT<T>::~CFrameT()
{
    if (m_kbdHook != 0) UnhookWindowsHookEx(m_kbdHook);
#ifdef OGWW
    if (m_menuFontNormal != NULL)
        ::DeleteObject(m_menuFontNormal);
#endif // OGWW
}

...

// Called by DrawMenuItem to render the text for popup menus.
template <class T>
inline void CFrameT<T>::DrawMenuItemText(LPDRAWITEMSTRUCT pDIS)
{
...

#ifdef OGWW
        // Obviously, at least ReactOS does not guarantee the selection of the proper menu font.
        if (m_menuFontNormal == NULL)
        {
            NONCLIENTMETRICSW nm;
            nm.cbSize = sizeof(NONCLIENTMETRICS);
            assert(::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, nm.cbSize,&nm, 0) != FALSE);
            m_menuFontNormal = ::CreateFontIndirect(&(nm.lfMenuFont));
        }
        assert(m_menuFontNormal != NULL);

        HFONT hOldFont = NULL;
        if (m_menuFontNormal != NULL)
            hOldFont = (HFONT)::SelectObject(pDIS->hDC, m_menuFontNormal);
#endif // OGWW

        SetTextColor(pDIS->hDC, colorText);
        int mode = SetBkMode(pDIS->hDC, TRANSPARENT);
        DrawText(pDIS->hDC, pItem, tab, textRect, DT_SINGLELINE | DT_LEFT | DT_VCENTER);

        // Draw text after tab, right aligned
        if (tab != -1)
            DrawText(pDIS->hDC, &pItem[tab + 1], -1, textRect,
                     DT_SINGLELINE | DT_RIGHT | DT_VCENTER);

        SetBkMode(pDIS->hDC, mode);
#ifdef OGWW
/*            if (hOldFont == NULL)
            ::SelectObject(pDIS->hDC, hOldFont);*/
#endif // OGWW
    }
}

I have included all my supplements into #ifdef OGWW ... #endif // OGWW statements.

A few last words about the development environment: I use Code::Blocks version 17.12 with the included MinGW on ReactOS 0.4.11 and the current Win32++ version 7.8. I have linked the following libraries into my project:

  • gdi32
  • user32
  • kernel32
  • comctl32
  • comdlg32
  • Ole32
  • Oleaut32
  • Ws2_32
  • Uuid

I have set these general #defines:

  • _UNICODE
  • UNICODE
  • __MSVCRT__ (yes, supported by MinGW on ReactOS)
  • OGWW (to mark specific source code sections)

History

  • 24th January, 2020: Initial version

License

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