Why lists cannot be used with wx.BoxSizer? - python

I wanted to use lists made by wx.BoxSizer elements to make my code more readable and easier to manage but for some reason wxpython cannot use this kind of lists.
So here I wrote a code that works well without using the lists:
import wx
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
vsizer = wx.BoxSizer(wx.VERTICAL)
hsizer1 = wx.BoxSizer(wx.HORIZONTAL)
hsizer2 = wx.BoxSizer(wx.HORIZONTAL)
hsizer3 = wx.BoxSizer(wx.HORIZONTAL)
ts = [wx.StaticText(self, -1, str(i)) for i in range(3)]
ct = [wx.TextCtrl(self, -1) for i in range(3)]
hsizer1.Add(ts[0], 1, wx.EXPAND)
hsizer2.Add(ts[1], 1, wx.EXPAND)
hsizer3.Add(ts[2], 1, wx.EXPAND)
hsizer1.Add(ct[0], 1, wx.EXPAND)
hsizer2.Add(ct[1], 1, wx.EXPAND)
hsizer3.Add(ct[2], 1, wx.EXPAND)
vsizer.Add(hsizer1, 1, wx.ALIGN_CENTER)
vsizer.Add(hsizer2, 1, wx.ALIGN_CENTER)
vsizer.Add(hsizer3, 1, wx.ALIGN_CENTER)
self.SetSizer(vsizer)
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
MyPanel(self)
self.Layout()
self.Show()
app = wx.App()
frame = MyFrame(None)
app.MainLoop()
With this code you'll obtain the desired layout.
But if I use lists and cycles everything goes wrong:
import wx
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
vsizer = wx.BoxSizer(wx.VERTICAL)
hsizer1 = [wx.BoxSizer(wx.HORIZONTAL)]*3
ts = [wx.StaticText(self, -1, str(i)) for i in range(3)]
ct = [wx.TextCtrl(self, -1) for i in range(3)]
for i in range(3):
hsizer1[i].Add(ts[i], 1, wx.EXPAND)
hsizer1[i].Add(ct[i], 1, wx.EXPAND)
for i in hsizer1:
vsizer.Add(i, 1, wx.ALIGN_CENTER)
self.SetSizer(vsizer)
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
MyPanel(self)
self.Layout()
self.Show()
app = wx.App()
frame = MyFrame(None)
app.MainLoop()
As you can see they should produce the very same output but the don't. My question then is: why lists cannot be used in this case?

The thing is, you have the list hsizer1, which contains the same object 3 times. So it does not quite matter if you add to hsizer[0], hsizer[1] or hsizer[2], you are always adding into the same sizer. To fix it:
hsizer1 = [wx.BoxSizer(wx.HORIZONTAL) for i in range(3)]
Or you could:
for i in range(3):
hsizer = wx.BoxSizer(wx.HORIZONTAL)
hsizer.Add(ts[i], 1, wx.EXPAND)
hsizer.Add(ct[i], 1, wx.EXPAND)
vsizer.Add(hsizer, 1, wx.ALIGN_CENTER)

Related

wxPython middle align items in sizer

