Creating modern style dynamic menu in Windows - winapi

In my MFC dialog based application I have main menu created with Visual Studio resource editor, and in one place there is dynamic menu, created at run time. All parts of menu that are created with Visual studio have modern look, and my dynamically created sub-menu has old look, as shown in image below.
My code for creating dynamic sub-menu is something like this (not the real code, but real code is not all that important):
CMenu subMenu;
subMenu.CreateMenu();
for (...)
{
subMenu.AppendMenu(
MF_STRING | (isChecked ? MF_CHECKED : MF_UNCHECKED),
<some menu ID>,
<some menu text>);
}
Inserting this sub-menu to where it belongs is done like this (pretty much the actual code):
TCHAR szMenuString[256];
MENUITEMINFO mii;
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_STATE | MIIM_ID | MIIM_SUBMENU | MIIM_CHECKMARKS |
MIIM_DATA | MIIM_STRING | MIIM_BITMAP | MIIM_FTYPE;
mii.dwTypeData = szMenuString;
mii.cch = sizeof(szMenuString) / sizeof(szMenuString[0]);
GetMenu()->GetMenuItemInfo(ID_SUBMENU, &mii);
mii.fMask |= MIIM_SUBMENU;
mii.hSubMenu = subMenu.GetSafeHmenu();
GetMenu()->SetMenuItemInfo(ID_SUBMENU, &mii);
How can I create my sub-menu so that it appears the same in style as the rest of the main menu?
My code is written in MFC, but your answer does not have to be in MFC (and probably cannot be).

I discovered the solution, but don't really understand what was happening. The solution is to prevent calling DestroyMenu on subMenu's destructor at the end of the function. This is done by either calling subMenu.Detach(), or making subMenu a pointer to CMenu.
What I don't understand is why is DestroyMenu turning new style menu to old style. I would expect that the menu is either destroyed and not shown, or copied in SetMenuItemInfo and so its style preserved. Whoever provides an answer to this one gets my vote :)
Also, I would like to know if I'm producing a resource leak by calling Detach here, or is my dynamic sub-menu destroyed along with the main menu. Points await the one who provides an answer.

Related

Third-party plugin that changes host's menu in Win32

I am working on 3rd-party plugin for a desktop Windows app. It is basically a traditional DLL that is only ever accessed by the host application. The host provides menu items for the plugin, based on an API, but I would like to extend the API by modifying the text of my menu items dynamically based on which file is open. The host uses an MDI window and I think it is developed with MFC. However, I am attempting to change the menu items directly with the Win API. (This may be the problem right there, which is part of my question.)
The code works correctly the first time I change the menu item. But subsequent changes do not appear on the menu. The weird thing is that GetMenuString seems always to return the value I set it to. That means that on the API level it appears to work, but the menu items do not change (except for the first time).
Here is the code I use to change the menu item text. It's very basic.
MENUITEMINFOW menuInfo;
memset ( &menuInfo, 0, sizeof(menuInfo) );
menuInfo.cbSize = sizeof(menuInfo);
menuInfo.fMask = MIIM_STRING;
menuInfo.fType = MFT_STRING;
menuInfo.dwTypeData = (LPWSTR)newItemText;
SetMenuItemInfoW (hMenu, idToChange, false, &menuInfo);
Could MFC be interfering with this? Or perhaps an idiosyncrasy of the host app's menu handling? Or is there something else I need to do to get the menu to display correctly?
I got this to work by using DeleteMenu and InsertMenu instead of SetMenuItemInfo. The code is straigthforward, but here it is in case it helps someone else like me.
const UINT idToChange = GetMenuItemID ( hMenu, index );
DeleteMenu ( hMenu, index, MF_BYPOSITION );
InsertMenuW ( hMenu, index, MF_BYPOSITION | MF_STRING, idToChange, (LPWSTR)newItemText );

Getting the menu handle

