Visual Basic Menu API Routines
SetMenuItemInfo: Custom Application Menu Colours
Posted:   Saturday February 15, 2003
Updated:   Monday December 26, 2011
Applies to:   VB4-32, VB5, VB6
Developed with:   VB6, Windows XP
OS restrictions:   Windows 98 or later, Windows 2000 or later
Author:   VBnet - Randy Birch


SetMenuItemInfo: Menu Scroll a Standard VB Menu
SetMenuItemInfo: Create a Multi-Column Font Menu
The MENUINFO structure used is available on Win98 or later, or Win2000 or later..

Here's a cool way to break every UI design rule in the book.

Windows' menu colour is a system property that applies to all applications, set through Control Panel's Appearance tab on the Display / Properties dialog. This setting is system-wide, and a well-behaved VB application will always respect the user's menu preferences.

But in a skinned or textured app there may be justifiable reasons to utilize a custom menu bar or menu/submenu colour. This page shows how to give your form a custom menu colour without affecting or changing the system's menu settings and, consequently, the user's preferences for Windows and for other applications.

As coded, the methods provide for the application to pass colour values as either long integers, long hexadecimal values, longs generated as the result of a call to the RGB() function, or VB or Windows constants representing standard system colours.

Translation of the colour value) passed is made via OleTranslateColor() in order to provide a mechanism ensure valid colours are applied (when the application provides for the user to choose the colours. Should the application not require this level of sophistication, for example when a specific or pre-defined set of known valid colours will are to be used, the routines could be simplified to directly use those values.

The routine provides for setting the colour of either or both of the main menu bar and the actual dropdown menu. At a minimum, the custom colour must be applied to the main menu bar. To provide the means to shade the menu bar and submenus differently (e.g. where different colours are desired for the menu bar and dropdown menus), the routine is called twice -- the first time specifying the colour for the dropdown menus (bIncludeSubmenus as True), then again with the colour for the menu bar passing bIncludeSubmenus as False. On the first call both the menu bar and submenus are assigned the new colour. On the second call only the menu bar itself is changed. The system menu responds to the same colouring technique, thus the demo includes the code to change the system menu to either the same or an entirely different colour than that of the other dropdown menus or menu bar.

Where the developer has created a menu for display as a popup menu, that menu can also receive the same or its own custom colour --- the popup will be displayed in the colour chosen. Naturally, popup menus defined on another form which has not received customization will appear in the normal system-wide menu preference colour.

Although this will customize the menu's BackColor, no corresponding method exists to change the menu text colour without delving into some serious subclassing.

If you would like your form's BackColor to follow the menu colour selected by the user, uncomment the BackColor lines in the demo code below. Also, be aware that to enable Windows to repaint the menu using the custom colour, it requires the brush created to persist during the lifetime of the application. Therefore, do not call DeleteObject to free the brush created; Windows will delete the brush on application termination.

One final point. Because Windows uses the hbrush specified to paint the menu, you could substitute CreateHatchedBrush for CreateSolidBrush -- the menu would be painted with diagonal, horizontal, vertical, hatched, or checked (square) lines alternating between the chosen colour and white. This is not recommended of course, but mentioned just to show it is possible.

 BAS Module Code

 Form Code
Add a combo box (Combo1) and a common dialog (CommonDialog1) to a form containing a menu. The menu can be have any menu name as the methods use GetMenu and related calls to obtain the actual hwnd to the menus. Add the following code to the form:

Option Explicit
' Copyright 1996-2011 VBnet/Randy Birch, All Rights Reserved.
' Some pages may also contain other copyrights by the author.
' Distribution: You can freely use this code in your own
'               applications, but you may not reproduce 
'               or publish this code on any web site,
'               online service, or distribute as source 
'               on any media without express permission.
Private Const MIM_BACKGROUND As Long = &H2
Private Const MIM_APPLYTOSUBMENUS As Long = &H80000000

Private Type MENUINFO
   cbSize As Long
   fMask As Long
   dwStyle As Long
   cyMax As Long
   hbrBack As Long
   dwContextHelpID As Long
   dwMenuData As Long
End Type

Private Declare Function DrawMenuBar Lib "user32" _
  (ByVal hwnd As Long) As Long

Private Declare Function GetMenu Lib "user32" _
  (ByVal hwnd As Long) As Long

Private Declare Function GetSystemMenu Lib "user32" _
  (ByVal hwnd As Long, _
   ByVal bRevert As Long) As Long

Private Declare Function SetMenuInfo Lib "user32" _
  (ByVal hmenu As Long, _
   mi As MENUINFO) As Long

Private Declare Function CreateSolidBrush Lib "gdi32" _
  (ByVal crColor As Long) As Long

Private Declare Function OleTranslateColor Lib "olepro32.dll" _
  (ByVal OLE_COLOR As Long, _
   ByVal HPALETTE As Long, _
   pccolorref As Long) As Long

Private Sub Form_Load()

   Dim cnt As Long
   With Combo1
      .Move 3720, 240
      .AddItem "Button Face (Default)"
      .ItemData(.NewIndex) = vbButtonFace
      .AddItem "Application Workspace"
      .ItemData(.NewIndex) = vbApplicationWorkspace
      .AddItem "Window Background"
      .ItemData(.NewIndex) = vbWindowBackground
      .AddItem "Active Title Bar"
      .ItemData(.NewIndex) = vbActiveTitleBar
      .AddItem "Tooltip Background"
      .ItemData(.NewIndex) = vbInfoBackground
      .AddItem "(Select colour...)"
      .ListIndex = 0
   End With
End Sub

Private Sub Combo1_Click()

   Dim clrref As Long
   With Combo1
      If .ListIndex > -1 Then
         Select Case .ListIndex
            Case .ListCount - 1
               clrref = GetColor()
               If clrref > -1 Then
                  SetMenuColour Me.hwnd, clrref, True
                  SetSysMenuColour Me.hwnd, clrref
                  'Me.BackColor = clrref
               End If
            Case Else
               SetMenuColour Me.hwnd, .ItemData(.ListIndex), True
               SetSysMenuColour Me.hwnd, .ItemData(.ListIndex)
               'Me.BackColor = .ItemData(.ListIndex)
         End Select
      End If
   End With
End Sub

Private Function GetColor() As Long

   On Local Error GoTo GetColor_error

   With CommonDialog1
     'trap cancel
      .CancelError = True

     'match colour if possible
      .flags = cdlCCRGBInit
     'get the actual color from
     'the form's current background.
     'This could also be passed as a
     'parameter to this method for
     'increased functionality
      .Color = TranslateOLEtoRBG(Me.BackColor)
     'get and return the colour selected
      GetColor = .Color
   End With

   Exit Function

  'return -1 to enable trapping
  'and still allow black (value 0)
  'to be selected
   GetColor = -1
   Resume GetColor_exit

End Function

Private Function SetMenuColour(ByVal hwndfrm As Long, _
                               ByVal dwColour As Long, _
                               ByVal bIncludeSubmenus As Boolean) As Boolean

  'set application menu colour
   Dim mi As MENUINFO
   Dim flags As Long
   Dim clrref As Long
  'convert a Windows colour (OLE colour)
  'to a valid RGB colour if required
   clrref = TranslateOLEtoRBG(dwColour)
  'we're changing the background,
  'so at a minimum set this flag
   If bIncludeSubmenus Then
     'MIM_BACKGROUND only changes
     'the back colour of the main
     'menu bar, unless this flag is set
      flags = flags Or MIM_APPLYTOSUBMENUS
   End If

  'fill in struct, assign to menu,
  'and force a redraw with the
  'new attributes
   With mi
      .cbSize = Len(mi)
      .fMask = flags
      .hbrBack = CreateSolidBrush(clrref)
   End With

   SetMenuInfo GetMenu(hwndfrm), mi
   DrawMenuBar hwndfrm

End Function

Private Function SetSysMenuColour(ByVal hwndfrm As Long, _
                                  ByVal dwColour As Long) As Boolean

  'set system menu colour
   Dim mi As MENUINFO
   Dim hSysMenu As Long
   Dim clrref As Long

  'convert a Windows colour (OLE colour)
  'to a valid RGB colour if required
   clrref = TranslateOLEtoRBG(dwColour)

  'get handle to the system menu,
  'fill in struct, assign to menu,
  'and force a redraw with the
  'new attributes
   hSysMenu = GetSystemMenu(Me.hwnd, False)
   With mi
      .cbSize = Len(mi)
      .hbrBack = CreateSolidBrush(clrref)
   End With

   SetMenuInfo hSysMenu, mi
   DrawMenuBar hSysMenu
End Function

Private Function TranslateOLEtoRBG(ByVal dwOleColour As Long) As Long
  'check to see if the passed colour
  'value is and OLE or RGB colour, and
  'if an OLE colour, translate it to
  'a valid RGB color and return. If the
  'colour is already a valid RGB colour,
  'the function returns the colour without
   OleTranslateColor dwOleColour, 0, TranslateOLEtoRBG
End Function
The demo shows how to use the two menu constants - MIM_BACKGROUND and MIM_APPLYTOSUBMENUS - to assign the custom menu colour to either the menu bar alone, or to the menu bar and all submenus on the form. It is also possible through a code tweak to colour each menu separately with its own colour - not something I really advocate in applications, but another good learning demo.

If you inspect the SetMenuColour routine, you'll see the hwnd used in the SetMenuInfo call is that of the form passed to the method as the hwnd parameter. Another API not used - GetSubMenu - can provide the hwnd for each individual dropdown menu when called in conjunction with a 0-based index that represents a menu's physical position along the menu bar (ie File=0, Edit=1, and so on). And, by drilling down even further, each pop out menu under the parent menu can also be identified by its own hwnd if need be.

By passing SetMenuInfo the handle from GetSubMenu you can affect a background colour change to just the specified menu, rather than the entire form menu. The following code will change the menu bar to yellow, the File menu (or first menu on your test form) to cyan, and the second menu (Edit) to green. Any remaining menus will retain VB's default menu colour. Note too that if a dropdown menu has a pop out submenu it will not be coloured unless MIM_APPLYTOSUBMENUS is specified in the flags.

Private Sub Command1_Click()
   Dim mi As MENUINFO
   With mi
      .cbSize = Len(mi)
      .fMask = MIM_BACKGROUND
      .hbrBack = CreateSolidBrush(vbYellow)
       SetMenuInfo GetMenu(Me.hwnd), mi  'main menu bar
      .hbrBack = CreateSolidBrush(vbCyan)
       SetMenuInfo GetSubMenu(GetMenu(Me.hwnd), 0), mi 'File menu (item 0)
      .hbrBack = CreateSolidBrush(vbGreen)
       SetMenuInfo GetSubMenu(GetMenu(Me.hwnd), 1), mi 'Edit menu (item 1)
      .hbrBack = CreateSolidBrush(vbRed)
       SetMenuInfo GetSubMenu(GetMenu(Me.hwnd), 2), mi 'Select menu (item 2)
    End With
   DrawMenuBar Me.hwnd

End Sub


PayPal Link
Make payments with PayPal - it's fast, free and secure!


Copyright 1996-2011 VBnet and Randy Birch. All Rights Reserved.
Terms of Use  |  Your Privacy


Hit Counter