Attach/Detach two frames in wxpython - python

I am designing a GUI with several components and two wx.Frame objects F1 and F2. F1 is the main frame and F2 is the secondary frame. I would like to have a mechanism, so the user can attach these two frames into one frame, and also detach them into two frames again if needed.
Assume F1 and F2 contain panels P1 and P2 respectively. When detached, the use should be able to move and resize each frame independently, and closing F1 will close the entire GUI. When attached, F1 will contain both P1 and P2 vertically and F2 will seem to vanish and become a part of F1. There is a lot of wiring and events and messages passed between P1 and P2 which should work in both attached and detached modes.
I have seen this effect in some modern GUI's, but I was unable to find a proper technique online to carry this out. What is a proper way to do this?
Thanks

I came up with a solution for this using the pubsub module. Following is a little example I wrote to show how it is done:
import wx
import gettext
from wx.lib.pubsub import pub
class SubFramePanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, wx.ID_ANY)
self.attachDetachButton = wx.Button(self, wx.ID_ANY, _("Attach"))
self.sayHelloButton = wx.Button(self, wx.ID_ANY, _("Say Hello"))
subPanelSizer = wx.BoxSizer(wx.HORIZONTAL)
subPanelSizer.Add(self.attachDetachButton, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL, 0)
subPanelSizer.Add(self.sayHelloButton, 0, wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, 0)
self.SetSizer(subPanelSizer)
self.attachDetachButton.Bind(wx.EVT_BUTTON, self.OnAttachDetachButton)
self.sayHelloButton.Bind(wx.EVT_BUTTON, self.OnSayHelloButton)
def OnAttachDetachButton(self, event):
if self.attachDetachButton.GetLabel() == "Attach":
self.attachDetachButton.SetLabel("Detach")
pub.sendMessage("show.mainframe.OnAttach", data=self)
else:
self.attachDetachButton.SetLabel("Attach")
pub.sendMessage("show.mainframe.OnDetach", data=self)
event.Skip()
def OnSayHelloButton(self, event):
pub.sendMessage("show.mainframe.addText", data="Say Hello\n")
event.Skip()
class SubFrame(wx.Frame):
def __init__(self, *args, **kwds):
kwds["style"] = wx.DEFAULT_FRAME_STYLE
if kwds.has_key("panel"):
self.panel = kwds["panel"]
del kwds["panel"]
else:
self.panel = None
wx.Frame.__init__(self, *args, **kwds)
if self.panel is None:
self.panel = SubFramePanel(self)
else:
self.panel.Reparent(self)
self.SetTitle(_("Sub Frame"))
self.SetSize((291, 93))
subFrameSizer = wx.BoxSizer(wx.VERTICAL)
subFrameSizer.Add(self.panel, 1, wx.EXPAND | wx.LEFT, 5)
self.SetSizer(subFrameSizer)
self.Layout()
pub.subscribe(self.OnClose, "show.subframe.OnClose")
def OnClose(self, data=None):
self.Close()
# end of class SubFrame
class MainFrame(wx.Frame):
def __init__(self, *args, **kwds):
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.text_ctrl_1 = wx.TextCtrl(self, wx.ID_ANY, "", style=wx.TE_MULTILINE)
pub.subscribe(self.OnAddText, "show.mainframe.addText")
pub.subscribe(self.OnAttach, "show.mainframe.OnAttach")
pub.subscribe(self.OnDetach, "show.mainframe.OnDetach")
self.SetTitle(_("Main Frame"))
self.SetSize((492, 271))
self.mainFrameSizer = wx.BoxSizer(wx.VERTICAL)
self.mainFrameSizer.Add(self.text_ctrl_1, 1, wx.ALL | wx.EXPAND, 5)
self.SetSizer(self.mainFrameSizer)
self.Layout()
def OnAddText(self, data):
self.text_ctrl_1.WriteText(data)
def OnAttach(self, data):
self.mainFrameSizer.Add(data, 0, wx.ALL | wx.EXPAND, 5)
data.Reparent(self)
self.Layout()
pub.sendMessage("show.subframe.OnClose")
def OnDetach(self, data):
subFrame = SubFrame(self, wx.ID_ANY, "", panel=data)
self.mainFrameSizer.Remove(data)
self.Layout()
subFrame.Show()
class MyApp(wx.App):
def OnInit(self):
mainFrame = MainFrame(None, wx.ID_ANY, "")
self.SetTopWindow(mainFrame)
mainFrame.Show()
subFrame = SubFrame(mainFrame, wx.ID_ANY, "")
subFrame.Show()
return 1
if __name__ == "__main__":
gettext.install("app")
app = MyApp(0)
app.MainLoop()

