Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WTL
Article

Cool WTL menu with Gradient Sidebar

Rate me:
Please Sign up or sign in to vote.
3.00/5 (4 votes)
29 May 2002 218.9K   2.5K   32   55
An article about changing the look of WTL icon menu

Sample Image - sidebarmenu.gif

First some history

Some months ago I was wondering how I could get cool menus with icons like Word. I was using MFC then and found the BCMenu class from Brent Corkum. It used the toolbar resource for mapping the icons with the menu. I liked this class and altered it somewhat to make it look like a menu I saw in the application SmartFTP from Mike Walter which inspired me to do this. This resulted in the same result as seen in the screenshot (but without the shading...). After some weeks I found the WTL header files on the Platform SDK and I thought it would be cool to make the same kind of menu for WTL by altering the WTL source. This may not seem the best action but all my WTL apps just needed one recompile and voila, cool looking menus. This article will explain how I altered the WTL source step-by-step. Anyone with some knowledge of C++ can easily follow this article. The alterations are also possible for the BCMenu class from Brent Corkum.

What has WTL got to do with this?

I have to be honest that the alterations are not WTL specific. But WTL already provides you with an iconmenu simular to the BCMenu. First is that I do not want you to force using BCMenu to get icons, second is to make you see how easy it is to make minor adjustments to do cool stuff with WTL.

Analyzing the screenshot

When you take a look at the screenshot you will probably notice the following things:

  • The hotitem has a 3D look border
  • The accellerator keys (shortcuts) are printed in blue
  • It has a gradient sidebar with vertical text (btw. the font is called LCD)

I will explain each of the three seperately. The WTL header file that we will be editing is 'atlctrlw.h' so make a backup of this file in case you screw up.

The 3D look border

At first I did now know where to start looking in the headerfiles so I just started looking for a DrawItem method until I found the correct file which is 'atlctrlw.h'. I then 'read' the source code and here is a textual description of both the DrawItem and MeasureItem methods:

MeasureItem:

  1. It retrieves the itemdata
  2. If its a seperator then it returns the seperators height and a width of 0
  3. It calculates the text width and height depending on it's state (normal, bold, font type).
  4. It adds margins, iconwidth(SM_CXMENUCHECK) and spacing to the width and returns it

DrawItem:

  1. It gets the currect itemdata
  2. It checks if the currect item is a seperator and draws it if it is one
  3. Some checks on the itemdata to draw the menuitem in a certain state
  4. It calculates the 'square' available for text
  5. If it has an icon it draws it in a certain way depending on the itemstate, if not then it check the itemdata if it is 'checked' and draws a checkmark accordingly.
  6. It fills the background with a certain color depending on the state
  7. It calls DrawMenuText(..) with a certain text color depending on the state

Windows calls MeasureItem for each item in a menu. This way it can calculate the width and height of the menu. After this menu window has been created it calls DrawItem for each menuitem and gives it a rectangle with information for where to output the menuitem it's data in the window.

So where do we need to draw the rectangle? The rectangle needs to be drawn after the background has been filled. So step 6 of DrawItem seems appropriate. So we need to add the following two lines of code after the line:

dc.FillRect(&rcBG, (HBRUSH)LongToPtr(bSelected ? (COLOR_HIGHLIGHT + 1) : 
                                                 (COLOR_MENU + 1)));
...
if(bSelected)
    dc.Draw3dRect(&rcBG, GetSysColor(COLOR_3DDKSHADOW), 
                      GetSysColor(COLOR_3DHILIGHT));
...

My first version used dc.DrawEdge(..) but this made the 3D look too '3D' :-). With Draw3DRect the menuitem looks like the 'sunken' toggle/style which most controls have.

So now we have one down but still two to go...

The accellerator keys

The accellerator keys needed some more attention. It needed to alter the DrawMenuText(..) method. I just set the color to the systems Highlight color just before drawing the shortcut and set it back to original color afterwards. But this has one bad side effect. When you 'hovered' above a menuitem you would not see the shortcut-key because it had the same color as the fillcolor. A better way is to draw the shortcut-key with the same color if it is highlighted and in the highlight color if it's normal. But DrawMenuText does not have access to the menuitemdata. I decided to make a copy of the DrawMenuText method and added an extra parameter which contained the color to draw the shortcut-key with. This way I can pass the correct shortcut-key color in the DrawItem method based on the itemdata.

