Windows: is there a way to force a menu width to be updated?

117 views
Skip to first unread message

dhyams

unread,
Jan 5, 2013, 1:08:17 PM1/5/13
to wxpytho...@googlegroups.com
I have a undo/redo mechanism, so when the user clicks the "Edit" menu, I want to display something like "Undo move", or "Undo resize" or something as appropriate for the action that is to be undone.

So I'm using wx.EVT_UPDATE_UI, and calling evt.SetText() appropriately.  That parts works just fine.

The problem, and it's windows only, is that Windows apparently only calculates the menu width once ever.  So, let's say that the first time the user opens the menu, it says "Undo move", and the second time the user opens the menu, it says "Undo your last thing".  The second time the menu opens, the menu still has the same width as it did on the first, so the "Undo your last thing" runs into the accelerator key text ("Ctrl+Z").

I have messed with this for hours and dug into the wxWidgets source, but I still don't see a workaround.  Does anyone have any suggestions?

I have a couple of pictures attached to illustrate.
menu1.png
menu2.png

dhyams

unread,
Jan 7, 2013, 9:42:07 PM1/7/13
to wxpytho...@googlegroups.com
Oops, forgot to mention this is wxPython 2.9.1.1.

Vlastimil Brom

unread,
Jan 8, 2013, 10:23:01 AM1/8/13
to wxpytho...@googlegroups.com
2013/1/5 dhyams <dhy...@gmail.com>:
> --
> To unsubscribe, send email to wxPython-user...@googlegroups.com
> or visit http://groups.google.com/group/wxPython-users?hl=en

Hi,
as your gui looks rather complex and fine tuned, I am not sure the
following trivial approach would be applicable,
but simply changing the respective label seems to work for me (python
2.7, wxPython 2.9; win 7, not sure for others). In this simplified
sample the menu item is referenced globally; you could likely use
menu.FindItemById( ... ) etc.

hth,
vbr

##################################################
#! Python
# -*- coding: utf-8 -*-

import wx
import random

def OnMenuOpen(evt):
undo_menu_item.SetText(u"&Undo %s\tCtrl+Z" % (random.choice(["x",
"xyz", "xxxyyyzz", "xxxxxxyyzzzzzzzzz",
"xxxxxxxxxyyyyyyyyyyyyyzzzzzzzzzzzzzzzzzzzzzzz"])))

def OnUndo(evt):
txt_field.SetValue(undo_menu_item.GetText())

app = wx.App()
frm = wx.Frame(None, -1, u"test menu label")
txt_field = wx.TextCtrl(frm, 1, style=wx.TE_MULTILINE)
frm.CreateStatusBar()

test_menu= wx.Menu()
undo_menu_item = test_menu.Append(-1, "&Undo\tCtrl+Z"," Undo last item")
dummy_menu_item = test_menu.Append(-1, "&Dummy\tCtrl+Z"," Dummy menu item 1")

menu_bar = wx.MenuBar()
menu_bar.Append(test_menu,"&Testmenu")
frm.SetMenuBar(menu_bar)

frm.Bind(wx.EVT_MENU_OPEN, OnMenuOpen)
frm.Bind(wx.EVT_MENU, OnUndo, undo_menu_item)

frm.Show(True)
app.MainLoop()

##################################################
wx_test_menu.py

dhyams

unread,
Jan 8, 2013, 11:13:42 AM1/8/13
to wxpytho...@googlegroups.com
vbr:

Thanks so much for your reply...it helped a lot!  I took your sample and slightly modified it...basically, all I did was add a bitmap to the menu item.  As soon as the bitmap is added, the menu width refuses to change width wider than the first invocation, leading to the problem that I posted about. 

Somehow, adding a bitmap anywhere in the menu is freezing its width.

I have attached the sample code and the one bitmap that needs to go with it...just put both in the same directory and run.
edit-undo.png
wx_test_menu_with_bitmap.py
menu1.png
menu2.png
menu3.png

Vlastimil Brom

unread,
Jan 8, 2013, 4:00:33 PM1/8/13
to wxpytho...@googlegroups.com
2013/1/8 dhyams <dhy...@gmail.com>:
>> [...]
>

