Welcome, guest | Sign In | My Account | Store | Cart

Using win32 API to change the background color of the menubar/menu items in a wxPython app without affecting system-wide settings.

Python, 103 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
# This script demonstrates changing the color of the menu bar/individual
# menus in a wxPython UI for the Win32 platform
# Requires
# - ctypes to access the user32 and gdi32 modules, to set up structures
#   and other win32 oriented helper functions
# - pywin32 module, to use win32con and access constants (this is optional
#   but really handy). Alternative is to get the values of the constants
#   from MSDN.

import wx
import win32con
from ctypes import *
import sys

# Structure passed to CreateSolidBrush function
# Represents RGB
class COLORREF(Structure):
    _fields_ = [
    ("byRed", c_byte),
    ("byGreen", c_byte),
    ("byBlue", c_byte)
    ]

# Menu structure used in calls to SetMenuInfo
class MENUINFO(Structure):
    _fields_ = [
    ("cbSize", c_long),
    ("fMask", c_long),
    ("dwStyle", c_long),
    ('cyMax', c_long),
    ("hbrBack", c_long),
    ("dwContextHelpID", c_long),
    ("dwMenuData", c_long)
    ]

class TestFrame(wx.Frame):
    """
    Subclass of wx.Frame that presents the app's main window
    """
    def __init__(self, parent, id=-1, title='Test Menu',
            pos=wx.DefaultPosition,
            size=wx.DefaultSize, 
            style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE,
            name='TestFrame',
            shadesubmenus=False):
        wx.Frame.__init__(self, parent, id, title, pos, size, style, name)
        self.bShadeSubMenus = shadesubmenus
        menubar = wx.MenuBar()
        menu1 = wx.Menu()
        menu1.Append(-1, 'Open', 'Open new file')
        menu1.Append(-1, 'Exit', 'Quit application')
        menubar.Append(menu1, 'File')
        menu2 = wx.Menu()
        menu2.Append(-1, 'About', 'About')
        menubar.Append(menu2, 'Help')
        self.SetMenuBar(menubar)
        self.Show(True)
        self.size = self.GetSize()
        # Get my windows handle - hwnd
        self.hwnd = self.GetHandle()
        self.ChangeMenuBarColor()

    def ChangeMenuBarColor(self):
        """
        Changes the background color of the menubar and optionally gives 
        different colors to menu items
        """
        user32 = windll.user32
        DrawMenuBar = user32.DrawMenuBar
        GetMenu = user32.GetMenu
        GetSubMenu = user32.GetSubMenu
        GetSystemMenu = user32.GetSystemMenu
        SetMenuInfo = user32.SetMenuInfo
        GetMenuInfo = user32.GetMenuInfo
        gdi32 = windll.gdi32
        CreateSolidBrush = gdi32.CreateSolidBrush
        # Instantiate MENUINFO
        menuinfo = MENUINFO()
        # Important to set the size
        menuinfo.cbSize = sizeof(MENUINFO)
        menuinfo.fMask = win32con.MIM_BACKGROUND
        if not self.bShadeSubMenus:
            menuinfo.fMask |= win32con.MIM_APPLYTOSUBMENUS
        menuinfo.hbrBack = CreateSolidBrush(COLORREF(255, 0, 0))
        # Important! Pass *pointer* of the menuinfo instance to the win32 call
        SetMenuInfo(GetMenu(self.hwnd), pointer(menuinfo))
        if self.bShadeSubMenus:
            menuinfo.fMask = win32con.MIM_BACKGROUND | win32con.MIM_APPLYTOSUBMENUS
            menuinfo.hbrBack = CreateSolidBrush(COLORREF(255, 255, 0))
            SetMenuInfo(GetSubMenu(GetMenu(self.hwnd), 0), pointer(menuinfo))
            menuinfo.fMask = win32con.MIM_BACKGROUND | win32con.MIM_APPLYTOSUBMENUS
            menuinfo.hbrBack = CreateSolidBrush(COLORREF(128, 255, 128))
            SetMenuInfo(GetSubMenu(GetMenu(self.hwnd), 1), pointer(menuinfo))
        DrawMenuBar(self.hwnd)

if __name__ == '__main__':
    try:
        bShadeSubMenus = sys.argv[1]
    except:
        bShadeSubMenus = False
    app = wx.PySimpleApp()
    f = TestFrame(None, shadesubmenus=bShadeSubMenus)
    app.MainLoop()

The motivation for this came about from a question posted on the wxpython-users mailing list where someone wanted to create a themed window in WinXP but was unable to change the background color of the menubar.

The background of the menubar can not be set using SetBackgroundColour() in wxPython (or at least did not work for me!). The solution I came up with to set the background color is by using Win32 API calls. The calls operate on the Windows handle and the handle of the wxPython frame is got using GetHandle().

Key API calls 1. GetMenu - gets menu handle for the given window handle. 2. GetSubMenu - gets submenu handle, given the menu handle and the 0-based index of the the submenu. In the example above, 0 = File Menu, 1 = Help Menu. 3. CreateSolidBrush - Create a COLORREF (RGB) based brush. 4. SetMenuInfo - Sets the menu characteristics based on a filled-in MENUINFO structure. 5. DrawMenuBar - Refreshes the menu bar, after setting the menu's information structure.

To pass a structure to a Win23 call, use ctypes module and subclass ctypes.Structure. SetMenuInfo takes a pointer to a MENUINFO structure, which can be created using the ctypes.pointer() function.

The fMask struct member is important in telling SetMenuInfo what to do to the Menu whose handle is passed. To change the background, use the flag MIM_BACKGROUND. Another interesting flag is the MIM_APPLYTOSUBMENUS. which tells SetMenuInfo to alter the info for the submenus as well. The actual color is set in the hbrBack member which holds an RGB brush color used in painting the background.

Additionally, colors of individual menus can also be set. In the code above, depending on the bShadeSubMenus, either the entire menubar along with menus is red or the menubar is red, with the file menu in yellow and help menu, greenish . The key to make this happen is setting the MIM_APPLYTOSUBMENUS - setting it in the first call makes all menus the same color (red in this case). Not setting it in fMask allows us to change the colors of individual submenus.

For more information on the API, refer MSDN for SetMenuInfo and other API functions used above.