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

Shell Blog

Vista Style Menus Part 2 – Custom menu drawing

Part 1 showed how to avoid owner draw menus by converting icons into bitmaps.  In Part 2 we will show how to use the Visual Style APIs in your owner draw code.

Sample Custom Menu

The sample app demonstrates a simple custom behavior: A context menu that highlights the previously selected item.  This was inspired by the "recently installed" highlighting that the Start Menu does.  A dropdown menu lets you choose between standard menu rendering, owner draw with standard rendering, and owner draw using Visual Style APIs and the new Vista menu parts.

Visual Style API Basics

Before getting into the menu specifics there are a few concepts to touch on.  A more complete discussion of the Visual Style API can be found here.

  1. Theme handles (HTHEME.)  You call OpenThemeData() with your window handle and the visual class you are interested in (VSCLASS_MENU for our purposes.)  You will get a NULL handle back if the current color scheme does not have visual styling enabled.  You also need to watch WM_SETTINGCHANGE and regenerate your HTHEME.  If you do get back a NULL handle you will need to fall back to non-styled APIs to do your rendering.
  2. Theme parts and states.  A visual element is made up of parts and each part may have multiple states.  For example, a button has a background part and different states to differentiate between pressed and non-pressed states.  The sample code will show how to translate owner-draw fState flags into part and state IDs for menus.  Vssym32.h contains the part and state IDs for every visual class.
  3. Measurement.  You inquire about the size or margins for a part / state pair using GetThemePartSize() and GetThemeMargins() respectively.  You use GetThemeTextExtent() to measure text strings.
  4. Drawing.  You can use DrawThemeBackground() and DrawThemeText() to render according to the current color scheme.

Overall structure of Sample App

The MENUITEMDATA structure holds our owner draw data and the GetMenuItem() function returns a MENUITEM structure that contains all the info we need:

struct MENUITEM

{

    MENUITEMINFO    mii;

    MENUITEMDATA    mid;

    WCHAR           szItemText[256];

};

The test app has a dropdown to switch between standard, owner draw, and owner draw using Visual APIs.  To make this easier an interface is defined and two implementors of the interface are provided (CClassicOwnerDrawMenu and CVistaOwnerDrawMenu):

interface IOwnerDrawMenu

{

    virtual HRESULT Initialize(HWND hwndParent) = 0;

    virtual void MeasureItem(__in MENUITEM *pmi, __inout MEASUREITEMSTRUCT *pmis) = 0;

    virtual void DrawItem(__in MENUITEM *pmi, __in DRAWITEMSTRUCT *pdis) = 0;

    virtual void SelectedItem(int id) = 0;

    virtual HRESULT SettingChange() = 0;

    virtual void Release() = 0;

};

The SelectedItem() method tells the class which item should receive special highlighting (The test app will call this with the previously selected menu item.)

CShellSimpleApp implements the outer shell of the program and is essentially a dialog box.  The WM_MEASUREITEM, WM_DRAWITEM, and WM_SETTINGCHANGE message handlers are the relevant portion of this class, along with the _SetMenuType() method which switches between menu types.

The remainder of this article will focus on CVistaOwnerDraw menu and it’s helper class CMenuMetrics.  CClassicOwnerDraw is left as an exercise to the reader.

Modifying owner-draw code – Initialization

Before you are able to measure or draw with the Visual APIs you need an HTHEME and, as I said earlier, the user may not be using a color scheme that uses Visual APIs.  The Initialize method will attempt to get an HTHEME based on the parent window and return failure if no HTHEME is available.  The test harness will fall back to one of the other menu types in this case.

 

The heart of the initialization is done here (this is not the complete function, it’s been pared down for illustration):

HRESULT CMenuMetrics::Initialize()

{

    HRESULT hr = E_FAIL;

   

    hTheme = OpenThemeData(hwndTheme, VSCLASS_MENU);

    if (hTheme)

    {

        GetThemePartSize(hTheme, NULL, MENU_POPUPCHECK, 0, NULL, TS_TRUE, &sizePopupCheck);

        GetThemeInt(hTheme, MENU_POPUPITEM, 0, TMT_BORDERSIZE, &iPopupBorderSize);

        GetThemeMargins(hTheme, NULL, MENU_POPUPCHECK, 0, TMT_CONTENTMARGINS, NULL, &marPopupCheck);

           

        hr = S_OK;

    }

    return hr;

}

