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.
Related
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 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).
I'm trying to make a simple docking frame that will dock to a parent window and follow it around. So far I have the following:
class DockingFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, style=wx.CAPTION)
parent.Bind(wx.EVT_MOVE, self.OnParentMove)
parent.Bind(wx.EVT_ACTIVATE, self.OnParentActivate)
parent.Bind(wx.EVT_SHOW, self.OnParentShow)
def OnParentMove(self, moveEvent):
print "Docked frame parent moved"
pr = positioning.position(
self.Rect,
my='right_top', at='left_top', of=self.Parent.Rect)
self.Move(pr.top_left)
moveEvent.Skip()
def OnParentActivate(self, event):
print "Docked frame parent activated"
self.Raise()
event.Skip()
def OnParentShow(self, event):
print "Docked frame parent showed"
self.Show(event.GetShow())
event.Skip()
class MainFrame(wx.Frame):
def __init__(self, title):
wx.Frame.__init__(self, None, title=title)
self.info_frame = DockingFrame(self)
self.Show(True)
This works in that if I move the parent window, the docked window moves with it, and if I click on the parent window, the docked window raises itself. However, it severely interferes with the parent window's functionality. I can't close or re-size the parent window anymore, and I get tons of "Docked frame parent activated" messages whenever I click the parent. It seems I don't understand some fundamental wxPython concept, here. What is going on and how do I fix it?
I've seen that aui seems to support docking, but documentation has been sparse so I haven't attempted it. If someone could supply a minimal working code sample as to how to make a Frame dock to another Frame with aui, I could also take that approach.
Note that I'm also using pygame and twisted in this app, which may or may not be interfering here...
And, of course, the simple approach is to simply use the wx.FRAME_FLOAT_ON_PARENT style...
class DockingFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="Last Hand",
style=wx.CAPTION | wx.FRAME_FLOAT_ON_PARENT)
parent.Bind(wx.EVT_MOVE, self.OnParentMove)
parent.Bind(wx.EVT_SHOW, self.OnParentShow)
def SnapToParent(self):
print "*Snapping to parent"
pr = positioning.position(
self.Rect,
my='right_top', at='left_top', of=self.Parent.Rect)
self.Move(pr.top_left)
def OnParentMove(self, moveEvent):
moveEvent.Skip()
self.SnapToParent()
def OnParentShow(self, event):
event.Skip()
print "Parent %s" % ("Hide", "Show")[event.GetShow()]
self.Show(event.GetShow())
The trick was that EVT_ACTIVATE fires both for activating and for de-activating. The following code worked. If the parent gets activated, then the dock raises itself & immediately raises the parent after.
class DockingFrame(wx.Frame):
def __init__(self, parent):
self.handleParentActiveState = 'noTriggers'
wx.Frame.__init__(self, parent, title="Last Hand", style=wx.CAPTION)
parent.Bind(wx.EVT_MOVE, self.OnParentMove)
parent.Bind(wx.EVT_ACTIVATE, self.OnParentActivate)
self.Bind(wx.EVT_ACTIVATE, self.OnActivate)
parent.Bind(wx.EVT_SHOW, self.OnParentShow)
def OnActivate(self, event):
event.Skip()
if not event.GetActive():
return
if self.handleParentActiveState == 'activateParentNext':
self.handleParentActiveState = 'resetTriggers'
self.Parent.Raise()
def OnParentActivate(self, event):
event.Skip()
if not event.GetActive():
return
if self.handleParentActiveState == 'noTriggers':
self.handleParentActiveState = 'activateParentNext'
self.Raise()
elif self.handleParentActiveState == 'resetTriggers':
self.handleParentActiveState = 'noTriggers'
def OnParentMove(self, moveEvent):
pr = positioning.position(
self.Rect,
my='right_top', at='left_top', of=self.Parent.Rect)
self.Move(pr.top_left)
moveEvent.Skip()
def OnParentShow(self, event):
event.Skip()
self.Show(event.GetShow())
wx.SpinCtrl is limited to spinning across integers, and not floats. Therefore, I am building a wx.TextCtrl + wx.SpinButton combo class which enables me to spin across floats. I am able to size and layout both of them programmatically so that the combo looks exactly the same as an ordinary wx.SpinCtrl.
I am subclassing this combo from the wx.TextCtrl because I want its parent panel to catch wx.EVT_TEXT events. I would appreciate if you can improve on this argument of mine.
The wx.EVT_SPIN_UP and wx.EVT_SPIN_DOWN events from the wx.SpinButton are both internal implementations and the parent frame doesn't care about these events.
Now, I just hit a brick wall. My combo class doesn't work well with sizers. After .Add()ing the combo class to a wx.GridBagSizer, only the wx.TextCtrl is laid out within the wx.GridBagSizer. The wx.SpinButton is left on its own by itself. The wx.EVT_SPIN* bindings work very well, though.
My problem is the layout. How should I write the class if I want the wx.GridBagSizer to treat it as one widget?
Here is my combo class code:
class SpinnerSuper(wx.TextCtrl):
def __init__(self, parent, max):
wx.TextCtrl.__init__(self, parent=parent, size=(48, -1))
spin = wx.SpinButton(parent=parent, style=wx.SP_VERTICAL, size=(-1, 21))
self.OnInit()
self.layout(spin)
self.internalBindings(spin)
self.SizerFlag = wx.ALIGN_CENTER
self.min = 0
self.max = max
def OnInit(self):
self.SetValue(u"0.000")
def layout(self, spin):
pos = self.GetPosition()
size = self.GetSize()
RightEdge = pos[0] + size[0]
TopEdge = pos[1] - (spin.GetSize()[1]/2 - size[1]/2)
spin.SetPosition((RightEdge, TopEdge))
def internalBindings(self, spin):
spin.Bind(wx.EVT_SPIN_UP, self.handlerSpinUp(self), spin)
spin.Bind(wx.EVT_SPIN_DOWN, self.handlerSpinDown(self), spin)
def handlerSpinUp(CallerObject, *args):
def handler(CallerObject, *data):
text = data[0]
prev = text.GetValue()
next = float(prev) + 0.008
text.SetValue("{0:0.3f}".format(next))
return lambda event: handler(CallerObject, *args)
def handlerSpinDown(CallerObject, *args):
def handler(CallerObject, *data):
text = data[0]
prev = text.GetValue()
next = float(prev) - 0.008
text.SetValue("{0:0.3f}".format(next))
return lambda event: handler(CallerObject, *args)
You need to override DoGetBestSize() if you want your control to be
correctly managed by sizers. Have a look at CreatingCustomControls.
You could also have a look at FloatSpin that ships with wxPython
(in wx.lib.agw) from version 2.8.9.2 upwards.
In response to your comments:
Implementing DoGetBestSize() does not require drawing bitmaps directly. You just need to find a way, how you can determine the best size of your new widget. Typically you'd just use
the sizes of the two widgets it is composed of (text + spinner) as basis.
To let sizers treat two widgets as one, you can place them in another sizer.
The recommended way to implement a custom widget with wxPython is to derive your new widget from wx.PyControl, add a sizer to it and add the two widgets you want to combine to that sizer.
As mentionned in Kit's comments, FloatSpin is now the way to go.
It has been integrated in recent versions.
Here is a simple example of usage:
import wx
from wx.lib.agw.floatspin import FloatSpin
class Example_FloatSpin(wx.Frame):
def __init__(self, parent, title):
super(Example_FloatSpin, self).__init__(parent, title=title, size=(480, 250))
panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
spin = FloatSpin(panel, value=0.0, min_val=0.0, max_val=8.0, increment=0.5, digits=2, size=(100,-1))
vbox.Add(spin, proportion=0, flag=wx.CENTER, border=15)
panel.SetSizer(vbox)
self.Centre()
self.Show()
if __name__ == '__main__':
app = wx.App()
Example_FloatSpin(None, title='Check FloatSpin')
app.MainLoop()
Okay, so I want to display a series of windows within windows and have the whole lot scrollable. I've been hunting through the wxWidgets documentation and a load of examples from various sources on t'internet. Most of those seem to imply that a wx.ScrolledWindow should work if I just pass it a nested group of sizers(?):
The most automatic and newest way is to simply let sizers determine the scrolling area.This is now the default when you set an interior sizer into a wxScrolledWindow with wxWindow::SetSizer. The scrolling area will be set to the size requested by the sizer and the scrollbars will be assigned for each orientation according to the need for them and the scrolling increment set by wxScrolledWindow::SetScrollRate.
...but all the example's I've seen seem to use the older methods listed as ways to achieve scrolling. I've got something basic working, but as soon as you start scrolling you lose the child windows:
import wx
class MyCustomWindow(wx.Window):
def __init__(self, parent):
wx.Window.__init__(self, parent)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.SetSize((50,50))
def OnPaint(self, event):
dc = wx.BufferedPaintDC(self)
dc.SetPen(wx.Pen('blue', 2))
dc.SetBrush(wx.Brush('blue'))
(width, height)=self.GetSizeTuple()
dc.DrawRoundedRectangle(0, 0,width, height, 8)
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.scrolling_window = wx.ScrolledWindow( self )
self.scrolling_window.SetScrollRate(1,1)
self.scrolling_window.EnableScrolling(True,True)
self.sizer_container = wx.BoxSizer( wx.VERTICAL )
self.sizer = wx.BoxSizer( wx.HORIZONTAL )
self.sizer_container.Add(self.sizer,1,wx.CENTER,wx.EXPAND)
self.child_windows = []
for i in range(0,50):
wind = MyCustomWindow(self.scrolling_window)
self.sizer.Add(wind, 0, wx.CENTER|wx.ALL, 5)
self.child_windows.append(wind)
self.scrolling_window.SetSizer(self.sizer_container)
def OnSize(self, event):
self.scrolling_window.SetSize(self.GetClientSize())
if __name__=='__main__':
app = wx.PySimpleApp()
f = TestFrame()
f.Show()
app.MainLoop()
Oops.. turns out I was creating my child windows badly:
wind = MyCustomWindow(self)
should be:
wind = MyCustomWindow(self.scrolling_window)
..which meant the child windows were waiting for the top-level window (the frame) to be re-drawn instead of listening to the scroll window. Changing that makes it all work wonderfully :)