I am dynamically changing the number of wx objects in my GUI. The code is working but the alignment of the objects is not perfect. The static text objects are not middle aligned with the text objects.
Notice that the word 'if' is not perfectly aligned with 'field name'.
Code:
import wx
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.numRows = 0
self.frame = parent
#create sizers
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
controlSizer = wx.BoxSizer(wx.HORIZONTAL)
self.colSizer = wx.BoxSizer(wx.VERTICAL)
self.addButton = wx.Button(self, label="Add")
self.addButton.Bind(wx.EVT_BUTTON, self.onAddWidget)
controlSizer.Add(self.addButton, 0, wx.CENTER|wx.ALL, 5)
self.removeButton = wx.Button(self, label="Remove")
self.removeButton.Bind(wx.EVT_BUTTON, self.onRemoveWidget)
controlSizer.Add(self.removeButton, 0, wx.CENTER|wx.ALL, 5)
self.mainSizer.Add(controlSizer, 0, wx.CENTER)
self.mainSizer.Add(self.colSizer, 0, wx.CENTER|wx.ALL, 10)
self.SetSizer(self.mainSizer)
#----------------------------------------------------------------------
def onAddWidget(self, event):
""""""
self.numRows += 1
#create the objects
rowSizer =wx.BoxSizer(wx.HORIZONTAL)
ifText = wx.StaticText(self, -1, 'If')
fieldBox1 = wx.TextCtrl(self, -1, 'Field Name')
dropDown1 = wx.Choice(self, -1, choices=['<', '<=', '=', '>=', '>'])
fieldBox2 = wx.TextCtrl(self, -1, 'Field Name')
thenText = wx.StaticText(self, -1, 'Then')
fieldBox3 = wx.TextCtrl(self, -1, 'Field Name')
#create a list of all the objects
objects = [ifText, fieldBox1, dropDown1, fieldBox2, thenText, fieldBox3]
#add object to row sizer
for myObject in objects:
rowSizer.Add(myObject, 0, wx.ALL, 5)
#add the objects to the sizer
self.colSizer.Add(rowSizer, 0, wx.ALL, 5)
self.frame.fSizer.Layout()
self.frame.Fit()
#----------------------------------------------------------------------
def onRemoveWidget(self, event):
""""""
if self.colSizer.GetChildren():
self.colSizer.Hide(self.numRows-1)
self.colSizer.Remove(self.numRows-1)
self.numRows -= 1
self.frame.fSizer.Layout()
self.frame.Fit()
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, parent=None, title="Add / Remove Buttons")
self.fSizer = wx.BoxSizer(wx.VERTICAL)
panel = MyPanel(self)
self.fSizer.Add(panel, 1, wx.EXPAND)
self.SetSizer(self.fSizer)
self.Fit()
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
del app
Please could someone advise on how I can fix this alignment issue.
Thanks a lot in advance!
Duh! It turns out I just needed to change the alignment when adding items to the row sizer.
rowSizer.Add(myObject, 0, wx.ALIGN_CENTER, 5)

wxPython: Hide a widget and remove the leftover space

I want to hide a widget in a sizer and not leave an empty space.
I do not want to remove the widget, because I want to show it again later.
Is there a way to hide a widget and have the shown widgets after it move over one space to fill the empty spot?
Here is a simple example of a space being left:
import wx
class MyPanel(wx.Panel):
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
sizer = wx.GridSizer(1, 3, 0, 0)
a = wx.Button(self, -1, 'a')
b = wx.Button(self, -1, 'b')
c = wx.Button(self, -1, 'c')
sizer.Add(a, 0, 0, 0)
sizer.Add(b, 0, 0, 0)
sizer.Add(c, 0, 0, 0)
b.Hide()
self.SetSizer(sizer)
class MyFrame(wx.Frame):
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, parent=None, title="Remove Spaces")
panel = MyPanel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(panel, 1, wx.EXPAND)
self.SetSizer(sizer)
self.Fit()
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
Do you have to use wx.GridSizer? Because it will give you a fixed layout, so even you hide a control the grid will still have that space displayed.
Have you considered using wx.GridBagSizer? Try this one:
class MyPanel(wx.Panel):
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.parent = parent
self.sizer = wx.GridBagSizer()
self.a = wx.Button(self, -1, 'a')
self.b = wx.Button(self, -1, 'b')
self.c = wx.Button(self, -1, 'c')
self.a.Bind(wx.EVT_BUTTON, self.button_clicked)
self.sizer.Add(self.a, pos=(0, 0))
self.sizer.Add(self.b, pos=(0, 1))
self.sizer.Add(self.c, pos=(0, 2))
self.SetSizer(self.sizer)
def button_clicked(self, event):
if self.b.IsShown():
self.b.Hide()
else:
self.b.Show()
self.parent.Fit()
Using FlexGridSizer:
class MyPanel(wx.Panel):
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.parent = parent
self.sizer = wx.FlexGridSizer(1, 3, 0, 0)
self.a = wx.Button(self, -1, 'a')
self.b = wx.Button(self, -1, 'b')
self.c = wx.Button(self, -1, 'c')
self.a.Bind(wx.EVT_BUTTON, self.button_clicked)
self.sizer.Add(self.a)
self.sizer.Add(self.b)
self.sizer.Add(self.c)
self.SetSizer(self.sizer)
def button_clicked(self, event):
if self.b.IsShown():
self.b.Hide()
else:
self.b.Show()
self.parent.Fit()