void DrawMenuText(CDCHandle& dc, RECT& rc, LPCTSTR lpstrText, 
                  COLORREF colorText, COLORREF colorAccellerator)
{
    int nTab = -1;
    for(int i = 0; i < lstrlen(lpstrText); i++)
    {
        if(lpstrText[i] == '\t')
        {
            nTab = i;
            break;
        }
    }
    dc.SetTextColor(colorText);
    dc.DrawText(lpstrText, nTab, &rc, 
                DT_SINGLELINE | DT_LEFT | DT_VCENTER);
    dc.SetTextColor( colorAccellerator );
    if(nTab != -1)
        dc.DrawText(&lpstrText[nTab + 1], -1, &rc, 
                    DT_SINGLELINE | DT_RIGHT | DT_VCENTER);
    dc.SetTextColor(colorText);
}

Now we only need to pass an extra parameter. I added the result of the code

::GetSysColor(bDisabled ? COLOR_GRAYTEXT : 
             (bSelected ? COLOR_HIGHLIGHTTEXT : COLOR_HIGHLIGHT))
as a parameter.

Well still one to go.

The gradient sidebar with text

The most work to do was the sidebar. I started thinking about how to make room to draw it. So I just added a certain width to each MeasureItem, but then I had to chop the bar in different draw pieces for eacht menuitem. So this was clearly not the way to go. I then found out how I could determine the size of the menu with dc.GetClipBox(..). I then did the same width trick as mentioned a few lines back. But I just needed to draw the sidebar once and not for each menuitem which had to be drawn. So I checked for a certain ID (=666) in the itemdata to make sure it was only drawn once. This still did not work as I expected, it totally messed up because of onmouseover messages (this is called hottracking right?). Then I just found the Break dropdownlistbox in the menuresourceeditor. I used a special ID for the sidebar in the first row of every menu (ID_SIDEBAR), set the Break option to 'column' for each second row and changed the resource value of ID_SIDEBAR to 666. Now I only needed to check for 666 in MeasureItem and DrawItem methods to do my own stuff. If I encountered 666 in MeasureItem I returned 0 for the menuitem height and the width of the sidebar minus the iconwidth for the menuitem width. If I encountered 666 in the DrawItem method I called my own added method DrawSidebar to draw the sidebar. DrawSidebar also printed the menuitem its string in a vertical position so I did not had to use special bitmaps anymore.

I thought it would be cool to add a gradient background just like the startmenu (althought that is just a bitmap). So I used the ::GradientFill call to do this. This is why it only works with Windows98 and Windows2000. If you just do a regular fill or add your own gradient method (the later is not that hard to write) then you are version independant. The other option is to distribute msimg32.dll with your application but I do not think Microsofts license agreement allows this.

