wxPython menu doesn't display image - python

I am creating a menu and assigning images to menu items, sometime first item in menu doesn't display any image, I am not able to find the reason. I have tried to make a simple stand alone example and below is the code which does demonstrates the problem on my machine.
I am using windows XP, wx 2.8.7.1 (msw-unicode)'
import wx
def getBmp():
bmp = wx.EmptyBitmap(16,16)
return bmp
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, style=wx.DEFAULT_FRAME_STYLE, parent=None)
self.SetTitle("why New has no image?")
menuBar = wx.MenuBar()
fileMenu=wx.Menu()
item = fileMenu.Append(wx.ID_NEW, "New")
item.SetBitmap(getBmp())
item = fileMenu.Append(wx.ID_OPEN, "Open")
item.SetBitmap(getBmp())
item = fileMenu.Append(wx.ID_SAVE, "Save")
item.SetBitmap(getBmp())
menuBar.Append(fileMenu, "File")
self.SetMenuBar(menuBar)
app = wx.PySimpleApp()
frame=MyFrame()
frame.Show()
app.SetTopWindow(frame)
app.MainLoop()
So are you able to see the problem and what could be the reason for it?
Conclusion: Yes this is a official bug, I have created a simple Menu class to overcome this bug, using the trick given by "balpha" in selected answer
It overrides each menu.Append method and sees if menu item with image is being added for first time, if yes creates a dummy item and deletes it later.
This also adds feature/constraint so that instead of calling SetBitmap, you should pass bitmap as optional argument image
import wx
class MockMenu(wx.Menu):
"""
A custom menu class in which image param can be passed to each Append method
it also takes care of bug http://trac.wxwidgets.org/ticket/4011
"""
def __init__(self, *args, **kwargs):
wx.Menu.__init__(self, *args, **kwargs)
self._count = 0
def applyBmp(self, unboundMethod, *args, **kwargs):
"""
there is a bug in wxPython so that it will not display first item bitmap
http://trac.wxwidgets.org/ticket/4011
so we keep track and add a dummy before it and delete it after words
may not work if menu has only one item
"""
bmp = None
if 'image' in kwargs:
bmp = kwargs['image']
tempitem = None
# add temp item so it is first item with bmp
if bmp and self._count == 1:
tempitem = wx.Menu.Append(self, -1,"HACK")
tempitem.SetBitmap(bmp)
ret = unboundMethod(self, *args, **kwargs)
if bmp:
ret.SetBitmap(bmp)
# delete temp item
if tempitem is not None:
self.Remove(tempitem.GetId())
self._lastRet = ret
return ret
def Append(self, *args, **kwargs):
return self.applyBmp(wx.Menu.Append, *args, **kwargs)
def AppendCheckItem(self, *args, **kwargs):
return self.applyBmp(wx.Menu.AppendCheckItem, *args, **kwargs)
def AppendMenu(self, *args, **kwargs):
return self.applyBmp(wx.Menu.AppendMenu, *args, **kwargs)

This hack does not appear to be necessary if you create each menu item with wx.MenuItem(), set its bitmap, and only then append it to the menu. This causes the bitmaps to show up correctly. I'm testing with wxPython 2.8.10.1 on Windows.

This is a confirmed bug which appearently has been open for quite a while. After trying around a little bit, this workaround seems to do it:
menuBar = wx.MenuBar()
fileMenu=wx.Menu()
tempitem = fileMenu.Append(-1,"X") # !!!
tempitem.SetBitmap(getBmp()) # !!!
item = fileMenu.Append(wx.ID_NEW, "New")
fileMenu.Remove(tempitem.GetId()) # !!!
item.SetBitmap(getBmp())
item = fileMenu.Append(wx.ID_OPEN, "Open")
item.SetBitmap(getBmp())
item = fileMenu.Append(wx.ID_SAVE, "Save")
item.SetBitmap(getBmp())
menuBar.Append(fileMenu, "File")
self.SetMenuBar(menuBar)
Note that the position of the fileMenu.Remove call is the earliest position that works, but you can also move it to the bottom. HTH.

Related

When I hide a button and unhide the button, another duplicate button is added in tkinter. How to fix this?