wxpython sizers expands issue

I have a screen which has 6 grids as follows:
I'm adding a 7th grid.
This causes the screen to look like:
however I want the screen to be:
I tried to do it with flexgrid but it's not working.
This is my code:
sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
sizer_2.AddSpacer(10)
sizer_4 = wx.BoxSizer(wx.VERTICAL)
sizer_4.Add(self.grid1, 1, wx.EXPAND, 0)
sizer_4.AddSpacer(20)
sizer_4.Add(self.grid4, 1, wx.EXPAND, 0)
sizer_4.AddSpacer(20)
sizer_4.Add(self.grid7, 1, wx.EXPAND, 0)
sizer_5 = wx.BoxSizer(wx.VERTICAL)
sizer_5.Add(self.grid2, 1, wx.EXPAND, 0)
sizer_5.AddSpacer(20)
sizer_5.Add(self.grid5, 1, wx.EXPAND, 0)
sizer_6 = wx.BoxSizer(wx.VERTICAL)
sizer_6.Add(self.grid3, 1, wx.EXPAND, 0)
sizer_6.AddSpacer(20)
sizer_6.Add(self.grid6, 1, wx.EXPAND, 0)
sizer_3=wx.BoxSizer(wx.HORIZONTAL)
sizer_3.AddSpacer(20)
sizer_3.Add(sizer_4, 1, wx.EXPAND, 0)
sizer_3.AddSpacer(20)
sizer_3.Add(sizer_5, 1, wx.EXPAND, 0)
sizer_3.AddSpacer(20)
sizer_3.Add(sizer_6, 1, wx.EXPAND, 0)
sizer_1= wx.BoxSizer(wx.VERTICAL)
sizer_1.AddSpacer(10)
sizer_1.Add(sizer_2)
sizer_1.AddSpacer(20)
sizer_1.Add(sizer_3, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
self.Layout()
What can I do?
I don't like the grid sizers because I find them pretty confusing. So I typically take something like this and draw boxes around the various groups. The boxes represent BoxSizers. Once I have that done, I know what to do and write my code accordingly. Here's how I would lay this out:
import wx
########################################################################
class GenericPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent, color):
"""Constructor"""
wx.Panel.__init__(self, parent, size=(200, 100))
self.SetBackgroundColour(color)
########################################################################
class MainPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
left_sizer = wx.BoxSizer(wx.VERTICAL)
for i in range(3):
panel = GenericPanel(self, 'red')
left_sizer.Add(panel, 0, wx.ALL, 5)
top_right_sizer = wx.BoxSizer(wx.HORIZONTAL)
for i in range(2):
panel = GenericPanel(self, 'green')
top_right_sizer.Add(panel, 0, wx.ALL, 5)
bottom_right_sizer = wx.BoxSizer(wx.HORIZONTAL)
for i in range(2):
panel = GenericPanel(self, 'blue')
bottom_right_sizer.Add(panel, 0, wx.ALL, 5)
stack_sizer = wx.BoxSizer(wx.VERTICAL)
stack_sizer.Add(top_right_sizer)
stack_sizer.Add(bottom_right_sizer)
main_sizer = wx.BoxSizer(wx.HORIZONTAL)
main_sizer.Add(left_sizer)
main_sizer.Add(stack_sizer)
main_sizer.AddStretchSpacer(prop=1)
self.SetSizer(main_sizer)
########################################################################
class MainFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title='Boxes', size=(700,400))
panel = MainPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()

wxpython how to recreate grid?