Hi,
just another take... - it looks quite clumsy, but deleting and
reinserting the respective menuitem seems to work.
I'd rather expect some drawbacks of this approach, but maybe you could
elaborate this trick and test it in your program.
I am not sure, whether the repeated event binding could cause problems
(hopefully the handlers are unbound on deleting the menuitem, but it
could possibly be done explicitely too.

hth,
vbr

##################################################
#! Python
# -*- coding: utf-8 -*-

import wx

words = "x xxxxxxxxxx xxxxxxxxxxxxxxxxxxxx".split()
indx = 0

def OnMenuOpen(evt):
global indx
global undo_menu_item
test_menu.DeleteItem(undo_menu_item)
undo_menu_item = wx.MenuItem(test_menu,-1,"&Undo\tCtrl+Z"," Undo last item")
test_menu.InsertItem(0, undo_menu_item)
undo_menu_item.SetItemLabel(u"&Undo %s\tCtrl+Z" % (words[indx%3]))
undo_menu_item.SetBitmap(wx.Bitmap("edit-undo.png",wx.BITMAP_TYPE_PNG))
frm.Bind(wx.EVT_MENU, OnUndo, undo_menu_item)
indx += 1

def OnUndo(evt):
txt_field.SetValue(undo_menu_item.GetText())

app = wx.App()
frm = wx.Frame(None, -1, u"test menu label")
txt_field = wx.TextCtrl(frm, 1, style=wx.TE_MULTILINE)
frm.CreateStatusBar()

test_menu= wx.Menu()
undo_menu_item = wx.MenuItem(test_menu,-1,"&Undo\tCtrl+Z"," Undo last item")
undo_menu_item.SetBitmap(wx.Bitmap("edit-undo.png",wx.BITMAP_TYPE_PNG))
dummy_menu_item = wx.MenuItem(test_menu,-1,"&Dummy\tCtrl+D"," Dummy
menu item 1")

test_menu.AppendItem(undo_menu_item)
test_menu.AppendItem(dummy_menu_item)

dhyams

unread,
Jan 8, 2013, 10:26:06 PM1/8/13
to wxpytho...@googlegroups.com
Certainly this does work!  There has to be a "OnMeasureItem" missing in wx somewhere, or so it seems.  When I get a moment, I am going to dive into the wxWidgets source and see if I can locate the cause of the problem. The first hurdle is compiling the beast under Windows though ;)

Here is what I ended up with.  Those with weak stomachs might want to avert their eyes.  I'm amazed that this works without crashing...a menu item is getting ripped out and replaced just as the menu is opened, and UPDATE_UI messages follow closely behind to actually set the menu text.

def OnMenuOpen(self,evt):
       menu =  evt.GetMenu()
       edit_menu = self.menubar.GetMenu(1) # hardcoded.  The Edit menu is the second one.
       if menu is edit_menu:
           
           for idx,name in enumerate(["Undo","Redo"]):
           
               menu_item = menu.FindItemByPosition(idx) 
               menu_item_id = menu_item.GetId() # must keep the same ID, or accelerator table doesn't work any more.
               
               self.Unbind(wx.EVT_MENU     ,menu_item)
               self.Unbind(wx.EVT_UPDATE_UI,menu_item)
            
               menu.DeleteItem(menu_item)
               menu_item = wx.MenuItem(menu,menu_item_id,name)  
               menu.InsertItem(idx, menu_item)   
               
               menu_item.SetBitmap(wx.Bitmap("edit-%s.png"%name.lower(),wx.BITMAP_TYPE_PNG))
               
               handler = getattr(self,'On%s'%name)
               uihandler = getattr(self,'Can%s'%name)
               self.Bind(wx.EVT_MENU     ,handler,menu_item) 
               self.Bind(wx.EVT_UPDATE_UI,uihandler,menu_item)
           
       evt.Skip()

dhyams

unread,
Jan 9, 2013, 9:16:32 AM1/9/13
to wxpytho...@googlegroups.com
I think that I've found the proper fix for this.  In wxwidgets, file src/msw/menuitem.cpp, routine wxMenuItem::SetItemLabel.

If the menu item is set to owner drawn, there is a short circuit out of this routine:

    if ( IsOwnerDrawn() )
    {
        // we don't need to do anything for owner drawn items, they will redraw
        // themselves using the new text the next time they're displayed
        return;
    }

But we can't allow this.....even though there is technically nothing to be done in SetItemLabel, the Windows API (SetMenuItemInfo) below still need to be called, so that Windows knows that the name has been changed and therefore later sends WM_MEASUREITEM later therefore triggers the OnMeasureItem() handler, which appropriately recalculates the menu's width.

So if I just remove the short-circuit, everything works just fine, both in the test codes in this thread, and in my application.  Personally I see no possible harm in removing the short-circuit, but perhaps others who know more than I can comment.

BTW, thanks Robin for making wxPython so easy to build under Windows.

dhyams

unread,
Jan 9, 2013, 9:35:32 AM1/9/13
to wxpytho...@googlegroups.com
A-ha!  From six years ago...

Robin Dunn

unread,
Jan 9, 2013, 10:47:42 AM1/9/13
to wxpytho...@googlegroups.com
On 1/9/13 8:35 AM, dhyams wrote:
> A-ha! From six years ago...
>
> http://trac.wxwidgets.org/ticket/3897
>

I was just about to reply and say be sure to create a ticket for this,
I'm glad you found the old one instead, thanks for taking the time to do
that.

--
Robin Dunn
Software Craftsman
http://wxPython.org

Reply all
Reply to author
Forward
0 new messages