void DrawSideBar (CDCHandle& dc, RECT& grrect, LPCTSTR lpstrSidebarText)
{
    RECT rct;
    dc.GetClipBox(&rct);
    int iWidth = grrect.right - grrect.left;
    int iHeight = grrect.top - grrect.bottom;
    int iSideBarHeight = rct.bottom-rct.top;
    int iSideBarWidth = rct.right-rct.left;

    COLORREF right    = GetSysColor(COLOR_ACTIVECAPTION);
    COLORREF left    = GetSysColor(27); // COLOR_GRADIENTACTIVECAPTION

    COLOR16 r = (COLOR16) ((left & 0x000000FF)<<8);
    COLOR16 g = (COLOR16) (left & 0x0000FF00);
    COLOR16 b = (COLOR16) ((left & 0x00FF0000)>>8);

    TRIVERTEX        vert[2] ;
    GRADIENT_RECT    gRect;
    vert [0] .x      = 0;
    vert [0] .y      = 0;
    vert [0] .Red    = r;
    vert [0] .Green  = g;
    vert [0] .Blue   = b;
    vert [0] .Alpha  = 0x0000;

    r = (COLOR16) ((right & 0x000000FF)<<8);
    g = (COLOR16) (right & 0x0000FF00);
    b = (COLOR16) ((right & 0x00FF0000)>>8);

    vert [1] .x      = iWidth;
    vert [1] .y      = iSideBarHeight;
    vert [1] .Red    = r;
    vert [1] .Green  = g;
    vert [1] .Blue   = b;
    vert [1] .Alpha  = 0x0000;
    gRect.UpperLeft  = 0;
    gRect.LowerRight = 1;
        
    GradientFill(dc.m_hDC,vert,2,&gRect,1,GRADIENT_FILL_RECT_V);

    HFONT hFont;

    hFont = CreateFont(iWidth, 0, 900,900,0,FALSE,FALSE,FALSE,0,
        OUT_DEFAULT_PRECIS,CLIP_MASK, PROOF_QUALITY, FF_DONTCARE, 
        _T(SIDEBAR_FONT));

    if (lpstrSidebarText)
    {
        dc.SetBkMode(TRANSPARENT);
        HFONT fontold = dc.SelectFont( hFont );

        RECT dims;

        dims.left = dims.top = 1;
        dims.right = iWidth;
        dims.bottom = iSideBarHeight;

        dc.SetTextColor( 0x0 );
        dc.DrawText(lpstrSidebarText, strlen(lpstrSidebarText),
            &dims,
            DT_SINGLELINE|DT_BOTTOM);

        dims.top -= 1;
        dims.left -= 1;
        dims.right -= 1;
        dims.bottom -= 1;

        dc.SetTextColor( GetSysColor(COLOR_CAPTIONTEXT) );
        dc.DrawText(lpstrSidebarText, strlen(lpstrSidebarText),
            &dims,
            DT_SINGLELINE|DT_BOTTOM);
        dc.SelectFont( fontold );
    }
}

This method needs to be called from MeasureItem like:

if(lpDrawItemStruct->itemID == SIDEBAR_ID)
{
    DrawSideBar(dc,(RECT)rcItem, pmd->lpstrText);
    return;
}

SIDEBAR_ID is just a define which is 666 in my source (the same as the ID_SIDEBAR value). You can add it even before you check if it is a seperator, but I choose to add it right after the start of the seperator its else part.

if(lpMeasureItemStruct->itemID == SIDEBAR_ID)
{
    // We only need to return the width of the sidebar to let windows
    // know the width of sidebar. It doesn't need to reserve height
    // because we determine the height ourselfs in the DrawItem method
    lpMeasureItemStruct->itemWidth = SIDEBAR_WIDTH - 
                                     GetSystemMetrics(SM_CXMENUCHECK);
    lpMeasureItemStruct->itemHeight= 0;
    return;
}
The same can be said for the above code which needs to be added to the MeasureItem method.

So to add a SideBar to a menu you should:

  1. Set the ID of first menurow of the menu you want to change to ID_SIDEBAR
  2. Enter a shortpiece of text to be shown vertically on the sidebar
  3. Set the Break dropdownlistbox of the second row to 'column' (or 'bar')
  4. Change the value of ID_SIDEBAR in resource.h to 666

If everything compiles and links ok then your application will now have a sidebar on the left in the edited menu.

History

30 May 2002 - updated source


The altered WTL header file and LCD font are included in the source. If you use this code then please mail me that you are using it so I can see if it was worth the trouble of writing this article.

Well, I hope you also have learned something of this article. Mail me bugs, flames, comments, suggestions, improvements or just for fun.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Netherlands Netherlands
My name is Ramon Smits and I live in the Netherlands in Rotterdam. My profession is software development and I work for the company InfoSupport. I develop using the programming languages C++, C#, VB6, (D)HTML, JavaScript, ASP(.NET), T-SQL. Languages that I used in the past are Borland Pascal and even x86 assembler (I still use it if I really have to but not at work but as a hobby). I am also familiar with (D)COM, MTS/COM+, ATL/WTL, STL, OLEDB, ADO, DirectX. So you could say that you can put me everywhere in an n-tier environment Smile | :) . I have knowledge about SOA, OO, UML, Prince II.