I created menus and sub menus using the resource editor in visual studio. I now want to add items to one of the menus during run time. I was going to use the InsertMenuItem function but I don't know how to get access to the HMENU variable.
LoadMenu seems is what you need. Use it to load menu from resource editor, something like this:
HMENU yourMenu = LoadMenu( hInst, // variable where you stored your HINSTANCE
MAKEINTRESOURCE(IDM_MENU1) ); // replace IDM_MENU1 with the ID of your menu
Here are lots of useful examples, you may find very useful. Some of them address your issue, and some might be useful to you in the future. I would study the Example of Menu-Item Bitmaps section if I were you...
If you need a menu handle that is already assigned to a window then use GetMenu as member arx said. Something like this:
HMENU yourMenu = GetMenu(hWnd); // hWnd is the HWND of the window that owns your menu
Do not forget to destroy the menu when it is no longer needed ( usually upon window destruction ) with DestroyMenu.
This example might help you as well. This is very good introductory tutorial for Win32, I suggest you to read it ( just go to the home page and download both PDF and .zip file with code examples ).
As I have said before, your question is not entirely clear, so if you have further questions leave me a comment.
Hopefully this answer solved your problems. Best regards.

Detect if an HMENU is a popup or dropdown menu

Ist it possible, given a HMENU, to detect if it's a popup or a drop down menu?
I want to create a (deep, modified) copy of an existing menu, and depending on this property I need to use either CreatePopupMenu or CreateMenu, respectively.
As Raymond Chen says here (with my emphasis):
CreateMenu creates a horizontal menu bar, suitable for attaching to a
top-level window. This is the sort of menu that says "File, Edit", and
so on. CreatePopupMenu creates a vertical popup menu, suitable for
use as a submenu of another menu (either a horizontal menu bar or
another popup menu) or as the root of a context menu.
If you get the two confused, you can get strange menu behavior.
Windows on rare occasions detects that you confused the two and
converts as appropriate, but I wouldn't count on Windows successfully
reading your mind.
There is no way to take a menu and ask it whether it is horizontal or
vertical. You just have to know.
Not clear with hmenu:
1) If you talk about hmenu, corresponding to the existing window (class #32768), it is sufficient to verify (via GetGUIThreadInfo) GUITHREADINFO.flags: availability GUI_INMENUMODE without GUI_POPUPMENUMODE without GUI_SYSTEMMENUMODE means that this menu - drop down.
2) If you talk about hmenu, existing in memory, we must find the root parent of this hmenu (btw, for menu there may be more than one, in contrast to the root parent window).
Then call TrackPopupMenu for found root parent and on WM_ENTERIDLE get hwnd appropriate window (class #32768) and call GetClientRect: if Rect =0, then the root parent = menubar (which can be created via LoadMenu(Indirect) or via CreateMenu), which means that the original hmenu - drop down (which can be created not only via CreatePopupMenu, but also via CreateMenu).
As for link to R.Chen. In reality the system always remembers exactly how was created hmenu in memory. But this mechanism (like so much else concerning the menu) is undocumented and Raymond obviously did not consider it necessary to uncover it...

how to create toolbars in an MFC dialog-based application

I wonder how this guy has created this toolbar on the program:
this is a modeless dialog created by the guy, I think:
but my dialog is a modal one. I don't think it makes a lot of change!
and this the code written by him to use the toolbar supplied in the res folder:
MainFrm.h
protected: // control bar embedded members
CMFCMenuBar m_wndMenuBar;
CMFCToolBar m_wndToolBar;
CMFCStatusBar m_wndStatusBar;
CMFCToolBarImages m_UserImages;
MainFrm.cpp
the code added in function:CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) to initialize the toolbar supplied in the resource:
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(theApp.m_bHiColorIcons ? IDR_MAINFRAME_256 : IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
CString strToolBarName;
bNameValid = strToolBarName.LoadString(IDS_TOOLBAR_STANDARD);
ASSERT(bNameValid);
m_wndToolBar.SetWindowText(strToolBarName);
CString strCustomize;
bNameValid = strCustomize.LoadString(IDS_TOOLBAR_CUSTOMIZE);
ASSERT(bNameValid);
m_wndToolBar.EnableCustomizeButton(TRUE, ID_VIEW_CUSTOMIZE, strCustomize);
// Allow user-defined toolbars operations:
InitUserToolbars(NULL, uiFirstUserToolBarId, uiLastUserToolBarId);
if (!m_wndStatusBar.Create(this))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT));
// TODO: Delete these five lines if you don't want the toolbar and menubar to be dockable
m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY);
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockPane(&m_wndMenuBar);
DockPane(&m_wndToolBar);
// enable Visual Studio 2005 style docking window behavior
CDockingManager::SetDockingMode(DT_SMART);
// enable Visual Studio 2005 style docking window auto-hide behavior
EnableAutoHidePanes(CBRS_ALIGN_ANY);
// Enable toolbar and docking window menu replacement
EnablePaneMenu(TRUE, ID_VIEW_CUSTOMIZE, strCustomize, ID_VIEW_TOOLBAR);
// enable quick (Alt+drag) toolbar customization
CMFCToolBar::EnableQuickCustomization();
if (CMFCToolBar::GetUserImages() == NULL)
{
// load user-defined toolbar images
if (m_UserImages.Load(_T(".\\UserImages.bmp")))
{
CMFCToolBar::SetUserImages(&m_UserImages);
}
}
// enable menu personalization (most-recently used commands)
// TODO: define your own basic commands, ensuring that each pulldown menu has at least one basic command.
CList<UINT, UINT> lstBasicCommands;
lstBasicCommands.AddTail(ID_FILE_NEW);
lstBasicCommands.AddTail(ID_FILE_OPEN);
lstBasicCommands.AddTail(ID_FILE_SAVE);
lstBasicCommands.AddTail(ID_FILE_PRINT);
lstBasicCommands.AddTail(ID_APP_EXIT);
lstBasicCommands.AddTail(ID_EDIT_CUT);
lstBasicCommands.AddTail(ID_EDIT_PASTE);
lstBasicCommands.AddTail(ID_EDIT_UNDO);
lstBasicCommands.AddTail(ID_APP_ABOUT);
lstBasicCommands.AddTail(ID_VIEW_STATUS_BAR);
lstBasicCommands.AddTail(ID_VIEW_TOOLBAR);
CMFCToolBar::SetBasicCommands(lstBasicCommands);
**In fact something that I don't understand and has wasted my time two days and made me to post four questions in this site is that after making a new toolbar supply here:
and leading to the toolbar editor
1-how can I create a new toolbar button here on the toolbar that has been supplied?
2-how can I change the shape of this button to the standard 24bit depth images or to a desired icon?
at last I want to have an image like this for my toolbar in my res folder and surely I want the buttons to be seperately clickable in my program's window.
If there's a need to the source of the project, I have uploaded the guy's project here
The program with mainframe.cpp is not a modeless dialog, it is an MFC doc/view program that is designed to support a menu bar and toolbar.
A dialog-based program does not have the toolbar support designed in. The MFC sample program DLGCBR32 (in MSDN) shows how to put a toolbar on a dialog.
I think it's just a question of reading the proper documentation, create new toolbar button: see MSDN Creating a New Toolbar Button and Toolbar Editor.
Changing the image depth of the buttons in a toolbar: load the bmp file associated with the toolbar (in the Res folder) in a bitmap editor (like Paint), save as a bmp with a different image depth, load the sln in VS and rebuild the project.

