Windows XP introduced Visual Styles as a mechanism for providing more visual appeal to windows and controls. Menus, however, were not rendered using visual styles for Windows XP. With Windows Vista menus now are part of the visual schema and are rendered using the visual styles engine.
Whenever we add new features to existing code, especially code with a long history like the Window Manager, we have to be careful not to break existing application behavior. In the context of menus there are a few situations that require us to disable visual style rendering:
· Owner-draw items (MFT_OWNERDRAW)
· Menu breaks (MFT_MENUBREAK or MFT_MENUBARBREAK)
· Use of HBMMENU_CALLBACK to defer bitmap rendering
· Using a destroyed menu handle
The reasoning for each of these may be discussed in a future blog entry. For the time being it’s only important for you to understand what may be causing your menu to render without visual styling.
Owner Draw Menus
In previous versions of Windows applications marked menus or menu items as owner draw in order to customize the rendering. In Windows Vista you can still use owner draw menus but you have to understand that they will not be visually styled.
If your code is using owner draw menus you have some options:
1. Do nothing. Your owner-draw menu(s) will continue to render the same as always. If you have a mixture of standard and owner-draw menus then your customers will see a mix of old and new rendering methods.
2. Get rid of your owner draw menus. You may decide that the benefits of your custom rendering do not outweigh the benefit of letting the system render visual styled menus.
3. Convert your menu icons to bitmaps. Rendering icons next to menu items turned out to be a very common reason in the shell to implement owner draw menus. Windows Vista offers an alternative, alpha blended bitmaps, which lets you show icons in your menu items without the need for owner draw. The remainder of this article discusses how to do this.
4. Make use of menu visual style parts and Visual Style APIs to render your owner draw menus. This will be discussed in a future article.
New for Windows Vista: Alpha-blended menu bitmaps
Given that a common reason for using owner draw menus is to show icons the logical thing to do would be to add an hIcon field to the MENUITEMINFO structure and add the drawing code using the hIcon. This option was not viable though given its relatively late addition to the feature set and the application compatibility implications so we came up with a compromise: support alpha blended bitmaps. Not only does it allow for icon display, it gives more flexibility than just allowing an hIcon to be specified.
Windows will alpha blend a menu bitmap that satisfies all of these requirements:
· The bitmap is a 32bpp DIB section
· The DIB section has BI_RGB compression
· The bitmap is assumed to contain pre-multiplied alpha pixels.
· The bitmap is stored in hbmpChecked, hbmpUnchecked, or hbmpItem fields. MFT_BITMAP items do not support PARGB32 bitmaps.
Sample Code
Now the question is: how do you convert an hIcon to a PARGB32 bitmap? The answer depends on which graphics system you are comfortable using. The easiest solution is to use the Windows Imaging Component (WIC):
IWICImagingFactory *pFactory;
HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFactory));
if (SUCCEEDED(hr))
{
IWICBitmap *pBitmap;
hr = pFactory->CreateBitmapFromHICON(hicon, &pBitmap);
if (SUCCEEDED(hr))
{
UINT cx, cy;
hr = pBitmap->GetSize(&cx, &cy);
if (SUCCEEDED(hr))
{
const SIZE sizIcon = { (int)cx, -(int)cy };
BYTE *pbBuffer;
hr = Create32BitHBITMAP(NULL, &sizIcon, reinterpret_cast<void **>(&pbBuffer), &hbmp);
if (SUCCEEDED(hr))
{
const UINT cbStride = cx * sizeof(ARGB);
const UINT cbBuffer = cy * cbStride;
hr = pBitmap->CopyPixels(NULL, cbStride, cbBuffer, pbBuffer);
}
}
pBitmap->Release();
}
pFactory->Release();
}
The sizIcon height is negative because CreateBitmapFromHICON() makes a top-down bitmap so we must make a top-down DIB section to match it. Create32BitHBITMAP() is a wrapper function for CreateDIBSection() that makes a 32bpp, BI_RGB DIB section.
A working application that demonstrates this code can be downloaded here.
The interesting functions:
The interesting functions: HRESULT AddIconToMenuItem(__in IWICImagingFactory *pFactory, HMENU hmenu, int iMenuItem, BOOL fByPosition, HICON hicon, BOOL fAutoDestroy, __out_opt HBITMAP *phbmp)
Convert an hIcon to a bitmap and adds it to the specified item’s hbmpItem field. The caller is responsible for destroying the generated bitmap and the icon will be destroyed if fAutoDestroy==TRUE. If you plan on using the menu item’s hbmpItem field to keep track of the bitmap you can pass NULL to phbmp.
BOOL TrackPopupIconMenuEx(HMENU hmenu, UINT fuFlags, int x, int y, HWND hwnd, __in_opt LPTPMPARAMS lptpm,
UINT cchIcons, __in_ecount_opt(cchIcons) const ICONMENUENTRY *pIcons)
Adds a list of icon resources to the menu, calls TrackPopupMenuEx(), then deletes the bitmaps. Here’s the call that produces the menu shown above:
void CShellSimpleApp::_OnRightClick(WPARAM wParam, int x, int y)
{
POINT ptClient = { x, y };
ClientToScreen(_hdlg, &ptClient);
HMENU hmenu = LoadMenu(g_hinst, MAKEINTRESOURCE(IDM_CONTEXTMENU));
if (hmenu)
{
ICONMENUENTRY aIcons[] = {
{ IDM_INFORMATION, NULL, IDI_INFORMATION },
{ IDM_ELEVATE, NULL, IDI_SHIELD }
};
TrackPopupIconMenuEx(_pWICFactory, GetSubMenu(hmenu, 0), 0, ptClient.x, ptClient.y, _hdlg, NULL, ARRAYSIZE(aIcons), aIcons);
DestroyMenu(hmenu);
}
}
If you use comctl32 v6 then you are already loading WindowsCodecs.dll and WIC is a great solution. If you want a solution that only uses GDI then it takes more effort:
1. Create a 32bpp, BI_RGB DIB Section and select it into a DC
2. Use BeginPaintBuffer() to target the DC/Bitmap from step 1. The paint buffer is an ARGB32 surface. We use this function to take advantage of the Paint Buffer API’s bitmap caching (as opposed to calling CreateDIBSection.) You must call BufferedPaintInit() at the start of your program in order to enable bitmap caching.
3. Use DrawIconEx() to draw/scale the icon into the paint buffer.
4. Check the paint buffer to see if it contains alpha values other than 0 or 255. If it does not then convert the buffer to pre-multiplied alpha pixels.
5. Use EndPaintBuffer() to transfer the converted icon to our bitmap created in step 1.
6. Use SetMenuItemInfo() to add the bitmap to the menu item.
7. Cleanup and return the bitmap
The sample code package contains a GDI implementation in GDI_CVistaMenu.cpp (this file replaces CVistaMenu.cpp). You will also want to look at _OnInitDialog () and _OnDestroyDlg() to see how the BufferedPaint API is initialized. Calling BufferedPaintInitialize() is not required for this code to work but doing so will leverage the bitmap cache, making the conversion of multiple bitmaps faster. TrackPopupIconMenuEx() makes use of LoadIconMetric () to create high quality hIcons.
Some of you have asked how to add a UAC shield to a menu item so one of the sample icons is IDI_SHIELD.
There is one other detail that needs to be emphasized: You need to do the SetMenuInfo() call with MNS_CHECKORBMP (see TrackPopupIconMenuEx()) in order to make the menu look good in the Windows Classic color scheme.
Rendering owner draws menus with the Visual Styles API
While this method eliminates a common reason to use owner draw menus, there are other situations that will still demand using owner draw menus. If you find yourself in this situation stay tuned for Part 2: Rendering with Visual Styles API.