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):
Related
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)
Based on user interaction, I would like to dynamically add and remove controls to a panel in a wxPython notebook. The approach I've tried most thoroughly is to call .Clear() on the panel's sizer and add all new controls. However, on both Windows 7 and Linux desktops, rendering artifacts and stale controls remain visible under the new contents. How can I completely remove the old controls and add new controls without these artifacts?
Below is a sample program that reproduces the issue on Windows 7. Note the two different .update() methods of StaticPanel and DynamicPanel:
#!/usr/bin/python
import wx
import sys
class StaticPane(wx.Panel):
"""A panel that contains simple text that is updated
when the .update() method is called. The text is updated
using .SetText(), and the text control sticks around
between calls to .update()."""
def __init__(self, *args, **kwargs):
super(StaticPane, self).__init__(*args, **kwargs)
self._sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self._sizer)
self._counter = 0
self._base_text = "Some Text"
self._text = wx.TextCtrl(self, -1,
self._base_text + "!" * self._counter,
style=wx.TE_READONLY)
self._sizer.Add(self._text, -1, wx.EXPAND)
def update(self):
self._counter += 1
self._text.SetValue(self._base_text + "!" * self._counter)
class DynamicPane(wx.Panel):
"""A panel that contains simple text that is updated
when the .update() method is called. The text is updated
by removing the existing text control, and adding a new one
with the updated text string."""
def __init__(self, *args, **kwargs):
super(DynamicPane, self).__init__(*args, **kwargs)
self._sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self._sizer)
self._counter = 0
self._base_text = "Some Text"
self._text = wx.TextCtrl(self, -1,
self._base_text + "!" * self._counter,
style=wx.TE_READONLY)
self._sizer.Add(self._text, -1, wx.EXPAND)
def update(self):
self._counter += 1
self._sizer.Clear()
self._text = wx.TextCtrl(self, -1,
self._base_text + "!" * self._counter,
style=wx.TE_READONLY)
self._sizer.Add(self._text, -1, wx.EXPAND)
self.Layout()
class TestViewer(wx.Frame):
"""A Frame with a button and a notebook. When the button is pressed,
each of the two pages in the notebook recieve a call to .update().
"""
def __init__(self, parent):
super(TestViewer, self).__init__(parent, -1, "Test Viewer")
self.Bind(wx.EVT_CLOSE, self.OnClose)
self._panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
self._panel.SetSizer(vbox)
update_button = wx.Button(self._panel, wx.ID_CLOSE, "Update")
update_button.Bind(wx.EVT_BUTTON, self.update)
vbox.Add(update_button, 0, wx.EXPAND)
self._nb = wx.Notebook(self._panel)
self._view_one = StaticPane(self._nb, -1)
self._view_two = DynamicPane(self._nb, -1)
self._nb.AddPage(self._view_one, "One")
self._nb.AddPage(self._view_two, "Two")
vbox.Add(self._nb, 1, wx.EXPAND | wx.ALL)
self.Layout()
def update(self, e):
self._view_one.update()
self._view_two.update()
def OnClose(self, event):
sys.exit(0)
if __name__ == "__main__":
app = wx.App(False)
frame = TestViewer(None)
frame.Show()
app.MainLoop()
Click the button "Update" to update the text in the two panels. The first panel updates the text control by using .SetText(), while the second panel replaces the TextCtrl with a new one. Note that as you resize the window or mouseover the second panel after a few button clicks, there are overlapping controls and other artifacts.
Here are screenshots showing the stacked controls. Both images were taken after the same number of button clicks, they just show the two different panels at the same state. I expected the text to be exactly the same.
Simply clearing the sizer will only remove the references to its contents, not the widgets. When you create a widget, it will register with the parent window you supplied. Consider the following code:
class Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None)
self.textCtrl = wx.TextCtrl(self) # create with Frame as parent
self.textCtrl = None # has no effect on the TextCtrl
The parent window (Frame in this example) will take ownership of the TextCtrl and even if you set it to None, the Frame will keep it alive until it's is destroyed. To remove the TextCtrl, you have to destroy it explicitly:
self.textCtrl.Destroy()
If you want to remove all child widgets at once, you can use:
self.DestroyChildren()
Help!
I am a non-GUI programmer who is trying to write a simple (!) program using wxPython.
I have read everything I can online, but my overall lack of GUI experience is presumably causing me to not see the problem.
In a nutshell, I want to have a window with a wxNotebook with several tabs. Each tab, of course, will have its own child widgets. I envision having either a wxListCtrl (as in my code) or possibly a wxGrid control, along with several buttons.
Here is my "EmployeesPanel" class. When I run this, I see a tiny square that must represent the listctrl, but for the life of me I cannot figure out how to make it look correct. Of course, it is possible that I am way off base in some other area(s) as well.
Any help as to what I am doing wrong would be greatly appreciated.
Here is the code:
import wx
import sys
employees = [('Earl Boffo', 'Software'), ('Mildred Plotka', 'Software'), ('Sugar Kane', 'QA')]
classes = [('Python'), ('Java'), ('C#')]
class EmployeesPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
# create some sizers
mainSizer = wx.BoxSizer(wx.VERTICAL)
grid = wx.GridBagSizer(hgap=5, vgap=5)
#hSizer = wx.BoxSizer(wx.HORIZONTAL|wx.EXPAND)
hSizer = wx.BoxSizer(wx.HORIZONTAL)
panel = wx.Panel(self, -1)
self.list = wx.ListCtrl(panel, size=(100,100), style=wx.LC_REPORT)
self.list.InsertColumn(0, 'Name')
self.list.InsertColumn(1, 'Group')
for i in employees:
index = self.list.InsertStringItem(sys.maxint, i[0])
self.list.SetStringItem(index, 1, i[1])
# A button
self.button = wx.Button(self, label="Exit")
self.Bind(wx.EVT_BUTTON, self.OnClick,self.button)
self.list.Show(True)
# add the listctrl widget to the grid
grid.Add(self.list, pos=(0,0), flag=wx.EXPAND|wx.ALL)
# add the button to the grid
grid.Add(self.button, pos=(1,0))
# add a spacer to the sizer
grid.Add((10, 40), pos=(1,1))
# add grid to hSizer
hSizer.Add(grid, 0, wx.ALL, 5)
# add hSizer to main (v) sizer
mainSizer.Add(hSizer, 0, wx.ALL, 5)
self.SetSizerAndFit(mainSizer)
self.Show()
def EvtComboBox(self, event):
self.logger.AppendText('EvtComboBox: %s\n' % event.GetString())
def OnClick(self,event):
sys.exit(3)
app = wx.App(False)
frame = wx.Frame(None, title="Training Tracker", size=(700,500))
nb = wx.Notebook(frame)
nb.AddPage(EmployeesPanel(nb), "Employees")
frame.Show()
app.MainLoop()
Welcome to wxPython! It's actually a lot of fun once you get the hang of it. I almost never use the grid sizers as they're just a pain for simple layouts like this. If you have a grid like interface and you don't have controls that are going to stretch across cells, then it's great. Otherwise, I almost always use BoxSizers nested in each other. I simplified your code quite a bit to show the two widgets. Currently the list control only stretches horizontally. If you need it to go vertically too, then change the proportion from 0 to 1 in the sizer.Add part.
import wx
import sys
employees = [('Earl Boffo', 'Software'), ('Mildred Plotka', 'Software'), ('Sugar Kane', 'QA')]
classes = [('Python'), ('Java'), ('C#')]
class EmployeesPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
# create some sizers
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.list = wx.ListCtrl(self, size=(100,100), style=wx.LC_REPORT)
self.list.InsertColumn(0, 'Name')
self.list.InsertColumn(1, 'Group')
for i in employees:
index = self.list.InsertStringItem(sys.maxint, i[0])
self.list.SetStringItem(index, 1, i[1])
# A button
self.button = wx.Button(self, label="Exit")
self.Bind(wx.EVT_BUTTON, self.OnClick,self.button)
mainSizer.Add(self.list, 0, wx.EXPAND|wx.ALL, 5)
mainSizer.Add(self.button, 0, wx.ALL, 5)
self.SetSizer(mainSizer)
self.Show()
def EvtComboBox(self, event):
self.logger.AppendText('EvtComboBox: %s\n' % event.GetString())
def OnClick(self,event):
sys.exit(3)
app = wx.App(False)
frame = wx.Frame(None, title="Training Tracker", size=(700,500))
nb = wx.Notebook(frame)
nb.AddPage(EmployeesPanel(nb), "Employees")
frame.Show()
app.MainLoop()
I also think these articles might help you:
wxPython: wx.ListCtrl Tips and Tricks
wxPython: Using ObjectListView instead of a ListCtrl
http://wiki.wxpython.org/ListControls
I'm teaching myself (Wx) Python and got stuck. I want to create a child window that has a recurring set of information inside of my TestFrame, which contains most of my code. The problem is, it only shows one widget in my code. I am trying to figure out the following, in order of importance to me.
**Note that this code is an extension to this page.*
How can I allow multiple widgets in the "AddBox" class to appear correctly?
When my window is resized, it corrupts the button images as in the screen attached. How could I fix this?
How do you call/bind each of these dynamically created widgets?
Bonus: is the "OnSize" module needed here?
Thank you for your help. If allowed/appropriate, I'm willing to contribute $5 via Paypal to the winner if you PM me.
import wx
class AddBox(wx.Window):
def __init__(self, parent):
wx.Window.__init__(self, parent)
pbox = wx.BoxSizer(wx.VERTICAL)
controlback = wx.Button(self, label="Back")
controlforward = wx.Button(self, label="Forward")
pbox.AddMany([(controlback, 1, wx.ALL), (controlforward, 1, wx.ALL)])
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, size=(1000, 550))
self.Bind(wx.EVT_SIZE, self.OnSize)
pbox0 = wx.BoxSizer(wx.VERTICAL)
controlback0 = wx.Button(self, label="Back0")
controlforward0 = wx.Button(self, label="Forward0")
pbox0.AddMany([(controlback0, 1, wx.ALL), (controlforward0, 1, wx.ALL)])
pbox2 = wx.BoxSizer(wx.VERTICAL)
self.scrolling_window = wx.ScrolledWindow( self )
self.scrolling_window.SetScrollRate(1,6)
self.scrolling_window.EnableScrolling(True,True)
self.sizer_container = wx.BoxSizer( wx.VERTICAL )
self.sizer = wx.BoxSizer( wx.VERTICAL )
self.sizer_container.Add(self.sizer,1,wx.CENTER,wx.EXPAND)
self.child_windows = []
for i in range(0,8):
wind = AddBox(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)
#self.Layout() #not needed?
pbox2.AddMany([(self.sizer_container, 1, wx.ALL)])
def OnSize(self, event):
self.scrolling_window.SetSize(self.GetClientSize())
if __name__=='__main__':
app = wx.PySimpleApp()
f = TestFrame()
f.Show()
app.MainLoop()
The code here is pretty convoluted and hard to follow. You almost never need to use wx.Window. In fact, in almost 6 years of using wxPython, I have NEVER used it directly. PySimpleApp is deprecated as well. Anyway, I cleaned up the code a bunch and re-did it below. I'm not sure if this is what you're looking for or not though. Also note that I swapped out ScrolledWindow for ScrolledPanel as I think the latter is easier to use:
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 = []
for i in range(0,8):
wind = self.addBox()
self.sizer.Add(wind, 0, wx.CENTER|wx.ALL, 5)
self.scrolling_window.SetSizer(self.sizer)
mainSizer.Add(self.scrolling_window, 1, wx.EXPAND)
panel.SetSizer(mainSizer)
def addBox(self):
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)])
return pbox
def OnSize(self, event):
self.scrolling_window.SetSize(self.GetClientSize())
if __name__=='__main__':
app = wx.App(False)
f = TestFrame()
f.Show()
app.MainLoop()
You should also take a look at the Widget Inspection Tool. It will help you figure out layout problems like this: http://wiki.wxpython.org/Widget%20Inspection%20Tool
EDIT: I think the code above takes care of items #1 and 2. For #3, see the following article I wrote last year: http://www.blog.pythonlibrary.org/2011/09/20/wxpython-binding-multiple-widgets-to-the-same-handler/
Basically you bind in the loop itself and then in the event handler, you can use event.GetEventObject() to return the calling widget and set its value or whatever. As for your 4th question, I would say no, you don't need the OnSize method. You almost never need to override that in wxPython and I certainly didn't in my example code.
I just have an answer to 3 for now: Assign to each dynamically created widget an ID, store this ID in a dictionary ({id : widget }) and pass as the callback function a lambda-function or use functools.partial to pass the ID to the "real" callback. (of course you can pass the widget directly over the lambda-function, but I like to have instances to the created widgets somewhere, if I need to access these sometime)
def __init__(...):
self.event_dispatch = dict()
...
for id in xrange(500, 508):
wind = AddBox(self.scrolling_window, id)
...
self.event_dispatch[id] = wind
self.scrolling_window.Bind(wx.MY_EVENT, lambda evt: self.on_event(evt, id), id=id)
def on_event(event, id):
widget = self.event_dispatch[id]
# do something with widget
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.