I'm not sure you can move a wxPanel from a wxFrame to another on the fly on wx.
The main reason is that the panel is dependent of its parent and you can't change it on the fly.
Now if you really want to do it, you'll have to create copy the panel in the other frame and delete the previous panel and frame (or just hide them).
There's no built in copy but you can find a way to get the content of your original panel and copy it on the other one.

There is a library in wxPython called AUI. It provides the mechanism to detach a panel from a frame. The following link has an example along with some other information:
http://wiki.wxpython.org/AuiNotebook%20(AGW)

Related

How can I make a wxpython Widget span two cells without pushing other widgets aside?

I'm trying to make a form that has several input fields. Underneath these fields I want to have a wxpython Ultimate List Control (for all intents and purposes it's the same thing as a List Control). My issue is with sizers. To give some context, my form looks like this
Name [TextCtrl]
Blah [TextCtrl]
ListControl
I want it to look like
Name [TextCtrl]
Blah [TextCtrl]
ListCtrl (this spans to the end of the row)
My problem is when I try to add the List Control. I want the list control to Stretch from The Static Text to the Text Control, but it pushes the TextControl over. Can someone please point me in the right direction? I have attached the relevant code below.
class UserField(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent=parent, title="Info", size=(350, 400),
style=wx.DEFAULT_FRAME_STYLE)
self.init_ui()
self.Center()
self.ShowModal()
def init_ui(self):
panel = wx.Panel(self, wx.ID_ANY)
hbox = wx.BoxSizer(wx.VERTICAL)
flex_grid = wx.FlexGridSizer(5, 2, 5, 10) # row, col, vgap, hgap
info_text = wx.StaticText(parent=panel, label="Enter information")
self.search_button = wx.Button(parent=panel, label="Search")
self.list_control = UltimateListCtrl(panel,
agwStyle=wx.LC_REPORT | wx.BORDER_SUNKEN | ULC_HAS_VARIABLE_ROW_HEIGHT, )
flex_grid.AddMany(
[
info_text, self.search_button
]
)
lbox = wx.BoxSizer(wx.HORIZONTAL)
lbox.Add(self.list_control
hbox.Add(flex_grid, wx.EXPAND|wx.ALL)
hbox.Add(lbox, proportion=1, flag=wx.ALL|wx.EXPAND)
panel.SetSizer(hbox)
Here's a quick demonstration of wx.GridBagSizer. The program opens a simple frame with a single button that spawns a dialog with a GridBagSizer. You can place items in the sizer according to a position (pos) and optionally allow a widget to span multiple rows and/or columns (span).
import wx
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.SetSize((300, 200))
self.Centre()
self.Show(True)
self.InitUI()
def InitUI(self):
panel = wx.Panel(self)
sizer = wx.BoxSizer()
btn = wx.Button(panel, label="Spawn Window")
btn.Bind(wx.EVT_BUTTON, self.spawn_window)
sizer.Add(btn)
panel.SetSizerAndFit(sizer)
def spawn_window(self, evt):
UserField(self)
def OnQuit(self, e):
self.Close()
class UserField(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent=parent, title="Info", size=(350, 400),
style=wx.DEFAULT_FRAME_STYLE)
self.init_ui()
self.Center()
self.ShowModal()
def init_ui(self):
panel = wx.Panel(self)
sizer = wx.GridBagSizer(10, 10)
field1Label = wx.StaticText(panel, label="Field 1")
field2Label = wx.StaticText(panel, label="Field 2")
field1Ctrl = wx.TextCtrl(panel)
field2Ctrl = wx.TextCtrl(panel)
listCtrl = wx.ListCtrl(panel)
sizer.Add(field1Label, pos=(0, 0))
sizer.Add(field2Label, pos=(1, 0))
sizer.Add(field1Ctrl, pos=(0, 1))
sizer.Add(field2Ctrl, pos=(1, 1))
# HERE'S THE IMPORTANT LINE. NOTE THE 'span' ARGUMENT:
sizer.Add(listCtrl, pos=(2, 0), span=(1, 2), flag=wx.EXPAND)
panel.SetSizerAndFit(sizer)
if __name__ == '__main__':
ex = wx.App()
mainFrame = Example(None)
ex.MainLoop()

Update/Refresh Dynamically–Created WxPython Widgets

New python programmer here and trying to learn how to dynamically update widgets. To start, I have the following code. What I would like to do is change my variable "self.dynamiclength" to any integer, and have WxPython update the number of widgets appropriately. I have tried putting self.Refresh() and self.Update() in my TestFrame after updating self.dynamiclength to no avail.
I have done as much reading as possible on this before resorting to asking for help, but I am just too new at Wx to solve this one on my own. Thank you much!
import wx
import wx.lib.scrolledpanel as scrolled
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, size=(1000, 550))
panel = wx.Panel(self)
mainSizer = wx.BoxSizer(wx.VERTICAL)
pbox0 = wx.BoxSizer(wx.VERTICAL)
controlback0 = wx.Button(panel, label="Back0")
controlforward0 = wx.Button(panel, label="Forward0")
pbox0.Add(controlback0, 0, wx.ALL)
pbox0.Add(controlforward0, 0, wx.ALL)
mainSizer.Add(pbox0)
self.scrolling_window = scrolled.ScrolledPanel( panel )
self.scrolling_window.SetAutoLayout(1)
self.scrolling_window.SetupScrolling()
self.sizer = wx.BoxSizer( wx.VERTICAL )
self.child_windows = []
##############################################
#this is the variable that I want to change,
#and I don't know how to get the 'for loop'
#below to update as well.
self.eedictionary = {}
self.dynamiclength = 5
for i in range(0,self.dynamiclength):
wind = self.addBox(i)
self.sizer.Add(wind, 0, wx.CENTER|wx.ALL, 5)
###############################################
#the following code binds all appropriate buttons to a pedigree variable updater
button_binding_list = ['controlback','controlforward']
for j in button_binding_list:
eid = self.eedictionary[str(i)+j]
self.scrolling_window.Bind(wx.EVT_BUTTON, lambda evt: self.onclick(evt, id), id=eid)
self.scrolling_window.SetSizer(self.sizer)
mainSizer.Add(self.scrolling_window, 1, wx.EXPAND)
panel.SetSizer(mainSizer)
def addBox(self, i):
pbox = wx.BoxSizer(wx.VERTICAL)
controlback = wx.Button(self.scrolling_window, label="Back")
controlforward = wx.Button(self.scrolling_window, label="Forward")
pbox.AddMany([(controlback, 0, wx.ALL), (controlforward, 0, wx.ALL)])
#for each object created in the addBox module, its id is added to the dictionary
self.eedictionary[str(i)+'controlback'] = controlback.GetId()
self.eedictionary[str(i)+'controlforward'] = controlforward.GetId()
return pbox
def onclick(self, event):
self.dynamiclength +=1
print 'added one to self.dynamiclength', self.dynamiclength
if __name__=='__main__':
app = wx.App(False)
f = TestFrame()
f.Show()
app.MainLoop()
I have similar test code which I have written some time ago. Maybe you will find it useful.
import wx
#===================================================================================================
class UpperPanel(wx.Panel):
def __init__(self, *args, **kwargs):
wx.Panel.__init__(self, *args, **kwargs)
self.combo = wx.ComboBox(self, choices=["0", "1", "2", "3", "4"], size=(200, -1))
self.combo.Bind(wx.EVT_COMBOBOX, self.GetParent().middlePanel.Change)
self.logo = wx.Button(self, size=(300, 100))
self.sizer = wx.BoxSizer()
self.sizer.Add(self.combo, 0, wx.EXPAND)
self.sizer.Add(self.logo, 0, wx.EXPAND)
self.SetSizerAndFit(self.sizer)
#===================================================================================================
class MiddlePanel(wx.Panel):
def __init__(self, *args, **kwargs):
wx.Panel.__init__(self, *args, **kwargs)
self.subs = []
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizerAndFit(self.sizer)
def Change(self, e):
self.sizer = wx.BoxSizer(wx.VERTICAL)
for a in self.subs:
a.Destroy()
self.subs = []
for a in range(int(e.GetString())):
b = wx.Button(self, size=(-1, 50))
self.subs.append(b)
self.sizer.Add(b, 1, wx.EXPAND)
self.SetSizerAndFit(self.sizer)
self.GetParent().Fit()
#===================================================================================================
class MainWin(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
self.middlePanel = MiddlePanel(self)
self.upperPanel = UpperPanel(self)
self.textArea = wx.TextCtrl(self, size=(-1, 300), style=wx.TE_MULTILINE)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.upperPanel, 0, wx.EXPAND)
self.sizer.Add(self.middlePanel, 0, wx.EXPAND)
self.sizer.Add(self.textArea, 1, wx.EXPAND)
self.SetSizerAndFit(self.sizer)
#===================================================================================================
if __name__ == '__main__':
app = wx.PySimpleApp()
main_win = MainWin()
main_win.Show()
app.MainLoop()
If you need to update the number of widgets AFTER you've already created and shown the application, the you'll need to do it in a method, NOT in the init. The init only runs the first time the application is instantiated. Whenever you add or remove widgets after the frame is shown, you'll need to call Layout() on the parent widget or its sizer. See also
http://wxpython-users.1045709.n5.nabble.com/dynamically-adding-amp-removing-widgets-td2342432.html
https://groups.google.com/forum/?fromgroups#!topic/wxPython-users/eQjlYlsw4qs
Adding a widget with a button - wxPython

