I would like to have two (I will add more later) panels that occupy the same space within the frame and for them to be shown/hidden when the respective button is pressed on the toolbar, "mListPanel" should be the default. Currently the settings panel is shown when the application is launched and the buttons don't do anything. I've searched and tried lots of stuff for hours and still can't get it to work. I apologise if it's something simple, I've only started learning python today.
This is what the code looks like now:
import wx
class mListPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent)
#wx.StaticText(self, -1, label='Search:')#, pos=(10, 3))
#wx.TextCtrl(self, pos=(10, 10), size=(250, 50))
class settingsPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent)
class bifr(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Title")
self.listPanel = mListPanel(self)
self.optPanel = settingsPanel(self)
menuBar = wx.MenuBar()
fileButton = wx.Menu()
importItem = wx.Menu()
fileButton.AppendMenu(wx.ID_ADD, 'Add M', importItem)
importItem.Append(wx.ID_ANY, 'Import from computer')
importItem.Append(wx.ID_ANY, 'Import from the internet')
exitItem = fileButton.Append(wx.ID_EXIT, 'Exit')
menuBar.Append(fileButton, 'File')
self.SetMenuBar(menuBar)
self.Bind(wx.EVT_MENU, self.Quit, exitItem)
toolBar = self.CreateToolBar()
homeToolButton = toolBar.AddLabelTool(wx.ID_ANY, 'Home', wx.Bitmap('icons/home_icon&32.png'))
importLocalToolButton = toolBar.AddLabelTool(wx.ID_ANY, 'Import from computer', wx.Bitmap('icons/comp_icon&32.png'))
importToolButton = toolBar.AddLabelTool(wx.ID_ANY, 'Import from the internet', wx.Bitmap('icons/arrow_bottom_icon&32.png'))
settingsToolButton = toolBar.AddLabelTool(wx.ID_ANY, 'settings', wx.Bitmap('icons/wrench_plus_2_icon&32.png'))
toolBar.Realize()
self.Bind(wx.EVT_TOOL, self.switchPanels(), settingsToolButton)
self.Bind(wx.EVT_TOOL, self.switchPanels(), homeToolButton)
self.Layout()
def switchPanels(self):
if self.optPanel.IsShown():
self.optPanel.Hide()
self.listPanel.Show()
self.SetTitle("Home")
elif self.listPanel.IsShown():
self.listPanel.Hide()
self.optPanel.Show()
self.SetTitle("Settings")
else:
self.SetTitle("Error")
self.Layout()
def Quit(self, e):
self.Close()
if __name__ == "__main__":
app = wx.App(False)
frame = bifr()
frame.Show()
app.MainLoop()
first off, i would highly suggest that you learn about wxpython sizers and get a good understanding of them (they're really not that hard the understand) as soon as possible before delving deeper into wxpython, just a friendly tip :).
as for your example, a few things:
when your'e not using sizers, you have to give size and position for every window or else they just wont show, so you'd have to change your panel classes to something like this (again this is only for demonstration, you should be doing this with wx.sizers, and not position and size):
class mListPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent,pos=(0,100),size=(500,500))
class settingsPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent,pos=(0,200),size (1000,1000))
further more, when binding an event it should look like this:
self.Bind(wx.EVT_TOOL, self.switchPanels, settingsToolButton)
self.Bind(wx.EVT_TOOL, self.switchPanels, homeToolButton)
notice how I've written only the name of the function without the added (), as an event is passed to it, you cant enter your own parameters to a function emitted from an event (unless you do it with the following syntax lambda e:FooEventHandler(paramaters))
and the event handler (function) should look like this:
def switchPanels(self, event):
if self.optPanel.IsShown():
self.optPanel.Hide()
self.listPanel.Show()
self.SetTitle("Home")
elif self.listPanel.IsShown():
self.listPanel.Hide()
self.optPanel.Show()
self.SetTitle("Settings")
else:
self.SetTitle("Error")
self.Layout()
there should always be a second parameter next to self in functions that are bind to event as the event object is passes there, and you can find its associated methods and parameters in the documentation (in this example it is the wx.EVT_TOOL).
Related
I am writing a program to play a video on the right panel when a button pressed on a left panel after selecting what to play. This acts as a test function to show the user. I am a beginner in using Python and WXPython. Learning on the go.
I have added a snippet of the code that I am stuck on below:
import wx, wx.media
filePathList = ["None", "None", "None", "None", "None"]
class FrameClass (wx.Frame):
def __init__(self, parent):
super(FrameClass, self).__init__(None, title = "Super Bot", size = (750, 400))
vsplitter = wx.SplitterWindow(self)
left = LeftPanel(vsplitter, self)
self.right = RightPanel(vsplitter, self)
vsplitter.SplitVertically(left, self.right)
vsplitter.SetMinimumPaneSize(200)
self.Show(True)
class LeftPanel (wx.Panel):
def __init__(self, parent, *args, **kwargs):
wx.Panel.__init__(self, parent = parent)
testBtn1 = wx.Button(self, -1, "Test", pos = (5, 20))
self.Bind(wx.EVT_BUTTON, self.buttonPressed1, testBtn1)
def buttonPressed1(self, event):
file0 = filePathList[0]
self.right.onTestClick(file0)
class RightPanel (wx.Panel):
def __init__(self, parent, media):
wx.Panel.__init__(self, parent = parent)
self.mediaFilePath = media
def onTestClick(self):
self.testMedia = wx.media.MediaCtrl(self, size = (500, 300), style=wx.SIMPLE_BORDER, szBackend = wx.media.MEDIABACKEND_WMP10)
self.testMedia.Bind(wx.media.EVT_MEDIA_LOADED, self.play)
self.testMedia.Load(self.mediaFilePath)
def play(self, event):
self.testMedia.Play()
Currently all works well. What apart from passing the video to the rightPanel video onTestClick. Where is shows the current error
Traceback (most recent call last):
File "frame1.py", line 151, in buttonPressed1
self.right.onTestClick(file0)
AttributeError: 'LeftPanel' object has no attribute 'right'
I can imagine that because right is defined in the FrameClass that it is not known about inside of the LeftPanel when trying to use it.
Any help would be appreciated.
If you want a quick fix, you can just call:
self.GetParent().right.onTestClick(file0)
which is kind-of ugly, because the the parent creates children
and children must know about the structure of the parent at
the same time.
Probably the most wxPython-ish solution would be to create your
own event, which would be created and triggered when the button
is pressed. This event would be handled in the FrameClass.
There is a nice intro here:
https://wxpython.org/Phoenix/docs/html/events_overview.html#custom-event-summary
Finally got it working. Thank you for your inputs as well. Most helpful.
I just needed to make the left panel aware of the right panel. In this case...
vsplitter = wx.SplitterWindow(self)
right = RightPanel(vsplitter, self)
left = LeftPanel(vsplitter, right)
vsplitter.SplitVertically(left, right)
vsplitter.SetMinimumPaneSize(200)
self.Show(True)
class LeftPanel (wx.Panel):
def __init__(self, parent, top):
wx.Panel.__init__(self, parent = parent)
self.refTop = top
and when I wanted to send it something, I reference "self.refTop". Such as...
def buttonPressed1(self, event):
file1 = filePathList[0]
self.refTop.onTestClick(file1)
making sure that RightPanel has an argument ready for it.
class RightPanel (wx.Panel):
def __init__(self, parent, media):
self.mediaFilePath = media
I so far failed to create what is colloquially called an "info icon" with wxPython. An icon with some sort of 'i' image that shows a large tooltip on hover.
I can add a wx.StaticBitmap for the image but it ignores all SetToolTipString or SetToolTip(wx.ToolTip()) calls. OR I can add a large tool tip to a wx.StaticText as shown below.
Ignore that the icon doesn't have the correct size yet.
Needless to say that eventually the tooltip needs a background color that is different from the panel background color (not the focus here). I can't use wx.adv.RichToolTip because I'm on wxPython 3.0.2.0 osx-cocoa.
What is a good way to solve this?
If you create a button with an ID of wx.ID_HELP then you'll get the stock help button for the platform, if it has one. Then you can do whatever you want with it like any button. Assign a tooltip, do something in the EVT_BUTTON event, etc. See the StockButtons sample in the demo. If the stock image or label doesn't meet your needs then you can probably just use a wx.BitmapButton to show the image you want and still have the standard tooltip support.
Something else you may want to look into is the ContextHelp sample in the demo. It shows how to use a wx.ContextHelpButton which, when clicked, puts the application into context-help mode. A popup tip window will then be shown for whatever widget is clicked on next. Not quite what you are asking for, but it might be a good fit.
wxArtProvider may be able to help http://docs.wxwidgets.org/trunk/classwx_art_provider.html
import wx
class Test(wx.Frame):
def __init__(self,parent,msg,title):
wx.Frame.__init__(self, None)
self.panel = wx.Panel(self, size=(300,400))
mainSizer = wx.BoxSizer(wx.HORIZONTAL)
staticIcon = wx.BitmapButton(self.panel, bitmap=wx.ArtProvider.GetBitmap(wx.ART_WARNING), size=(32,32))
mainSizer.Add(staticIcon, flag=wx.ALL, border=10)
ttip = "xxxxxxxxxxxxxxx\n"
ttip += "xxxxxxxxxxxxxxxxxxxxxxxxxx\n"
ttip += "xxxxxxxxxxxxxxxxxxxxxxxxxxx\n"
ttip += "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
staticIcon.SetToolTipString(ttip)
buttonText = wx.StaticText(self.panel, -1, msg, wx.DefaultPosition, wx.DefaultSize, 0)
mainSizer.Add(buttonText, flag=wx.ALL, border=10)
staticIcon.Bind(wx.EVT_BUTTON, self.OnButton)
self.SetSizer(mainSizer)
self.Show()
def OnButton(self, evt):
print "The button was pressed - display some help"
if __name__ == '__main__':
app = wx.App()
Test(None, "Dummy Exercise", "Test 123")
app.MainLoop()
If all you want to do is show a tooltip when the image is moused over, then you need to bind your instance of the wx.StaticBitmap to EVT_MOTION:
import wx
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
bmp = wx.ArtProvider.GetBitmap(wx.ART_WARNING)
self.image = wx.StaticBitmap(self, bitmap=bmp)
self.image.Bind(wx.EVT_MOTION, self.on_mouse_over)
def on_mouse_over(self, event):
self.image.SetToolTipString('BLAH BLAH BLAH')
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Icon Mouser')
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
When I run this code, I get something like this:
I am programming an application using wxpython and wx.lib.pubsub. in python 2.7.3
1- There is a Frame with a menu item. When this menu is clicked, a message is published by pubsub.
2- This message destroys (if possible) and creates a "first level" dialogue.
3- "First Level" dialogue has an list of valules and an "add value" button. (NOTE: Such list of variables can be modified so I am trying to update this list)
4- When the "add value" button is clicked, another message is published by pubsub.
5- This message creates a "Second Level" dialogue, so a new name for the new variable can be written.
6- There is a "continue" button in this "second level" dialogue which has two consequences:
First one: Self.Destroy();
Second one: goes to step 2, i.e. destroys the "first level" dialogue and creates it again.
To that point the program seems to work fine, however, when I finish "adding" variables to the "first level" dialogue I Destroy it and then I cannot go back to the main Frame stated in step 1.
Why is this happening?
All the Dialogues are shown via ShowModal(). However if I use only Show() it seems to work fine but, since the program has many menus and items, ShowModal() is preferred.
Any idea why it works with Show() but not with ShowModal()?
If there is a simpler way to perform the task I want to do, it would be appreciated.
import wx
from wx.lib.pubsub import Publisher as pub
class itemReceiver(object):
def __init__(self):
pub.subscribe(self.__OnShowDialog, 'show.dialog')
def __OnShowDialog(self, message):
self.dlgParent = message.data[0]
print str(self.dlgParent)
self.valuesToShow = message.data[1]
print self.valuesToShow
#try to destroy dialog before creating a new one
try:
self.manageParametersDialog.Destroy()
except:
pass
self.manageParametersDialog = manageParamsDialog(self.dlgParent, self.valuesToShow)
print "ready to show first level dialogue"
self.manageParametersDialog.ShowModal() #if .Show() instead, there is no problem
class secondaryReceiver(object):
def __init__(self):
pub.subscribe(self.__OnShowDialog, 'add.item')
def __OnShowDialog(self, message):
dlgParent = message.data[0]
dlgGrandParent = message.data[1]
self.variableList = message.data[2]
editParameterDialog = editParamDlg(dlgParent, dlgGrandParent, self.variableList)
editParameterDialog.ShowModal()
class manageParamsDialog (wx.Dialog):
def __init__(self, parent, valueList):
self.valueList = valueList
self.parent = parent
wx.Dialog.__init__(self, parent, -1, "first level dialogue", style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
sizer=wx.BoxSizer(wx.VERTICAL)
self.optionList = wx.ListBox(self, -1, size=(200, 70), choices = valueList)
sizer.Add(self.optionList)
addButton = wx.Button(self, -1, 'Add New')
self.Bind(wx.EVT_BUTTON, self.OnButton, addButton)
sizer.Add(addButton)
cancelButton = wx.Button(self, -1, 'Cancel')
self.Bind(wx.EVT_BUTTON, self.OnCancel, cancelButton)
sizer.Add(cancelButton)
self.SetSizer(sizer)
self.Fit()
def OnButton (self, e):
pub.sendMessage('add.item', [self, self.parent, self.valueList])
def OnCancel(self,e):
self.Destroy()
class editParamDlg(wx.Dialog):
def __init__(self, parent, grandParent, variableList):
self.variableList = variableList
self.grandParent = grandParent
wx.Dialog.__init__(self, parent, -1, "second level dialogue", style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
hboxSizer = wx.BoxSizer(wx.HORIZONTAL)
self.textInput = wx.TextCtrl(self, -1)
hboxSizer.Add(self.textInput)
addButton = wx.Button(self, -1, 'Continue')
self.Bind(wx.EVT_BUTTON, self.OnAdd, addButton)
hboxSizer.Add(addButton)
cancelButton = wx.Button(self, -1, 'Cancel')
self.Bind(wx.EVT_BUTTON, self.OnCancel, cancelButton)
hboxSizer.Add(cancelButton)
self.SetSizer(hboxSizer)
self.Fit()
def OnAdd(self, e):
self.variableList.append(self.textInput.GetValue())
self.Destroy()
pub.sendMessage('show.dialog',[self.grandParent, self.variableList])
def OnCancel(self,e):
self.Destroy()
class ToolbarFrame(wx.Frame):
#this ToolbarFrame is the main window, with a Toolbar and a white panel below.
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, "this is a frame", size=(480, 320))
myPanel = wx.Panel(self)
myPanel.SetBackgroundColour("White")
menuBar = wx.MenuBar()
fileMenu = wx.Menu()
menuItem = wx.MenuItem(fileMenu, -1, "menu item", "opens dialog via pubsub")
self.Bind(wx.EVT_MENU, self.OnMenuItem, menuItem)
fileMenu.AppendItem(menuItem)
menuBar.Append(fileMenu, "File")
self.SetMenuBar(menuBar)
def OnMenuItem(self, e):
pub.sendMessage('show.dialog', [self, ["one", "two", "three"]])
app = wx.PySimpleApp()
frame = ToolbarFrame(parent=None, id=-1)
frame.Show()
newItemListener = itemReceiver()
editParameterListener = secondaryReceiver()
app.MainLoop()
try changing secondaryReciever as follows
class secondaryReceiver(object):
def __init__(self):
pub.subscribe(self.__OnShowDialog, 'add.item')
def __OnShowDialog(self, message):
dlgParent = message.data[0]
dlgGrandParent = message.data[1]
self.variableList = message.data[2]
editParameterDialog = editParamDlg(dlgParent, dlgGrandParent, self.variableList)
editParameterDialog.ShowModal()
#this line will not execute till the dialog closes
self.dlgParent.optionList.SetItems(editParameterDialog.variableList)
editParameterDialog.Destroy()
and also change editParamDlg
def OnAdd(self, e):
self.variableList.append(self.textInput.GetValue())
self.Close()
the problem was that you would call the show.modal from that OnAdd ... which would try to destroy the existing window and then open a new one... but the old one wasnt destroyed ... this left weird remnants that caused you errors ... and really all you want to do is update the item list ...
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.
I have been looking around the Internet but I am not sure if there is a way to show 2 classes in wxPython in 2 separate windows. And could we communicate between them (like one class being the dialog and the other the main class)?
I think I did this before using Show() but I am not sure how to repeat this.
So basically I would like to be able to have a dialog but by using a class instead. This would be more powerful than using Modal dialogs.
Thanks
Here you have a simple example of two frames communicating:
The trick is in sending an object reference to share between frames, either creating one inside the other (as in this case) or through a common parent.
The code is:
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, size=(150,100), title='MainFrame')
pan =wx.Panel(self)
self.txt = wx.TextCtrl(pan, -1, pos=(0,0), size=(100,20), style=wx.DEFAULT)
self.but = wx.Button(pan,-1, pos=(10,30), label='Tell child')
self.Bind(wx.EVT_BUTTON, self.onbutton, self.but)
self.child = ChildFrame(self)
self.child.Show()
def onbutton(self, evt):
text = self.txt.GetValue()
self.child.txt.write('Parent says: %s' %text)
class ChildFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, None, size=(150,100), title='ChildFrame')
self.parent = parent
pan = wx.Panel(self)
self.txt = wx.TextCtrl(pan, -1, pos=(0,0), size=(100,20), style=wx.DEFAULT)
self.but = wx.Button(pan,-1, pos=(10,30), label='Tell parent')
self.Bind(wx.EVT_BUTTON, self.onbutton, self.but)
def onbutton(self, evt):
text = self.txt.GetValue()
self.parent.txt.write('Child says: %s' %text)
if __name__ == "__main__":
App=wx.PySimpleApp()
MainFrame().Show()
App.MainLoop()
You can also use pubsub to communicate between two frames. I show one way of doing just that in this article: http://www.blog.pythonlibrary.org/2010/06/27/wxpython-and-pubsub-a-simple-tutorial/
If you don't want the first frame to hide itself, just remove the line with the Hide() in it.