Big problems with MFC/WinAPI

I need to create a SDI form with a formview that has two tabs, which encapsulate multiple dialogs as the tab content. But the form has to have a colored background.
And things like these makes me hate programming.
First, I tried CTabControl, via resource editor, tried different things, but the undocumented behavior and the quirks with no answers led me into a roadblock.
After many hours of searching, I found that there is a control called property sheet, which is actually what I need.
Some more searching later, I found that property sheet can even be actually embedded onto CFormView like so: http://www.codeguru.com/Cpp/controls/propertysheet/article.php/c591
And that the dialog classes derived from CPropertyPage can be directly added as pages via AddPage method of CPropertySheet.
Great! Not quite so... Some of the controls didn't worked, and were not created, ran into weird asserts. Turns out the DS_CONTROL style was missing from the dialogs. Found it completely accidentaly on Link, no word about that on MSDN!!!! Property page must have: DS_3DLOOK | DS_CONTROL | WS_CHILD | WS_TABSTOP, and can have: DS_SHELLFONT | DS_LOCALEDIT | WS_CLIPCHILDREN styles! Not any other, which are created by default with resource editor. Sweet, super hidden information for software developers!
The quote in comments on that page: "OMG. That's where that behavior came from...
It turns out that the PlaySound API relied on that behavior when playing sounds on 64bit machines." by Larry Osterman, who as I understand works for Microsoft for 20 years, got me laughing out loud.
Anyway, fixed that, the dialog-controls(CPropertyPages) are created as expected now, and that part looks something remotely promising, but the next part with color is dead end again!
Normally you override WM_CTLCOLOR, check for control ID or hwnd and supply the necessary brush to set the color you need. Not quite so with CPropertySheet, the whole top row stays gray! For CTabCtrl it somehow works, for CPropertySheet it doesn't.
Why? Seems that the CPropertySheet is kinda embedded inside CTabControl or something, because if I override WM_ERASEBKGND, only the internal part changes the color.
Now it seems that there is a GetTabControl() method in the CPropertySheet, that returns the actual CTabCtrl* of the CPropertySheet. But since it's constructed internally, I can't find how to override it's WM_CTLCOLOR message processing.
There seems to be a way to subclass the windowproc, but after multiple tries I can't find any good source on how to do it. SubclassWindow doc on MSDN says: "The window must not already be attached to an MFC object when this function is called."?! What's that?
I tried creating a custom CCustomTabCtrl class based on CTabCtrl via MFC wizard, created an instance of it, called SubclassWindow from one of the CCustomPropertySheet handlers to override the internal CTabCtrl, but nothing works, mystical crashes deep inside MFC.
Tried setting WindowLong with GCL_HBRBACKGROUND for the internal CTabCtrl, nothing changed.
And worst of all, I can't find any sort of useful documentation or tutorials on the topic.
Most I can find is how to ownerdraw the tab control, but this is seriously wrong on so many ways, I want a standard control behavior minus background color, I don't want to support different color schemes, windows versions, IAccesible interfaces and all this stuff, and none of the ownerdraw samples I've seen can get even 10% of all the standard control behavior right. I have no illusion that I will create something better, I wont with the resources at hand.
I stumbled upon this thread, and I can't agree with the author more: http://arstechnica.com/civis/viewtopic.php?f=20&t=169886&sid=aad002424e80121e514548d428cf09c6 owner draw controls are undocumented PITA, that are impossible to do right, and there is NULL information on MSDN to help.
So is there anything I have missed or haven't tried yet? How to change the top strip background color of the CPropertySheet? Anyone?
Your only option is to ownerdraw the tab control. It's not that hard. Well, it is frustrating because MFC doesn't tell you how to make the necessary Win32 calls.
In your CPropertySheet-derived class, overwrite OnInitDialog() and add:
GetTabControl()->ModifyStyle(0,TCS_OWNERDRAWFIXED);
This puts your CPropertySheet-derived class in charge of drawing the tab control. Add a handler for WM_DRAWITEM (OnDrawItem) and change backgroundColor and textColor to match whatever colors you wanted. Code for OnDrawItem follows:
void CPropSht::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
if (ODT_TAB != lpDrawItemStruct->CtlType)
{
CPropertySheet::OnDrawItem(nIDCtl, lpDrawItemStruct);
return;
}
// prepare to draw the tab control
COLORREF backgroundColor = RGB(0,255,0);
COLORREF textColor = RGB(0,0,255);
CTabCtrl *c_Tab = GetTabControl();
// Get the current tab item text.
TCHAR buffer[256] = {0};
TC_ITEM tcItem;
tcItem.pszText = buffer;
tcItem.cchTextMax = 256;
tcItem.mask = TCIF_TEXT;
if (!c_Tab->GetItem(c_Tab->GetCurSel(), &tcItem )) return;
// draw it
CDC aDC;
aDC.Attach(lpDrawItemStruct->hDC);
int nSavedDC = aDC.SaveDC();
CBrush newBrush;
newBrush.CreateSolidBrush(backgroundColor);
aDC.SelectObject(&newBrush);
aDC.FillRect(&lpDrawItemStruct->rcItem, &newBrush);
aDC.SetBkMode(TRANSPARENT);
aDC.SetTextColor(textColor);
aDC.DrawText(tcItem.pszText, &lpDrawItemStruct->rcItem, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
aDC.RestoreDC(nSavedDC);
aDC.Detach();
}
Thank you for this solution but...
The above solution works well with one tab, but when you have multiple tabs it seems rename the wrong tabs. I needed to change the if statement for GetItem to:
if (!c_Tab->GetItem(lpDrawItemStruct->itemID, &tcItem )) return;
Needed lpDrawItemStruct->itemID to get the tabs named correctly

Resources