I want to recreate a grid. For example: old grid is 4x4, I want change it to 5x5.
Here is my code:
import wx
import wx.xrc
import wx.grid
class MyFrame2(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=wx.EmptyString, pos=wx.DefaultPosition,
size=wx.Size(500, 300), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL)
bSizer4 = wx.BoxSizer(wx.VERTICAL)
self.m_grid2 = wx.grid.Grid(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0)
# Grid
self.m_grid2.CreateGrid(4, 4)
bSizer4.Add(self.m_grid2, 0, wx.ALL, 5)
self.m_button3 = wx.Button(self, wx.ID_ANY, u"MyButton", wx.DefaultPosition, wx.DefaultSize, 0)
bSizer4.Add(self.m_button3, 0, wx.ALL, 5)
self.m_button3.Bind(wx.EVT_BUTTON, self.OnClick)
self.SetSizer(bSizer4)
self.Layout()
def OnClick(self, event):
self.m_grid2.CreateGrid(5, 5)
self.Layout()
app = wx.App()
frame = MyFrame2(None)
frame.Show(True)
app.MainLoop()
It raise an error when I run that:
File "C:\Python27\lib\site-packages\wx-3.0-msw\wx\grid.py", line 1221, in CreateGrid
return _grid.Grid_CreateGrid(*args, **kwargs)
wx._core.PyAssertionError: C++ assertion "!m_created" failed at ..\..\src\generic\grid.cpp(2325) in wxGrid::CreateGrid(): wxGrid::CreateGrid or wxGrid::SetTable called more than once
It seems I can't recreate this grid again. How can do this job?
If you don't want to Append the rows and columns, then you'll just have to recreate the grid itself. Here's a fairly simple demo that demonstrates how to do that:
import wx
import wx.grid as gridlib
########################################################################
class MyGrid(gridlib.Grid):
#----------------------------------------------------------------------
def __init__(self, parent, rows, cols):
gridlib.Grid.__init__(self, parent)
self.CreateGrid(rows, cols)
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.grid_created = False
row_sizer = wx.BoxSizer(wx.HORIZONTAL)
col_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
rows_lbl = wx.StaticText(self, label="Rows", size=(30, -1))
row_sizer.Add(rows_lbl, 0, wx.ALL|wx.CENTER, 5)
self.rows = wx.TextCtrl(self)
row_sizer.Add(self.rows, 0, wx.ALL|wx.EXPAND, 5)
cols_lbl = wx.StaticText(self, label="Cols", size=(30, -1))
col_sizer.Add(cols_lbl, 0, wx.ALL|wx.CENTER, 5)
self.cols = wx.TextCtrl(self)
col_sizer.Add(self.cols, 0, wx.ALL|wx.EXPAND, 5)
grid_btn = wx.Button(self, label="Create Grid")
grid_btn.Bind(wx.EVT_BUTTON, self.create_grid)
self.main_sizer.Add(row_sizer, 0, wx.EXPAND)
self.main_sizer.Add(col_sizer, 0, wx.EXPAND)
self.main_sizer.Add(grid_btn, 0, wx.ALL|wx.CENTER, 5)
self.SetSizer(self.main_sizer)
#----------------------------------------------------------------------
def create_grid(self, event):
""""""
rows = int( self.rows.GetValue() )
cols = int( self.cols.GetValue() )
if self.grid_created:
for child in self.main_sizer.GetChildren():
widget = child.GetWindow()
if isinstance(widget, gridlib.Grid):
self.main_sizer.Remove(widget)
grid = MyGrid(self, rows, cols)
self.main_sizer.Add(grid, 0, wx.ALL, 5)
self.grid_created = True
self.main_sizer.Layout()
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="grids", size=(800, 600))
panel = MyPanel(self)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
You may only call the CreateGrid function once. if you want to change the size, you need to use the functions AppendCols, AppendRows, DeleteCols or DeleteRows.
def OnClick(self, event):
self.m_grid2.AppendCols(1)
self.m_grid2.AppendRows(1)
self.Layout()
Lokla

wxpython widgets within not responding to events