wxPython problems with wrapping staticText

A simplified version of the code is posted below (white space, comments, etc. removed to reduce size - but the general format to my program is kept roughly the same).
When I run the script, the static text correctly wraps as it should, but the other items in the panel do not move down (they act as if the statictext is only one line and thus not everything is visible).
If I manually resize the window/frame, even just a tiny amount, everything gets corrected, and displays as it is should.
Why doesn't it display correctly to begin with? I've tried all sorts of combination's of GetParent().Refresh() or Update() and GetTopLevelParent().Update() or Refresh(). I've also tried everything I can think of but cannot get it to display correctly without manually resizing the frame/window. Once re-sized, it works exactly as I want it to.
Information:
Windows XP
Python 2.5.2
wxPython 2.8.11.0 (msw-unicode)
My Code:
#! /usr/bin/python
import wx
class StaticWrapText(wx.PyControl):
def __init__(self, parent, id=wx.ID_ANY, label='', pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.NO_BORDER,
validator=wx.DefaultValidator, name='StaticWrapText'):
wx.PyControl.__init__(self, parent, id, pos, size, style, validator, name)
self.statictext = wx.StaticText(self, wx.ID_ANY, label, style=style)
self.wraplabel = label
#self.wrap()
def wrap(self):
self.Freeze()
self.statictext.SetLabel(self.wraplabel)
self.statictext.Wrap(self.GetSize().width)
self.Thaw()
def DoGetBestSize(self):
self.wrap()
#print self.statictext.GetSize()
self.SetSize(self.statictext.GetSize())
return self.GetSize()
class TestPanel(wx.Panel):
def __init__(self, *args, **kwargs):
# Init the base class
wx.Panel.__init__(self, *args, **kwargs)
self.createControls()
def createControls(self):
# --- Panel2 -------------------------------------------------------------
self.Panel2 = wx.Panel(self, -1)
msg1 = 'Below is a List of Files to be Processed'
staticBox = wx.StaticBox(self.Panel2, label=msg1)
Panel2_box1_v1 = wx.StaticBoxSizer(staticBox, wx.VERTICAL)
Panel2_box2_h1 = wx.BoxSizer(wx.HORIZONTAL)
Panel2_box3_v1 = wx.BoxSizer(wx.VERTICAL)
self.wxL_Inputs = wx.ListBox(self.Panel2, wx.ID_ANY, style=wx.LB_EXTENDED)
sz = dict(size=(120,-1))
wxB_AddFile = wx.Button(self.Panel2, label='Add File', **sz)
wxB_DeleteFile = wx.Button(self.Panel2, label='Delete Selected', **sz)
wxB_ClearFiles = wx.Button(self.Panel2, label='Clear All', **sz)
Panel2_box3_v1.Add(wxB_AddFile, 0, wx.TOP, 0)
Panel2_box3_v1.Add(wxB_DeleteFile, 0, wx.TOP, 0)
Panel2_box3_v1.Add(wxB_ClearFiles, 0, wx.TOP, 0)
Panel2_box2_h1.Add(self.wxL_Inputs, 1, wx.ALL|wx.EXPAND, 2)
Panel2_box2_h1.Add(Panel2_box3_v1, 0, wx.ALL|wx.EXPAND, 2)
msg = 'This is a long line of text used to test the autowrapping '
msg += 'static text message. '
msg += 'This is a long line of text used to test the autowrapping '
msg += 'static text message. '
msg += 'This is a long line of text used to test the autowrapping '
msg += 'static text message. '
msg += 'This is a long line of text used to test the autowrapping '
msg += 'static text message. '
staticMsg = StaticWrapText(self.Panel2, label=msg)
Panel2_box1_v1.Add(staticMsg, 0, wx.ALL|wx.EXPAND, 2)
Panel2_box1_v1.Add(Panel2_box2_h1, 1, wx.ALL|wx.EXPAND, 0)
self.Panel2.SetSizer(Panel2_box1_v1)
# --- Combine Everything -------------------------------------------------
final_vbox = wx.BoxSizer(wx.VERTICAL)
final_vbox.Add(self.Panel2, 1, wx.ALL|wx.EXPAND, 2)
self.SetSizerAndFit(final_vbox)
class TestFrame(wx.Frame):
def __init__(self, *args, **kwargs):
# Init the base class
wx.Frame.__init__(self, *args, **kwargs)
panel = TestPanel(self)
self.SetClientSize(wx.Size(500,500))
self.Center()
class wxFileCleanupApp(wx.App):
def __init__(self, *args, **kwargs):
# Init the base class
wx.App.__init__(self, *args, **kwargs)
def OnInit(self):
# Create the frame, center it, and show it
frame = TestFrame(None, title='Test Frame')
frame.Show()
return True
if __name__ == '__main__':
app = wxFileCleanupApp()
app.MainLoop()
Using Mike Driscoll's code as a baseline, I hope this demonstrates my issue. There are two different versions of using "txt". Here are three things I want you to try:
Run it as-is. With my StaticWrapText. It displays wrong at first, but re-size the window and it works EXACTLY as I want. There is no blank/wasted space below the text before the "button"
Change these two lines (change the comments):
txt = wx.StaticText(panel, label=text)
#txt = StaticWrapText(panel, label=text)
Now you will see there is no wrapping and the text is always on only one line. Definitely not what we want. This is because of "sizer.Add(txt, 0, wx.EXPAND, 5) "...so going on to Part 3...
Keep the change from Part 2 and also change:
sizer.Add(txt, 0, wx.EXPAND, 5)
to:
sizer.Add(txt, 1, wx.EXPAND, 5)
So now the statictext will expand. This is CLOSE to working...BUT I don't want all that wasted space between the text and the button. If you make the window large, there is a lot of wasted space. See Part 1 after the window is re-sized to see the difference.
Code:
import wx
class StaticWrapText(wx.PyControl):
def __init__(self, parent, id=wx.ID_ANY, label='', pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.NO_BORDER,
validator=wx.DefaultValidator, name='StaticWrapText'):
wx.PyControl.__init__(self, parent, id, pos, size, style, validator, name)
self.statictext = wx.StaticText(self, wx.ID_ANY, label, style=style)
self.wraplabel = label
#self.wrap()
def wrap(self):
self.Freeze()
self.statictext.SetLabel(self.wraplabel)
self.statictext.Wrap(self.GetSize().width)
self.Thaw()
def DoGetBestSize(self):
self.wrap()
#print self.statictext.GetSize()
self.SetSize(self.statictext.GetSize())
return self.GetSize()
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
text = "I'm subclasses the statictext because I want it to act exactly like a static text, but correctly wordwrap as needed. I've found several examples of it on the web, but none that worked how I wanted. The wordwrap makes it look much nicer when the user may decide to re-size the window, so I would definitely like to have it be wordwrapped. I know about the wx.lib.wordwrap, but chose to use the built in Wrap function of the statictext control instead. It basically does the same thing from what I understand."
#txt = wx.StaticText(panel, label=text)
txt = StaticWrapText(panel, label=text)
wxbutton = wx.Button(panel, label='Button', size=wx.Size(120,50))
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(txt, 0, wx.EXPAND, 5)
sizer.Add(wxbutton, 1, wx.EXPAND, 5)
panel.SetSizer(sizer)
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
EDIT:
AHHH...finally! I tried using the Layout() method on virtually every level of the program, but I actually needed to use Layout() on the SIZER which is found with the method GetSizer() - or you can send SendSizeEvent() to the panel (commented in code below). Thus, the following now does EXACTLY what I want! Thanks for the help. The only other change was to store the panel with self.panel in the frame class. As a note, I had to put this statement AFTER the frame.Show() or it didn't work correctly.
Code:
import wx
class StaticWrapText(wx.PyControl):
def __init__(self, parent, id=wx.ID_ANY, label='', pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.NO_BORDER,
validator=wx.DefaultValidator, name='StaticWrapText'):
wx.PyControl.__init__(self, parent, id, pos, size, style, validator, name)
self.statictext = wx.StaticText(self, wx.ID_ANY, label, style=style)
self.wraplabel = label
#self.wrap()
def wrap(self):
self.Freeze()
self.statictext.SetLabel(self.wraplabel)
self.statictext.Wrap(self.GetSize().width)
self.Thaw()
def DoGetBestSize(self):
self.wrap()
#print self.statictext.GetSize()
self.SetSize(self.statictext.GetSize())
return self.GetSize()
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")
# Add a panel so it looks the correct on all platforms
self.panel = wx.Panel(self, wx.ID_ANY)
text = "I'm subclasses the statictext because I want it to act exactly like a static text, but correctly wordwrap as needed. I've found several examples of it on the web, but none that worked how I wanted. The wordwrap makes it look much nicer when the user may decide to re-size the window, so I would definitely like to have it be wordwrapped. I know about the wx.lib.wordwrap, but chose to use the built in Wrap function of the statictext control instead. It basically does the same thing from what I understand."
txt = StaticWrapText(self.panel, label=text)
wxbutton = wx.Button(self.panel, label='Button', size=wx.Size(120,50))
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(txt, 0, wx.EXPAND, 5)
sizer.Add(wxbutton, 1, wx.EXPAND, 5)
self.panel.SetSizer(sizer)
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm()
frame.Show()
#frame.panel.SendSizeEvent()
frame.panel.GetSizer().Layout()
app.MainLoop()
As a final note, in my original program posted, the following line needs to be added just before or after frame.Show():
frame.panel.Panel2.GetSizer().Layout()
Interestingly...with that original example this can be before or after frame.Show() but the other example requires that it be after frame.Show(). I'm not sure why, but just put it after and you're safe.
I use
width = 200 # panel width
txt = wx.StaticText(panel, label=text)
txt.Wrap(width)
This works great and the next widgets are positioned correctly. You can easily do the txt.Wrap(width) dynamically.
Why are you subclassing it? Do you need wordwrap? If so, there's a module for that in wx.lib.wordwrap that you can use.
In answer the the OP's comment, check this out:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
text = "I'm subclasses the statictext because I want it to act exactly like a static text, but correctly wordwrap as needed. I've found several examples of it on the web, but none that worked how I wanted. The wordwrap makes it look much nicer when the user may decide to re-size the window, so I would definitely like to have it be wordwrapped. I know about the wx.lib.wordwrap, but chose to use the built in Wrap function of the statictext control instead. It basically does the same thing from what I understand."
txt = wx.StaticText(panel, label=text)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(txt, 1, wx.EXPAND, 5)
panel.SetSizer(sizer)
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
I used the OP's comment for the text. Anyway, this works fine for me on Windows XP, Python 2.5 and wxPython 2.8.10.1.
I found what I think is a much easier and automatic way to handle this issue.
After creating the StaticText control, bind the control's wx.EVT_SIZE to a handler that calls the StaticText's Wrap() function with the event's GetSize()[0] as an argument (and then skips the event).
An example:
class MyDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent = parent, title = "Test Dialog", style = wx.CAPTION)
bigstr = "This is a really long string that is intended to test the wrapping functionality of the StaticText control in this dialog. If it works correctly, it should appear as multiple lines of text with a minimum of fuss."
self.__label__ = wx.StaticText(parent = self, label = bigstr)
self.__actionbutton__ = wx.Button(parent = self, label = "Go")
self.__label__.Bind(wx.EVT_SIZE, self.__WrapText__)
self.__actionbutton__.Bind(wx.EVT_BUTTON, self.__OnButton__)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.__label__, flag = wx.ALL | wx.EXPAND, border = 5)
sizer.Add(self.__actionbutton__, flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.CENTER, border = 0)
self.SetSizer(sizer)
self.Layout()
def __OnButton__(self, event):
self.EndModal(wx.ID_OK)
def __WrapText__(self, event):
self.__label__.Wrap(event.GetSize()[0])
event.Skip()
This is what it looks like on my system (MSW, Python 2.7.5, wx 2.8.12.1):