Developing with Microsoft ASP.NET with serviced components and webservices is my main occupation at work. Current projects are build with SOA like architecture. I am experienced with TDD, unittesting, interoperability, contract-first and agile practices.

When I'm at home and I really do not have anything to-do except watching Jerry Springer I turn on my nice old computer and do some cool coding. Some years ago I did this in Watcom C++ with the pmode extender in VESA but now I use managed C++ with DirectX 9. I have common knowledge about 3D engines, particle systems, terrain engines and general graphics programming.

Then there are three hobbies left and that's volleybal, formula one and sudoku Wink | ;-)

Comments and Discussions

 
GeneralMENUBREAK can't be a PopUp Menu Pin
benjamin2320-Mar-08 0:02
benjamin2320-Mar-08 0:02 
QuestionPlease Help me out Pin
PrafullaT11-Sep-06 0:52
PrafullaT11-Sep-06 0:52 
AnswerRe: Please Help me out Pin
PrafullaT11-Sep-06 3:50
PrafullaT11-Sep-06 3:50 
GeneralDrawText and gradiants Pin
RJSoft22-Sep-04 13:03
RJSoft22-Sep-04 13:03 
GeneralRe: DrawText and gradiants Pin
Ramon Smits24-Sep-04 22:51
Ramon Smits24-Sep-04 22:51 
GeneralRe: DrawText and gradiants Pin
RJSoft25-Sep-04 0:20
RJSoft25-Sep-04 0:20 
GeneralRe: DrawText and gradiants Pin
RJSoft25-Sep-04 0:24
RJSoft25-Sep-04 0:24 
GeneralText draws horizontally Pin
Dimitris Vasiliadis23-May-04 5:08
Dimitris Vasiliadis23-May-04 5:08 
GeneralRe: Text draws horizontally Pin
pb94946-Dec-04 2:47
pb94946-Dec-04 2:47 
GeneralFont resource leaks Pin
Ralph Walden20-May-04 5:57
Ralph Walden20-May-04 5:57 
GeneralCompilation Size Pin
PinkPanter8-Jun-02 7:01
PinkPanter8-Jun-02 7:01 
GeneralRe: Compilation Size Pin
Ramon Smits9-Jun-02 23:35
Ramon Smits9-Jun-02 23:35 
GeneralSource file package incorrect Pin
Ramon Smits24-May-02 7:10
Ramon Smits24-May-02 7:10 
QuestionHow to get the include file "atlres.h"? Pin
17-May-02 3:07
suss17-May-02 3:07 
AnswerRe: How to get the include file "atlres.h"? Pin
Tim Smith17-May-02 3:36
Tim Smith17-May-02 3:36 
GeneralGDI-AlphaBlend -- Link error Pin
25-Feb-02 5:00
suss25-Feb-02 5:00 
GeneralRe: GDI-AlphaBlend -- Link error Pin
dsreynolds3-Nov-04 9:29
sussdsreynolds3-Nov-04 9:29 
GeneralGradient sidebar with vertical text in Win2k Pin
24-Dec-01 17:13
suss24-Dec-01 17:13 
GeneralRe: Gradient sidebar with vertical text in Win2k Pin
Ramon Smits2-Jan-02 22:19
Ramon Smits2-Jan-02 22:19 
GeneralRe: Gradient sidebar with vertical text in Win2k Pin
3-Jan-02 0:16
suss3-Jan-02 0:16 
OK, Thank you for the answer, I understood this, but why __horizontal__ text, not vertical?
GeneralRe: Gradient sidebar with vertical text in Win2k Pin
pb94946-Dec-04 2:40
pb94946-Dec-04 2:40 
GeneralUser-drawn popup menus Pin
25-May-01 8:41
suss25-May-01 8:41 
GeneralRe: User-drawn popup menus Pin
Ramon Smits25-May-01 9:28
Ramon Smits25-May-01 9:28 
GeneralDoes Not Compile Pin
27-Feb-01 14:18
suss27-Feb-01 14:18 
GeneralRe: Does Not Compile Pin
Ramon Smits27-Feb-01 21:11
Ramon Smits27-Feb-01 21:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.