Welcome to shell: revealed Sign in | Join | Help
in Search

Shell Blog

Vista Style Menus, Part 1 - Adding icons to standard menus

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.

Published Tuesday, February 06, 2007 10:25 AM by ksykes

Comments

 

Ted. said:

One of the best articles I've read in a very long time - no more owner draw menus for me!

February 6, 2007 12:19 PM
 

Ted. said:

Ken, the VCPROJ file is missing from the download zip (only the source code and the SLN are in there)

February 6, 2007 1:34 PM
 

ksykes said:

Thanks Ted!  The .zip file has been updated to contain a .vcrproj file.

February 6, 2007 2:58 PM
 

tina_kahn said:

wow that's definitely one feature worth trying Vista for. I haven't taken the plunge yet due to security and compatibility fears. However, I might just make the shift next week since I already found a site (http://www.radarsync.com/vista) which has an extensive database of Windows Vista drivers. I just hope that the new OS is secure enough for online purchases.

March 7, 2007 10:04 AM
 

jimbo11883 said:

Is this possible in VB.Net? I'm trying to get 32-bit png into my menus, and I've started converting the C++ code, but don't know how to convert the part of the code for IWICImagingFactory.

April 12, 2007 5:20 AM
 

Ted. said:

how about part 2?  :)

April 12, 2007 2:03 PM
 

ksykes said:

Part 2 of the article is being worked on now, it's been a challenge to balance a useful sample without making it too complicated.

Re: doing this in VB.NET, I am not an expert on the Framework but the end goal is the same: generate an HBITMAP that is a 32-bpp DIB section and put that HBITMAP in the menu.  I did look at the framework classes a bit and Graphics.DrawIcon() may be a good option.  There is an Icon.ToBitmap() method but it's documented as losing transparency.  If you figure it out feel free to add a comment with the solution!

April 25, 2007 6:23 PM
 

Jim Barry said:

Ken, thanks for writing this article. There's a slight problem with the WIC-based version, in that the images are drawn incorrectly (look closely around the edges), because the RGB values haven't been premultiplied with the alpha values.

An alternative approach that you didn't mention is simply to use GetDIBits to extract 32-bit pixel data from the hbmColor bitmap returned by GetIconInfo. This is actually a relatively efficient method, despite the creation of the temporary hbmColor and hbmMask bitmaps.

I took the liberty of performing some rudimentary timings and found the results quite interesting. WIC did very badly indeed, taking about 12 times as long as the buffered paint method. My own version using GetIconInfo/GetDIBits did somewhat better, taking only about 75% as long as buffered paint. My timings were taken over 10000 iterations, and it strikes me that the buffered paint API's bitmap cache completely failed to deliver any tangible performance benefit.

I'm still left wondering why it wasn't possible to add something like MIIM_ICON - what are the "application compatibility implications" that you mention? And couldn't you at least have provided a utility function such as CreatePARGBBitmapFromIcon to spare people from having to go through all this?

April 25, 2007 6:46 PM
 

ksykes said:

jimbo11883: I saw this article on codeproject, it may be what you're looking for:

http://www.codeproject.com/cs/miscctrl/AlphaImageImagelist.asp

April 28, 2007 11:03 AM
 

ksykes said:

Jim: Thanks for the detailed comments!  Let me try to address them:

WIC sample not generating pre-multiplied alpha: That could be, I will have to look into that.  

GetIconInfo: I don't think you're guaranteed to get PARGB32 from GetIconInfo's hbmColor bitmap.  I did this early on and it looked like it worked but I think it depends on how the icon is authored.

WIC 12x slower: That's a huge difference - were you creating/destroying the factory on each iteration?  To be fair that should be moved outside the loop.

Buffered paint slower: did you compare apples to apples?  It sounds like you compared DrawIconEx+BufferedPaint to GetIconInfo without BufferedPaint.  I haven't done extensive timings of BufferedPaint but the team that implemented it did.  If it didn't add value I don't believe we would have done it :)  If you have evidence to the contrary please share the test code and I'll look into it!

AppCompat/etc: I can't really get into details here except to say that apps do surprising things and this particular feature was not part of the original Vista design, it emerged due to internal needs.  You are correct that we should have provided a helper function to do this but it didn't work out that way.  I would like to see this situation improve for Win7.

April 28, 2007 11:36 AM
 

Jim Barry said:

Ken, thanks for following up.

GetIconInfo returns device dependent bitmaps, so if the screen mode is 32-bit then GetDIBits should retrieve ARGB pixel data from hbmColor. If the screen is at lower colour depth, then the alpha channel is lost - but that's just a general property of icons, i.e. they only get alpha blending on 32-bit displays. If there's some scenario where GetIconInfo+GetDIBits fails to get ARGB from a 32-bit icon on a 32-bit display then I'd like to hear about it, but at the moment I really can't imagine what that scenario could be.

I tried to make my timings as fair as possible. The WIC factory creation was only done once.

Yes, I was comparing DrawIconEx+BufferedPaint with GetIconInfo+GetDIBits. I'm not saying there is anything wrong with buffered paint per se. My point is that rendering the icon into a device context is almost bound to be slower than yanking out the bits explicitly. And my timings seem to confirm that quite clearly.

Fixing the situation in Win7 is closing the stable door after the horse has bolted, as Win7 apps will still need a solution when running on Vista. However, it would still be nice to have a "CreateARGBBitmapFromIcon" GDI function that has better performance than GetIconInfo+GetDIBits, by virtue of having direct access to Windows innards (i.e. avoiding creation of the intermediate hbmColor and hbmMask bitmaps). Also, it would be nice to have a function to create a 32-bit ARGB bitmap directly from an icon file or resource (regardless of the current display mode), as there is currently no straightforward way to do this.

April 30, 2007 10:48 AM
 

jimbo11883 said:

ksykes: I really don't think that article on codeproject will help me. I need a way of converting the code you supplied into VB.Net 2005 code.

May 21, 2007 5:58 PM
 

ono said:

Thanks for great article. This was my basic to create some patches for TortoiseCVS & SVN. Also I created some detailed tutorial how to create menu's with icons on various Windows systems. See the attached link.

I use there alternative method of getting PARGB32 on Vista using Gdiplus, which is IMHO much simpler than yours.

Cheers!

June 12, 2007 12:05 PM
 

ono said:

Ahh well I thought the link will be somehow visually published.. sorry for reposting but here it is:

http://www.nanoant.com/programming/themed-menus-icons-a-complete-vista-xp-solution

June 12, 2007 12:07 PM
 

s.bourne said:

Is this method valid for Vista only? Should I use owner-draw for XP or is this method valid for XP too?

September 1, 2007 6:40 PM
Anonymous comments are disabled
Powered by Community Server, by Telligent Systems © 2006 Microsoft Corporation. All rights reserved. Terms of Use | Trademarks | Privacy Statement.