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
.
int CMainFrame::OnCreate(CREATESTRUCT& cs)
{
UseThemes(FALSE); UseToolBar(FALSE);
OnCreateFrameMenu();
return CFrame::OnCreate(cs);
}
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.
template <class T>
class CFrameT : public T
{
...
#ifdef OGWW
HFONT m_menuFontNormal; #endif // OGWW
};
...
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
}
...
template <class T>
inline void CFrameT<T>::DrawMenuItemText(LPDRAWITEMSTRUCT pDIS)
{
...
#ifdef OGWW
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);
if (tab != -1)
DrawText(pDIS->hDC, &pItem[tab + 1], -1, textRect,
DT_SINGLELINE | DT_RIGHT | DT_VCENTER);
SetBkMode(pDIS->hDC, mode);
#ifdef OGWW
#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 #define
s:
_UNICODE
UNICODE
__MSVCRT__
(yes, supported by MinGW on ReactOS) OGWW
(to mark specific source code sections)
History
- 24th January, 2020: Initial version