Functionality: When I click "Music theory" button, I want it to show another button ("Sight reading" button) and when I click "Music theory" button again, I want the "Sight reading button to hide.
Inside musictheorysubbtns() I'm able to hide the dropdown optionmenu when I click the "Sight reading" button. I tried doing the same for the "music theory button thats outside the function and it added two buttons on the window. I'm able to hide it but when I click "music theory button again, it adds 2 more duplicate buttons, now in total 4 buttons.
How to fix this?
...
tab6 = ttk.Frame(tabControl)
tabControl.add(tab6, text="Music")
tabControl.pack(expand=5, fill='both')
musicbtnsframe = tk.Frame(tab6)
musicbtnsframe.pack(side='top')
mtheorysubbtsframe = tk.Frame(tab6)
mtheorysubbtsframe.pack(anchor='center')
mtdropdownframe = tk.Frame(tab6)
mtdropdownframe.pack(anchor='center')
mrefreshingframe = tk.Frame(tab6)
mrefreshingframe.pack(anchor='center')
def musictheorysubbtns():
def mtheorydrops():
mtheory_opt = mtdropdown
mtheory_opt.config(width=50, font=('Helvetica', 12))
mtheory_opt.pack()
def mtheory_images(mt):
print(mt) # selected option
mt_label.config(image=mtimgz[mt])
mt_label = tk.Label(mtdropdownframe)
mt_label.pack(side = 'bottom', pady=padylength)
mtimgz = {}
for mtimgz_name in tradinglists.retracements:
mtimgz[mtimgz_name] = ImageTk.PhotoImage(Image.open("./Images/{}.png".format(mtimgz_name)))
mtvariable = tk.StringVar(tab2)
mtvariable.set(tradinglists.retracements[0])
mtdropdown = tk.OptionMenu(mtdropdownframe, mtvariable, *tradinglists.retracements, command=mtheory_images)
def refreshmtsub():
mtdropdownframe.pack_forget() if mtdropdownframe.winfo_manager() else mtdropdownframe.pack(anchor='center')
mtheory_k = tk.Button(mtheorysubbtsframe, text="Sight reading", width = artbtn_width, height = btnsize_height, command=lambda:[mtheorydrops(), refreshmtsub()])
mtheory_k.pack(side = 'left', padx=padxwidth, pady=padylength)
def refreshmt():
mtheorysubbtsframe.pack_forget() if mtheorysubbtsframe.winfo_manager() else mtheorysubbtsframe.pack(anchor='center')
theory_k = tk.Button(musicbtnsframe, text="Music theory", width = btnsize_width, height = btnsize_height, command=lambda:[musictheorysubbtns(), refreshmt()])
theory_k.pack(side='left', padx=padxwidth, pady=padylength)
v2:
tab6 = ttk.Frame(tabControl)
tabControl.add(tab6, text="Music")
tabControl.pack(expand=5, fill='both')
class ShowHideButton(tk.Button):
def __init__(self, parent, target_widget, *args, **kwargs):
self.target = target_widget
super().__init__(parent, *args, **kwargs)
self.config(command=self.toggle)
def toggle(self, force_off = False):
if force_off or self.target.winfo_manager():
self.target.pack_forget()
else:
self.target.pack()
if isinstance(self.target, ShowHideButton):
self.target.toggle(force_off=True)
musicbtnsframe = tk.Frame(tab6)
musicbtnsframe.pack(side='top')
mt_sub_frame = tk.Frame(tab6)
mt_sub_frame.pack(side='top')
mt_SRframe = tk.Frame(tab6)
mt_SRframe.pack(anchor='center')
mt_compframe = tk.Frame(tab6)
mt_compframe.pack(anchor='center')
def mt_images(m1t):
print(m1t) # selected option
mt_label.config(image=mtimgz[m1t])
mt_label = tk.Label(mt_SRframe)
mt_label.pack(side = 'bottom', pady=padylength)
mtimgz = {}
for mt_name in musiclists.sightReaing:
mtimgz[mt_name] = ImageTk.PhotoImage(importImageWithResize("./Music_images/Sightreading/{}.png".format(mt_name)))
mtvar = tk.StringVar(tab2)
mtvar.set(musiclists.sightReaing[0])
mt = tk.OptionMenu(mt_SRframe, mtvar, *musiclists.sightReaing, command=mt_images)
mt_opt = mt
mt_opt.config(width=50, font=('Helvetica', 12))
mt_opt.pack()
def mtcomp_images(mtcomp1t):
print(mtcomp1t) # selected option
mtcomp_label.config(image=mtcompimgz[mtcomp1t])
mtcomp_label = tk.Label(mt_compframe)
mtcomp_label.pack(side = 'bottom', pady=padylength)
mtcompimgz = {}
for mtcomp_name in musiclists.composition:
mtcompimgz[mtcomp_name] = ImageTk.PhotoImage(importImageWithResize("./Music_images/Composition/{}.png".format(mtcomp_name)))
mtcompvar = tk.StringVar(tab2)
mtcompvar.set(musiclists.composition[0])
mtcomp = tk.OptionMenu(mt_compframe, mtcompvar, *musiclists.composition, command=mtcomp_images)
mtcomp_opt = mtcomp
mtcomp_opt.config(width=50, font=('Helvetica', 12))
mtcomp_opt.pack()
def mt_sub_btns():
theory_k_sightreading_k = ShowHideButton(mt_sub_frame, mt_opt, text='Sight Reading')
theory_k_composition_k = ShowHideButton(mt_sub_frame, mtcomp_opt, text='Composition')
theory_k = ShowHideButton(musicbtnsframe, mt_sub_btns, text='Music theory')
theory_k.pack(side='left', padx=padxwidth, pady=padylength)
When you want functionality added to a feature in tkinter, it is usually best to customize the widgets to do what you need instead of stringing together functionality. Let me show you what I mean with an example. Since you want one button that show/hides another button, and another button that show/hides an optionmenu, it sounds like it would be best for you to have buttons that show/hide other widgets.
To do this, make your own version of a button:
import tkinter as tk
class ShowHideButton(tk.Button):
def __init__(self, parent, target_widget, *args, **kwargs):
self.target = target_widget
super().__init__(parent, *args, **kwargs)
self.config(command=self.toggle)
The ShowHideButton is our custom name of our new widget. The (tk.Button) says which widget we are basing it off of.
The __init__ method (that's init with two underscores on each side) is what happens right when you make this widget. The parameters we put in there basically all need to be there. self is just required for python reasons, parent and *args, **kwargs are things that tk.Button needs, but target_widget is just something I added to be the target of our show/hide tools.
self.target = target_widget saves the widget we pass through the parameters to the instance of our button.
The line that starts with 'super' makes python run tk.Button's init method so it will build everything that is needed to be a button. (Which is good because I am trying to take tkinter's button widget and adjust it to fit our needs.)
The last line sets the command of the button to a function called self.toggle. Since this happens, you will never need to set the command of our buttons. In fact, we want our buttons to hide/show some other widget, and the whole purpose is to have that functionality built in so it wouldn't make sense to manually set the command.
Put this under the init method to define what self.toggle does:
def toggle(self):
if or self.target.winfo_manager():
self.target.pack_forget()
else:
self.target.pack()
That should be the same indentation level as the init method. You can probably see what this does. It checks if self.target is packed or not and then changes it.
If you use this, you may notice something kind of strange: if you hide a widget, and that widget had opened something, it leaves that something packed. If you would like it to pass down hides, change it to be something like this:
def toggle(self, force_off = False):
if force_off or self.target.winfo_manager():
self.target.pack_forget()
else:
self.target.pack()
if isinstance(self.target, ShowHideButton):
self.target.toggle(force_off=True)
This will check if the target is itself a ShowHideButton and then turn it off as well if it is.
Here is my whole test script to demo the new button:
import tkinter as tk
root = tk.Tk()
class ShowHideButton(tk.Button):
def __init__(self, parent, target_widget, *args, **kwargs):
self.target = target_widget
super().__init__(parent, *args, **kwargs)
self.config(command=self.toggle)
def toggle(self, force_off = False):
if force_off or self.target.winfo_manager():
self.target.pack_forget()
else:
self.target.pack()
if isinstance(self.target, ShowHideButton):
self.target.toggle(force_off=True)
my_option_value = tk.StringVar()
my_option_value.set('opt1')
my_option_menu = tk.OptionMenu(root, my_option_value, 'opt1', 'opt2', 'etc')
s_r_button = ShowHideButton(root, my_option_menu, text='Sight Reading')
m_t_button = ShowHideButton(root, s_r_button, text='Music theory')
m_t_button.pack()
root.mainloop()
You can just copy and paste that class-block into your code and start using ShowHideButtons like they are any other widget. Note the order that I made the buttons in the demo above though. They need to have their targets when you make them so you have to make the ones that are targets before the ones that target them.
This could also be adapted to use grid or place instead of pack. Really, you can modify tkinter widgets in any way you like. Let us know if you have any other questions etc.
EDIT: if you would like a version of this where you can have more than one item being toggled by the button, here you go:
class ShowHideButton(tk.Button):
def __init__(self, parent, target_widgets, *args, **kwargs):
self.targets = target_widgets
super().__init__(parent, *args, **kwargs)
self.config(command=self.toggle)
def toggle(self, force_off = False):
for i in self.targets:
if force_off or i.winfo_manager():
i.pack_forget()
else:
i.pack()
if isinstance(i, ShowHideButton):
self.target.toggle(force_off=True)
Note that you must make target_widgets an iterable item (like a list or tuple) in this case. If you use this version but only want one widget to toggle, you can make a single-length list by surrounding the name of it by [ ].