Using the same handler for multiple wx.TextCtrls?

I'm having a bit of trouble with a panel that has two wxPython TextCtrls in it. I want either an EVT_CHAR or EVT_KEY_UP handler bound to both controls, and I want to be able to tell which TextCtrl generated the event. I would think that event.Id would tell me this, but in the following sample code it's always 0. Any thoughts? I've only tested this on OS X.
This code simply checks that both TextCtrls have some text in them before enabling the Done button
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, ID, title):
wx.Frame.__init__(self, parent, ID, title,
wx.DefaultPosition, wx.Size(200, 150))
self.panel = BaseNameEntryPanel(self)
class BaseNameEntryPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
self.entry = wx.TextCtrl(self, wx.NewId())
self.entry2 = wx.TextCtrl(self, wx.NewId())
self.donebtn = wx.Button(self, wx.NewId(), "Done")
self.donebtn.Disable()
vsizer = wx.BoxSizer(wx.VERTICAL)
vsizer.Add(self.entry, 1, wx.EXPAND|wx.GROW)
vsizer.Add(self.entry2, 1, wx.EXPAND|wx.GROW)
vsizer.Add(self.donebtn, 1, wx.EXPAND|wx.GROW)
self.SetSizer(vsizer)
self.Fit()
self.entry.Bind(wx.EVT_KEY_UP, self.Handle)
self.entry2.Bind(wx.EVT_KEY_UP, self.Handle)
def Handle(self, event):
keycode = event.GetKeyCode()
print keycode, event.Id # <- event.Id is always 0!
def checker(entry):
return bool(entry.GetValue().strip())
self.donebtn.Enable(checker(self.entry) and checker(self.entry2))
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, -1, "Hello from wxPython")
frame.Show(True)
self.SetTopWindow(frame)
return True
app = MyApp(0)
app.MainLoop()
You could try event.GetId() or event.GetEventObject() and see if either of these work.
Another approach to this is to use lambda or functools.partial to effectively pass a parameter to the handler. So, for example, sub in the lines below into your program:
self.entry.Bind(wx.EVT_KEY_UP, functools.partial(self.Handle, ob=self.entry))
self.entry2.Bind(wx.EVT_KEY_UP, functools.partial(self.Handle, ob=self.entry2))
def Handle(self, event, ob=None):
print ob
And then ob will be either entry or entry2 depending on which panel is clicked. But, of course, this shouldn't be necessary, and GetId and GetEventObject() should both work -- though I don't (yet) have a Mac to try these on.