I'm a newbie in wxpython and have been trying to put some widgets within books(treebook, notebook, choicebook) I have often ended up with some widgets placed inside containers not responding to events. I'm not sure what I'm doing wrong. Below is one of my code
import wx
class ChoicePanelTwo(wx.Panel):
def __init__(self, parent, seed):
wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
self.SetBackgroundColour('blue')
sizer = wx.BoxSizer(wx.HORIZONTAL)
self.List = wx.ListCtrl(self, -1, style=wx.LC_REPORT)
for i in range(seed):
self.List.InsertStringItem(i, str(i))
sizer.Add(self.List, 1, wx.ALL|wx.EXPAND, 5)
self.SetSizer(sizer)
class ChoicePanelOne(wx.Panel):
def __init__(self, parent, seed):
wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
self.SetBackgroundColour('green')
sizer = wx.BoxSizer(wx.HORIZONTAL)
self.RegisterList = wx.Choicebook(self, wx.ID_ANY)
sizer.Add(self.RegisterList, 1, wx.ALL|wx.EXPAND, 5)
for i in range(seed):
self.RegisterList.AddPage(ChoicePanelTwo(self, seed*50), str(i))
self.SetSizer(sizer)
class TreePanel(wx.Panel):
def __init__(self, parent, seed):
wx.Panel.__init__(self, parent, id=wx.ID_ANY)
self.SetBackgroundColour('cyan')
self.Choicbook = wx.Choicebook(self, wx.ID_ANY)
for i in range(seed):
self.Choicbook.AddPage(ChoicePanelOne(self, seed*2), str(i))
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.Choicbook, 1, wx.ALL|wx.EXPAND, 5)
self.SetSizer(sizer)
class AppFrame(wx.Frame):
""" The main frame of the application
"""
title = 'Application'
WindowSize = (1024, 768)
seed = 2
def __init__(self):
wx.Frame.__init__(self, None, -1, self.title, size=self.WindowSize, style=wx.MINIMIZE_BOX|wx.SYSTEM_MENU|wx.CAPTION|wx.CLOSE_BOX|wx.CLIP_CHILDREN)
self.create_main_panel()
def create_main_panel(self):
self.panel = TreePanel(self, self.seed)
if __name__ == '__main__':
app = wx.PySimpleApp()
app.frame = AppFrame()
app.frame.Show()
app.MainLoop()
In this example. The choice book and list does seem to be working. What is that I'm doing wrong ?
It's a parenting issue. You are setting all the ChoicePanelOne instance's parents as the TreePanel when it should be the ChoicBook. And you're doing the same thing in ChoicePanelOne where you create a whole bunch of ChoicePanelTwo's whose parents are ChoicePanelOne when they should be RegisterList. Check out the slightly changed code below:
import wx
class ChoicePanelTwo(wx.Panel):
def __init__(self, parent, seed):
wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
self.SetBackgroundColour('blue')
sizer = wx.BoxSizer(wx.HORIZONTAL)
self.List = wx.ListCtrl(self, -1, style=wx.LC_REPORT)
for i in range(seed):
self.List.InsertStringItem(i, str(i))
sizer.Add(self.List, 1, wx.ALL|wx.EXPAND, 5)
self.SetSizer(sizer)
class ChoicePanelOne(wx.Panel):
def __init__(self, parent, seed):
wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
self.SetBackgroundColour('green')
sizer = wx.BoxSizer(wx.HORIZONTAL)
self.RegisterList = wx.Choicebook(self, wx.ID_ANY)
sizer.Add(self.RegisterList, 1, wx.ALL|wx.EXPAND, 5)
for i in range(seed):
self.RegisterList.AddPage(ChoicePanelTwo(self.RegisterList, seed*50), str(i))
self.SetSizer(sizer)
class TreePanel(wx.Panel):
def __init__(self, parent, seed):
wx.Panel.__init__(self, parent, id=wx.ID_ANY)
self.SetBackgroundColour('cyan')
self.Choicbook = wx.Choicebook(self, wx.ID_ANY)
for i in range(seed):
self.Choicbook.AddPage(ChoicePanelOne(self.Choicbook, seed*2), str(i))
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.Choicbook, 1, wx.ALL|wx.EXPAND, 5)
self.SetSizer(sizer)
class AppFrame(wx.Frame):
""" The main frame of the application
"""
title = 'Application'
WindowSize = (1024, 768)
seed = 2
def __init__(self):
wx.Frame.__init__(self, None, -1, self.title, size=self.WindowSize,
style=wx.MINIMIZE_BOX|wx.SYSTEM_MENU|wx.CAPTION|wx.CLOSE_BOX|wx.CLIP_CHILDREN)
self.create_main_panel()
def create_main_panel(self):
self.panel = TreePanel(self, self.seed)
if __name__ == '__main__':
app = wx.PySimpleApp()
app.frame = AppFrame()
app.frame.Show()
app.MainLoop()
You should download the wxPython demo as it has lots of good examples in it. Also see the wiki or my blog article.
You could use the Widget Inspection Tool to help you diagnose the problem as well as it will show you what parent is where and how your sizers are laid out, among other things.

Categories

Resources