prevent the sub windows to open multiple times

I am creating an application by using the language wxPython.
I have a simple problem in which I cant really find the solution in the internet.
I have a main user interface with a menubar which contain a menu called new file.
By clicking the new file, a new window will appear demanding the user to fill up the necessary information.
The problem is that, by clicking multiple times the menu (new file), the application opens multiple windows.
How can i prevent this?
The following code creates a new sub frame if one doesn't exists already.
If it does exist already, it uses the existing sub frame.
Note the code is tested with latest wxpython phoenix and classic.
import wx
from wx.lib import sized_controls
class MultiMessageFrame(sized_controls.SizedFrame):
def __init__(self, *args, **kwargs):
super(MultiMessageFrame, self).__init__(*args, **kwargs)
pane = self.GetContentsPane()
text_ctrl = wx.TextCtrl(
pane, style=wx.TE_READONLY | wx.TE_CENTRE | wx.TE_MULTILINE)
text_ctrl.SetSizerProps(proportion=1, expand=True)
text_ctrl.SetBackgroundColour('White')
self.text_ctrl = text_ctrl
pane_btns = sized_controls.SizedPanel(pane)
pane_btns.SetSizerType('horizontal')
pane_btns.SetSizerProps(align='center')
button_ok = wx.Button(pane_btns, wx.ID_OK)
button_ok.Bind(wx.EVT_BUTTON, self.on_button_ok)
def append_msg(self, title_text, msg_text):
self.SetTitle(title_text)
self.text_ctrl.AppendText(msg_text)
def on_button_ok(self, event):
self.Close()
class MainFrame(sized_controls.SizedFrame):
def __init__(self, *args, **kwargs):
super(MainFrame, self).__init__(*args, **kwargs)
self.SetInitialSize((800, 600))
self.CreateStatusBar()
menubar = wx.MenuBar()
self.SetMenuBar(menubar)
menu_file = wx.Menu()
menu_file.Append(
wx.ID_NEW, 'Show msg', 'Add a new message to message frame')
menubar.Append(menu_file, '&File')
self.Bind(wx.EVT_MENU, self.on_new, id=wx.ID_NEW)
self.count = 1
self.multi_message_frame = None
def on_new(self, event):
title_text = 'MultiMessageFrame already exists'
if not self.multi_message_frame:
title_text = 'Newly created MultiMessageFrame'
self.multi_message_frame = MultiMessageFrame(
self, style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT)
self.multi_message_frame.Bind(
wx.EVT_CLOSE, self.on_multi_message_frame_close)
self.multi_message_frame.Center()
self.multi_message_frame.Show()
self.multi_message_frame.append_msg(
title_text, 'message no.{}\n'.format(self.count))
self.count += 1
def on_multi_message_frame_close(self, event):
self.multi_message_frame = None
event.Skip()
if __name__ == '__main__':
app = wx.App(False)
main_frame = MainFrame(None)
main_frame.Show()
app.MainLoop()