An HTHEME is requested from the parent window which maps to the menu class.  If it succeeds we then ask for the metrics we will need to properly measure and layout our menu items.

Modifying Owner-Draw code – Measurement

There are several “Get” functions that return information on a part/state:

  • GetThemePartSize() will give you the dimensions of a part/state pair.
  • GetThemeMargins() will give you the spacing around a part/state pair.
  • GetThemeInt(…, TMT_BORDERSIZE, …) will give you the size of the border around a part/state pair
  • GetThemeTextExtent() will give you the dimensions of the text you specify using the correct font for the part/state pair.  The parameters are similar to the DrawText API, including a parameter that accepts DT_* flags.

This information will enable you to make the appropriate measurement and layout calculations.  In the test app CMenuMetrics caches these metrics and provides some helper functions, like ToMeasureSize() which applies the specified margins to the tight bounding box you calculated for the menu item.

Modifying Owner-Draw code – Drawing

The first thing you need to do is convert the DRAWITEMSTRUCT’s itemState field into the correct state id (POPUPITEMSTATES for popup menus.)  For example, ODS_HOTLIGHT gets translated to MPI_HOT and ODS_INACTIVE can get translated to MPI_DISABLED.  See CMenuMetrics::ToItemStateId() for details.

The next thing you do is layout the items according to the metrics you calculated during WM_MEASUREITEM and draw the menu in layers using DrawThemeBackground(), starting from the bottom layer:

  • MENU_POPUPBACKGROUND (if the background contains transparency)
  • MENU_POPUPGUTTER (if you want a gutter)
  • MENU_POPUPSEPARATOR (if the item is a separator)
  • MENU_POPUPITEM
  • MENU_POPUPCHECKBACKPGROUND (if you are rendering a checkmark)
  • MENU_POPUPCHECK (if you are rendering a checkmark)
  • DrawThemeText(…, MENU_POPUPITEM, …)

CVistaOwnerDraw::_DrawMenuItem() demonstrates this process.

Special considerations

Allowing the test app to switch between owner-draw and non-owner draw menus presented an interesting issue: USER does not issue new WM_MEASUREITEM messages when the MFT_OWNERDRAW bit is toggled so it continues to use the old metrics.  This may be old news (it appears to have always worked this way) but it was a surprise to me.

Fortunately there is a simple workaround: make sure fMask has MIIM_BITMAP set when you call SetMenuItemInfo() and this will cause new WM_MEASUREITEM messages to be sent.  The ResetMenuMetrics() helper function will clear out all the menu items of the specified hmenu.  A more efficient method would be to set this bit when flipping the MFT_OWNERDRAW bit but I wanted to call this out clearly in the sample code.  The MakeOwnerDraw() helper function is used by the test app to change between owner-draw and non-owner-draw.

Important details not covered in article

The sample code presents a simplified version of menu rendering in order to explain the basic concepts.  It does not use every part and state (no submenu rendering), it does not cover the menu bar, and it will not necessarily line up to the pixel with the system’s menu rendering.  If you are in the business of custom rendered menus then I don’t believe any of these simplifications will be an issue for you. 

At this point someone may say “Why didn’t you make an API that would render portions of the menu to make all this easier?”  The answer to that is a familiar one: time and resources.  Vista was a big undertaking and we had plenty to do in order to get it wrapped up and out the door. 

Conclusion

The Visual Style APIs provide all the mechanisms needed to measure, layout and render many of our visual elements, and Vista added menu visuals with VSCLASS_MENU.  In spite of this owner-draw menus are still a significant amount of work to develop and maintain.  Now that Windows draws nicer looking menus, and there’s a way to get good looking icons with standard menus, I hope that the amount of owner-draw menu code reduces dramatically, leaving all of you with more time for innovation of your core products.

Published Wednesday, May 09, 2007 7:03 PM by ksykes

Comments

 

rev23dev said:

fantastic, how about now you show us why when we have an owner drawn combobox with DropDownList style it doesn't draw with the new "button" look.

May 10, 2007 11:51 AM
 

Rean said:

"A more complete discussion of the Visual Style API can be found _here_"  The link points to the sample code. Where can I find this discussion?

May 11, 2007 7:14 AM
 

ksykes said:

Thanks for catching that Rean, I had edited the wrong hyperlink.  It's fixed now.

May 11, 2007 9:24 AM
 

Rean said:

Is there any picture picture explaining all these menu metrics and menu parts? I create, process and draw whole menu by myself (from various reasons) and it's kinda hard to guess how to draw it. I'm able to draw the menu that it looks exactly as system menu, but I'm not sure, if it's the right way. Are there any other visual styles with different metrics that can be used on Windows Vista to test my implementation on?

Personally, I don't like the baby-blue buttons in Aero. Does Microsoft plans to release other official visual styles for Vista? Or it will be again up to 3rd party companies to create new visual styles (Window Blinds etc.)?

Visual Styles API looks promising, but lack of serious documentation and examples is something than I didn't expect.

Btw I still can't find any discussion on that MSDN link... Am I blind or just stupid? :-)

May 14, 2007 5:57 AM
 

ksykes said:

Maybe blind? :)

this is the link which you should be getting:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/userex/refentry.asp

It shows how to use Visual APIs in general, not specifics about the menu visuals.  Maybe that is what you were expecting?

A picture of the menu metrics / visuals would definitiely be helpful and I wanted to make one for the article but it's a little above my graphics design grade.  If I can get one of our graphic designers to make a professional one I will post it.

Window Blinds and others have reverse-engineered our file formats and are not sanctioned methods of extension.  I think it's fair to say that our focus is on the glass-enabled version of Aero and the lack of graphics h/w acceleration when not running glass Aero is annoying (esp. for an old GDI developer!)  Beyond that, I'm not privvy to future plans on visual styles, and wouldn't be the person to announce the details anyways! :)

May 14, 2007 12:09 PM
 

ksykes said:

rev23dev: I posted an answer on Friday but it never made it to the website:

We were not able to enable visual style for owner-draw comboboxes because of apps which depend on the control background matching GetSysColor(COLOR_WINDOW) (i.e. white)

The choices we thought of are to switch to a standard comboboxex /w an imagelist (if you just wanted to present icons in the dropdown) or subclass the combobox and call the visual APIs yourself.  

May 14, 2007 12:16 PM
 

Rean said:

ksykes:

First of all, thank you for your patience with me. What I was expecting was some kind of user comments (discussion), not just plain reference. Have you ever seen user notes in PHP manual at www.php.net? That's what I call comunity. I know, there is a place for "Comunity Content" on some MSDN pages, but I haven't find any content yet. And for Visual Styles Reference I would recommend this link - http://msdn2.microsoft.com/en-us/library/ms649782.aspx - it contains more information and some topics are updated with Vista specific content (e.g. "Parts and States").

A picture of menu metrics doesn't have to be professional, I would appreciate nearly anything. I'm not sure about popup margins, popup item margins and gutter. Something similar to this http://shellrevealed.com/photos/blog_images/images/4538/original.aspx would be great.

I do understand that reverse-engineering is not sanctioned method but is there any official method? I would like to be able to create and modify easily my own visual styles, distribute them with our application and let user to choose their visual style. But that's only my naive dream :-)

I've developed my own skinning system for windows, toolbars and menus. But now with Vista, where I can't draw to caption, I'm a bit trapped. I do want to fully support Aero (that's nearly done), but I don't want to lose ability to change appearance of our application...

May 14, 2007 4:35 PM
 

Maddy said:

Hello,

have u seen Maddy anywhere?

June 4, 2007 12:20 PM
 

calloatti said:

It would be nice if you mentioned this two part article to the IE7 guys who are still owner drawing the Favorites menu, with no style, in Vista.

October 13, 2007 6:19 AM
Anonymous comments are disabled
Powered by Community Server, by Telligent Systems © 2006 Microsoft Corporation. All rights reserved. Terms of Use | Trademarks | Privacy Statement.