How do I Create an instance of a class in another class in Python

I am trying to learn Python and WxPython. I have been a SAS programmer for years. This OOP stuff is slowly coming together but I am still fuzzy on a lot of the concepts. Below is a section of code. I am trying to use a button click to create an instance of another class. Specifically-I have my main panel in one class and I wanted to instance a secondary panel when a user clicked on one of the menu items on the main panel. I made all of this work when the secondary panel was just a function. I can't seem to get ti to work as a class.
Here is the code
import wx
class mainPanel(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, 'directEDGAR Supplemental Tools', size=(450, 450))
wx.Panel(self,-1)
wx.StaticText(self,-1, "This is where I will describe\n the purpose of these tools",(100,10))
menubar = wx.MenuBar()
parser = wx.Menu()
one =wx.MenuItem(parser,1,'&Extract Tables with One Heading or Label')
two =wx.MenuItem(parser,1,'&Extract Tables with Two Headings or Labels')
three =wx.MenuItem(parser,1,'&Extract Tables with Three Headings or Labels')
four =wx.MenuItem(parser,1,'&Extract Tables with Four Headings or Labels')
quit = wx.MenuItem(parser, 2, '&Quit\tCtrl+Q')
parser.AppendItem(one)
parser.AppendItem(two)
parser.AppendItem(three)
parser.AppendItem(four)
parser.AppendItem(quit)
menubar.Append(parser, '&Table Parsers')
textRip = wx.Menu()
section =wx.MenuItem(parser,1,'&Extract Text With Section Headings')
textRip.AppendItem(section)
menubar.Append(textRip, '&Text Rippers')
dataHandling = wx.Menu()
deHydrate =wx.MenuItem(dataHandling,1,'&Extract Data from Tables')
dataHandling.AppendItem(deHydrate)
menubar.Append(dataHandling, '&Data Extraction')
self.Bind(wx.EVT_MENU, self.OnQuit, id=2)
this is where I think I am being clever by using a button click to create an instance
of subPanel.
self.Bind(wx.EVT_MENU, self.subPanel(None, -1, 'TEST'),id=1)
self.SetMenuBar(menubar)
self.Centre()
self.Show(True)
def OnQuit(self, event):
self.Close()
class subPanel(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, 'directEDGAR Supplemental Tools', size=(450, 450))
wx.Panel(self,-1)
wx.StaticText(self,-1, "This is where I will describe\n the purpose of these tools",(100,10))
getDirectory = wx.Button(panel, -1, "Get Directory Path", pos=(20,350))
getDirectory.SetDefault()
getTerm1 = wx.Button(panel, -1, "Get Search Term", pos=(20,400))
getTerm1.SetDefault()
#getDirectory.Bind(wx.EVT_BUTTON, getDirectory.OnClick, getDirectory.button)
self.Centre()
self.Show(True)
app = wx.App()
mainPanel(None, -1, '')
app.MainLoop()
I don't know wxWidgets, but based on what I know of Python, I'm guessing that you need to change:
self.Bind(wx.EVT_MENU, self.subPanel(None, -1, 'TEST'),id=1)
to:
self.Bind(wx.EVT_MENU, subPanel(None, -1, 'TEST'),id=1)
"subPanel" is a globally defined class, not a member of "self" (which is a mainPanel).
Edit: Ah, "Bind" seems to bind an action to a function, so you need to give it a function that creates the other class. Try the following. It still doesn't work, but at least it now crashes during the subPanel creation.
self.Bind(wx.EVT_MENU, lambda(x): subPanel(None, -1, 'TEST'),id=1)
You should handle the button click event, and create the panel in your button handler (like you already do with your OnQuit method).
I think the following code basically does what you're after -- creates a new Frame when the button is clicked/menu item is selected.
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, title="My Frame", num=1):
self.num = num
wx.Frame.__init__(self, parent, -1, title)
panel = wx.Panel(self)
button = wx.Button(panel, -1, "New Panel")
button.SetPosition((15, 15))
self.Bind(wx.EVT_BUTTON, self.OnNewPanel, button)
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
# Now create a menu
menubar = wx.MenuBar()
self.SetMenuBar(menubar)
# Panel menu
panel_menu = wx.Menu()
# The menu item
menu_newpanel = wx.MenuItem(panel_menu,
wx.NewId(),
"&New Panel",
"Creates a new panel",
wx.ITEM_NORMAL)
panel_menu.AppendItem(menu_newpanel)
menubar.Append(panel_menu, "&Panels")
# Bind the menu event
self.Bind(wx.EVT_MENU, self.OnNewPanel, menu_newpanel)
def OnNewPanel(self, event):
panel = MyFrame(self, "Panel %s" % self.num, self.num+1)
panel.Show()
def OnCloseWindow(self, event):
self.Destroy()
def main():
application = wx.PySimpleApp()
frame = MyFrame(None)
frame.Show()
application.MainLoop()
if __name__ == "__main__":
main()
Edit: Added code to do this from a menu.
You need an event handler in your bind expression
self.bind(wx.EVT_MENU, subPanel(None, -1, 'TEST'),id=1)
needs to be changed to:
self.bind(wx.EVT_MENU, <event handler>, <id of menu item>)
where your event handler responds to the event and instantiates the subpanel:
def OnMenuItem(self, evt): #don't forget the evt
sp = SubPanel(self, wx.ID_ANY, 'TEST')
#I assume you will add it to a sizer
#if you aren't... you should
test_sizer.Add(sp, 1, wx.EXPAND)
#force the frame to refresh the sizers:
self.Layout()
Alternatively, you can instantiate the subpanel in your frame's __init__ and call a subpanel.Hide() after instantiation, and then your menuitem event handler and call a show on the panel subpanel.Show()
Edit: Here is some code that will do what I think that you are asking:
#!usr/bin/env python
import wx
class TestFrame(wx.Frame):
def __init__(self, parent, *args, **kwargs):
wx.Frame.__init__(self, parent, *args, **kwargs)
framesizer = wx.BoxSizer(wx.VERTICAL)
mainpanel = MainPanel(self, wx.ID_ANY)
self.subpanel = SubPanel(self, wx.ID_ANY)
self.subpanel.Hide()
framesizer.Add(mainpanel, 1, wx.EXPAND)
framesizer.Add(self.subpanel, 1, wx.EXPAND)
self.SetSizerAndFit(framesizer)
class MainPanel(wx.Panel):
def __init__(self, parent, *args, **kwargs):
wx.Panel.__init__(self, parent, *args, **kwargs)
panelsizer = wx.BoxSizer(wx.VERTICAL)
but = wx.Button(self, wx.ID_ANY, "Add")
self.Bind(wx.EVT_BUTTON, self.OnAdd, but)
self.panel_shown = False
panelsizer.Add(but, 0)
self.SetSizer(panelsizer)
def OnAdd(self, evt):
if not self.panel_shown:
self.GetParent().subpanel.Show()
self.GetParent().Fit()
self.GetParent().Layout()
self.panel_shown = True
else:
self.GetParent().subpanel.Hide()
self.GetParent().Fit()
self.GetParent().Layout()
self.panel_shown = False
class SubPanel(wx.Panel):
def __init__(self, parent, *args, **kwargs):
wx.Panel.__init__(self, parent, *args, **kwargs)
spsizer = wx.BoxSizer(wx.VERTICAL)
text = wx.StaticText(self, wx.ID_ANY, label='I am a subpanel')
spsizer.Add(text, 1, wx.EXPAND)
self.SetSizer(spsizer)
if __name__ == '__main__':
app = wx.App()
frame = TestFrame(None, wx.ID_ANY, "Test Frame")
frame.Show()
app.MainLoop()

Categories