Inserted tabs not showing in QTabWidget despite show() called

I have a QTabWidget in PyQt which provides the possibility to tear off tabs and the re-attach them when they are closed. It works well except that newly attached tabs aren't immediately showing. A blank widget is showing and the widget is shown only after the current tab has been changed and then changed back.
I've searched StackOverflow and the answers to similar problems have pointed out that the added widget's show()-method needs to be called after the new tab is added. I tried that but the newly added tab is still not showing.
Slimmed down example code:
from PyQt4 import QtGui, QtCore
class DetachableTabWidget(QtGui.QTabWidget):
""" Subclass of QTabWidget which provides the ability to detach
tabs and making floating windows from them which can be reattached.
"""
def __init__(self, *args, **kwargs):
super(DetachableTabWidget, self).__init__(*args, **kwargs)
self.setTabBar(_DetachableTabBar())
def detach_tab(self, i):
""" Make floating window of tab.
:param i: index of tab to detach
"""
teared_widget = self.widget(i)
widget_name = self.tabText(i)
# Shift index to the left and remove tab.
self.setCurrentIndex(self.currentIndex() - 1 if self.currentIndex() > 0 else 0)
self.removeTab(i)
# Store widgets window-flags and close event.
teared_widget._flags = teared_widget.windowFlags()
teared_widget._close = teared_widget.closeEvent
# Make stand-alone window.
teared_widget.setWindowFlags(QtCore.Qt.Window)
teared_widget.show()
# Redirect windows close-event into reattachment.
teared_widget.closeEvent = lambda event: self.attach_tab(teared_widget, widget_name)
def attach_tab(self, widget, name):
""" Attach widget when receiving signal from child-window.
:param widget: :class:`QtGui.QWidget`
:param name: name of attached widget
"""
widget.setWindowFlags(widget._flags)
widget.closeEvent = widget._close
self.addTab(widget, name)
self.setCurrentWidget(widget)
self.currentWidget().show()
class _DetachableTabBar(QtGui.QTabBar):
def __init__(self, *args, **kwargs):
super(_DetachableTabBar, self).__init__(*args, **kwargs)
self._start_drag_pos = None
self._has_dragged = False
self.setMovable(True)
def mousePressEvent(self, event):
# Keep track of where drag-starts.
self._start_drag_pos = event.globalPos()
super(_DetachableTabBar, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
# If tab is already detached, do nothing.
if self._has_dragged:
return
# Detach-tab if drag in y-direction is large enough.
if abs((self._start_drag_pos - event.globalPos()).y()) >= QtGui.QApplication.startDragDistance()*8:
self._has_dragged = True
self.parent().detach_tab(self.currentIndex())
def mouseReleaseEvent(self, event):
self._has_dragged = False
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = QtGui.QMainWindow()
widget = DetachableTabWidget()
widget.addTab(QtGui.QLabel('Tab 1'), 'tab 1')
widget.addTab(QtGui.QLabel('Tab 2'), 'tab 2')
window.setCentralWidget(widget)
window.show()
sys.exit(app.exec_())
It seems than when you accept the QCloseEvent (default behavior) the widget is hidden at the end of your closeEvent handler. But your call to show() occurs before the end of the handler. The solution is to ignore the QCloseEvent.
...
teared_widget.closeEvent = lambda event: self.attach_tab(teared_widget, widget_name, event)
def attach_tab(self, widget, name, event):
""" Attach widget when receiving signal from child-window.
:param widget: :class:`QtGui.QWidget`
:param name: name of attached widget
:param event: close Event
"""
widget.setWindowFlags(widget._flags)
widget.closeEvent = widget._close
self.addTab(widget, name)
self.setCurrentWidget(widget)
event.ignore()

WxTimer doesn't let the wxFrame close

I am trying to debug this code, the Frame doesn't close on pressing the 'x' button, however when I comment out the wxTimer I am able to close it. Upon googling I found this - http://wiki.wxpython.org/Timer and I tried to bind an event to the top level window however the onClose function is never called on pressing the 'x' button.
Any suggestions?
class matplotsink(wx.Panel):
def __init__(self, parent, title, queue):
# wx.Frame.__init__(self, parent, -1, title)
wx.Panel.__init__(self, parent, wx.SIMPLE_BORDER)
#self.datagen = DataGen()
#self.data = self.datagen.next()
self.data = []
self.parent = parent
self.title = title
self.queue = queue
self.paused = False
#self.create_menu()
#self.create_status_bar()
self.toplevelcontainer = wx.GetApp().GetTopWindow()
self.toplevelcontainer.CreateStatusBar()
print 'Hey'
# menuBar = wx.MenuBar()
# fileMenu = wx.Menu()
# fileMenu.Append(wx.ID_NEW, '&New')
# fileMenu.Append(wx.ID_OPEN, '&Open')
# fileMenu.Append(wx.ID_SAVE, '&Save')
# menuBar.Append(fileMenu, '&File')
# self.toplevelcontainer.SetMenuBar(menuBar)
self.toplevelcontainer.Bind(wx.EVT_CLOSE, self.onCloseFrame)
self.create_main_panel()
self.redraw_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.draw_callback, self.redraw_timer)
self.redraw_timer.Start(100)
def onCloseFrame(self,event):
print 'Hey1'
self.redraw_timer.Stop()
self.toplevelcontainer.Destroy()
self.toplevelcontainer.Close()
I cannot see that onCLoseFrame() does not get called. Quite to the contrary, self.toplevelcontainer.Destroy() retriggers EVT_CLOSE ad infinitum until the maximum recursion depth is reached. This is the reason self.toplevelcontainer never gets closed.
Instead of trying to destroy the top level window yourself, let the event handler do its job by skipping after you are done with the cleanup:
def onCloseFrame(self, event):
# ...
self.redraw_timer.Stop() #cleanup, important!
event.Skip()
You can check this stackoverflow answer (the link in the answer) for an explanation. As I have seen, the particular wxPython-wiki entry for wx.Timer is not very useful.

passing variables from one class to another in wxpython

I have a problem with passing textCtrl data from one class to another in wxpython. I tried using the instance method of passing variables but if I use init_function it is only relevant at the start of the program and doesn't take into account any changes to the text control box after the initial start. Tried the Update() or Refresh() and it didn't work either.
Here is the code simplified.
class DropTarget(wx.DropTarget):
def __init__(self,textCtrl, *args, **kwargs):
super(DropTarget, self).__init__( *args, **kwargs)
self.tc2=kwargs["tc2"]
print self.tc2
class Frame(wx.Frame):
def __init__(self, parent, tc2):
self.tc2 = wx.TextCtrl(self, -1, size=(100, -1),pos = (170,60))#part number
def main():
ex = wx.App()
frame = Frame(None, None)
frame.Show()
b = DropTarget(None, kwarg['tc2'])
ex.MainLoop()
if __name__ == '__main__':
main()
The following way of passing the variable gives me a keyerror. Any help is appreciated.
This isn't the most elegant solution to the problem, but I had a similar issue. If you dump your text to a temporary text file, you can pick it back up whenever you want. So it would be something like this:
tmpFile = open("temp.txt",'w')
tmpFile.write(self.tc2.GetValue())
tmpFile.close()
#when you want the string back in the next class
tmpFile = open("temp.txt",'r')
string = tmpFile.read()
tmpFile.close()
os.system("del temp.txt") #This just removes the file to clean it up, you'll need to import os if you want to do this
import wx
class DropTarget(wx.DropTarget):
def __init__(self,textCtrl, *args, **kwargs):
self.tc2 = kwargs.pop('tc2',None) #remove tc2 as it is not a valid keyword for wx.DropTarget
super(DropTarget, self).__init__( *args, **kwargs)
print self.tc2
class Frame(wx.Frame):
def __init__(self, parent, tc2):
#initialize the frame
super(Frame,self).__init__(None,-1,"some title")
self.tc2 = wx.TextCtrl(self, -1, size=(100, -1),pos = (170,60))#part number
def main():
ex = wx.App(redirect=False)
frame = Frame(None, None)
frame.Show()
#this is how you pass a keyword argument
b = DropTarget(frame,tc2="something")
ex.MainLoop()
if __name__ == '__main__':
main()
there were at least a few errors in your code ... it at least makes the frame now

Categories

Resources