wxPython -- StaticBox Sizer resizing Static Boxes, and adding a box? - python

I have a for loop that creates one static box for each node in a dictionary and it puts them all in a panel, 3 to a row max, and spaced apart. Now I want to add text inside each static box, but when I went to do that, it shrunk the boxes, put them side by side off-center, and added an extra box to the end with nothing in it. I commented out the lines that caused this issue:
nodeSizer = wx.BoxSizer(wx.VERTICAL)
rowSizer = wx.BoxSizer(wx.HORIZONTAL)
nodeSizer.AddSpacer((0,25))
rowSizer.AddStretchSpacer(1)
for index, (key, value) in enumerate(self.addArgs["khzObj"].nodes.items(), start=1):
self.nodeBoxes[key] = wx.StaticBox(nodeTab, wx.ID_ANY, value.name, size=(nodeWidth,nodeHeight))
# boxSizer = wx.StaticBoxSizer(self.nodeBoxes[key], wx.VERTICAL)
# nodeInfo = "Node ID: {id}".format(id=value.id)
# nodeIdText = wx.StaticText(nodeTab, wx.ID_ANY, nodeInfo)
# nodeStateText = wx.StaticText(nodeTab, wx.ID_ANY, value.currentStateStr)
# boxSizer.Add(nodeIdText, proportion=1, flag=wx.ALIGN_CENTER)
# boxSizer.Add(nodeStateText, proportion=2, flag=wx.ALIGN_CENTER)
# NOTE: the next line usually adds the staticBox, not the boxSizer
# rowSizer.Add(boxSizer, proportion=10)
rowSizer.AddStretchSpacer(1)
if index % 3 == 0:
nodeSizer.Add(rowSizer, flag=wx.ALIGN_CENTER)
nodeSizer.AddSpacer((0,25))
rowSizer = wx.BoxSizer(wx.HORIZONTAL)
rowSizer.AddStretchSpacer(1)
Can anyone see what I'm doing wrong? Why are my boxes resizing and re-laying-out? I thought to use the box sizer as the horizontal sizer for the row, but that won't work because there are multiple static boxes.

Related

wxpython: StaticText vertically centered text with background

I've read so many questions similar to this but I'm caving in and making my own because nothing is working. Basically, I want to have my wx.StaticText have vertically centered text while resizing with the window. My understanding is that existing solutions don't work because I care about the background of the StaticText, and so I cannot simply vertically center the label itself in a sizer. I also see that it requires messy subclassing to have a transparent-background StaticText, so overlaying it on a panel sounds difficult.
Here is a minimal example (in my project the sizer has several other things in it):
import wx
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None, title='Sample')
sizer=wx.BoxSizer(wx.VERTICAL)
label = wx.StaticText(self, -1, 'PLEASE VERTICALLY CENTER ME ;(', style=wx.ALIGN_CENTRE | wx.ST_NO_AUTORESIZE)
label.SetMinSize((300,300))
label.SetBackgroundColour((255,0,0))
sizer.Add(label, 1, wx.EXPAND | wx.ALL, 10)
self.SetSizerAndFit(sizer)
if __name__ == '__main__':
app=wx.App()
frame=MyFrame()
frame.Show()
app.MainLoop()
Despite that, it's hard to accept it's not simple to vertically center text. What is the easiest way to have the text in the label be vertically centered?
Solution:
catalin's answer gave the concept necessary! Here is a full snippet for anyone else who encounters this problem. I added a button below the StaticText to demonstrate the vertical sizer remaining in control. The vertical sizer could be removed altogether if your application doesn't need it.
import wx
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None, title='Sample')
sizer=wx.BoxSizer(wx.VERTICAL) # "main" sizer
panel = wx.Panel(self) # panel just for centering label
panel.SetBackgroundColour((255,0,0))
label = wx.StaticText(panel, -1, 'PLEASE VERTICALLY CENTER ME ;(', style=wx.ALIGN_CENTRE | wx.ST_NO_AUTORESIZE)
hsizer=wx.BoxSizer(wx.HORIZONTAL) # sizer for the panel
hsizer.Add(label, 1, wx.ALIGN_CENTER_VERTICAL)
panel.SetSizer(hsizer)
sizer.Add(panel, 1, wx.EXPAND)
btn = wx.Button(self, -1, 'Button')
sizer.Add(btn, 0, wx.EXPAND)
self.SetSizerAndFit(sizer)
if __name__ == '__main__':
app=wx.App()
frame=MyFrame()
frame.Show()
app.MainLoop()
In c++ a quick way would look like this:
wxPanel* p = new wxPanel(this); // you don't need to put it in a sizer if it's the only child
p->SetBackgroundColour({ 255, 0, 0 }); // this will be inherited by children
wxStaticText* label = new wxStaticText(p, wxID_ANY, "PLEASE VERTICALLY CENTER ME ;(");
wxSizer* s = new wxBoxSizer(wxHORIZONTAL); // for vertical one, you'd need stretch-spacers, and could not use wxALIGN_CENTER_VERTICAL, see below
s->Add(label, wxSizerFlags(1).Align(wxALIGN_CENTER_VERTICAL));
p->SetSizer(s);
Mind that if you want the text to wrap upon resize, wxStaticText will probably not resize correctly, and you might need to replace it with something else.
Another example of the sizer concept.
The frame is broken up into component parts, with each part assigned its relevant sizer.
The component sizers are then put within a main (self) sizer, to put it all together.
In this case the components are text, that needs to be centered and a set of buttons to the right, vertically stacked.
Obviously, components and arrangements differ but the concept of breaking the design into parts, which is then assembled for final presentation, remains the same.
Indeed, complicated components often need to have the same principle applied, leaving you with a series of sub-assemblies before the final one, in the main sizer.
import wx
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None, title='Sample')
mainsizer=wx.BoxSizer(wx.HORIZONTAL) # "main" sizer
tsizer=wx.BoxSizer(wx.HORIZONTAL) # "text" sizer
bsizer=wx.BoxSizer(wx.VERTICAL) # "button" sizer
panel = wx.Panel(self) # panel for label
button_panel = wx.Panel(self) # panel for buttons
panel.SetBackgroundColour((190,0,0))
button_panel.SetBackgroundColour((160,0,0))
label = wx.StaticText(panel, -1, 'PLEASE VERTICALLY CENTER ME ;(')
btn1 = wx.Button(button_panel,-1, 'Button 1')
btn2 = wx.Button(button_panel,-1, 'Button 2')
btn3 = wx.Button(button_panel,-1, 'Button 3')
btn4 = wx.Button(button_panel,-1, 'Button 4')
tsizer.AddSpacer(10)
tsizer.Add(label, 0, wx.CENTER)
tsizer.AddSpacer(10) # Ensure gap between text and buttons
bsizer.Add(btn1)
bsizer.Add(btn2)
bsizer.Add(btn3)
bsizer.Add(btn4, 0, wx.BOTTOM, 35) # With space below
panel.SetSizer(tsizer)
button_panel.SetSizer(bsizer)
mainsizer.Add(panel, proportion=1, flag=wx.EXPAND) # panel to grow when resized
mainsizer.Add(button_panel, proportion=0, flag=wx.EXPAND) # panel to fill available space
self.SetSizerAndFit(mainsizer)
if __name__ == '__main__':
app=wx.App()
frame=MyFrame()
frame.Show()
app.MainLoop()

wxPython : Issues on windows using multiple sizers, but works well on linux

I am writing a small app that works very well on linux, but I have some trouble on windows. Here is the code sample:
import wx
#####################################################################
class Main(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="StackOverflow", pos=wx.DefaultPosition, size=(800,600))
self.SetMinSize( self.GetSize() )
p = wx.Panel(self)
nb = wx.Notebook(p)
page1 = AddToCollection(nb)
page2 = CollectionStatistics(nb)
nb.AddPage(page1, "Page 1")
nb.AddPage(page2, "Page 2")
# finally, put the notebook in a sizer for the panel to manage
# the layout
sizer = wx.BoxSizer()
sizer.Add(nb, 1, wx.EXPAND)
p.SetSizer(sizer)
#########################################################################
class CollectionStatistics(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
#########################################################################
class AddToCollection(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.v1_qty_list = [str(x) for x in range(9)]
self.v2_qty_list = [str(x) for x in range(9)]
self.sizername = wx.GridBagSizer(5, 5)
self.sizername.AddGrowableCol(0,0)
self.name_txt = wx.StaticText(self, label="Enter Name :")
self.sizername.Add(self.name_txt,(2,0),(1,1),wx.EXPAND)
self.name = wx.TextCtrl(self,style=wx.TE_PROCESS_ENTER,value=u"")
self.sizername.Add(self.name,(3,0),(1,1),wx.EXPAND)
self.Bind(wx.EVT_TEXT_ENTER, self.OnPressEnter, self.name)
self.SetSizerAndFit(self.sizername)
self.SetSizeHints(-1,self.GetSize().y,-1,self.GetSize().y )
##########################################################################
def OnPressEnter(self,event):
self.selected_name = self.name.GetValue()
self.AddToCol()
##########################################################################
def AddToCol(self):
self.sizerAdd = wx.GridBagSizer(5, 5)
self.sizerAdd.AddGrowableCol(0, 0)
self.name.Enable(False)
### Expansion
self.expansion = wx.Choice(self, -1, choices=['test 1', 'test 2'])
self.expansion.SetSelection(0)
self.sizerAdd.Add(self.expansion,(5,0),(1,6),wx.EXPAND)
### Quantities txt
self.v1_txt = wx.StaticText(self, label="V1 Quantity :")
self.sizerAdd.Add(self.v1_txt,(7,0),(1,1),wx.EXPAND)
self.v2_txt = wx.StaticText(self, label="V2 Quantity :")
self.sizerAdd.Add(self.v2_txt,(8,0),(1,1),wx.EXPAND)
### Quantities choices
self.v1_qty = wx.Choice(self, -1, choices=self.v1_qty_list)
self.v1_qty.SetSelection(0)
self.sizerAdd.Add(self.v1_qty,(7,5),(1,1),wx.EXPAND)
self.v2_qty = wx.Choice(self, -1, choices=self.v1_qty_list)
self.v2_qty.SetSelection(0)
self.sizerAdd.Add(self.v2_qty,(8,5),(1,1),wx.EXPAND)
### Ok Button
self.Add_btn = wx.Button(self, -1, "Add")
self.Add_btn.Bind(wx.EVT_BUTTON, self.OnAdd)
self.sizerAdd.Add(self.Add_btn,(9,5),(1,1),wx.EXPAND)
### Reset Button
self.Reset_btn = wx.Button(self, -1, "Reset")
self.Reset_btn.Bind(wx.EVT_BUTTON, self.OnResetPanel)
self.sizerAdd.Add(self.Reset_btn,(9,4),(1,1),wx.EXPAND)
self.SetSizerAndFit(self.sizerAdd)
self.SetSizeHints(-1,self.GetSize().y,-1,self.GetSize().y )
######################################################################
def OnResetPanel(self,event):
### Kill all children
self.expansion.Destroy()
self.v1_txt.Destroy()
self.v1_qty.Destroy()
self.v2_txt.Destroy()
self.v2_qty.Destroy()
self.Add_btn.Destroy()
self.Reset_btn.Destroy()
### Reinitialise sizer
self.name.Enable(True)
self.name.SetValue("")
######################################################################
def OnAdd(self,event):
print 'Add'
self.OnResetPanel(self)
######################################################################
######################################################################
if __name__ == "__main__":
app = wx.App()
Main().Show()
app.MainLoop()
Basically, I have a TextCtrl in a first sizer which is waiting for an entry. Once the user hits enter, several objects appear in a second sizer.
The issue on windows seems to come from the use of the two gridbagsizers (sizername and sizerAdd). After pressing enter (waited event in the __init__), the objects defined within the sizerAdd do not appear. When I extend the window where the script is running, these objects appear magically !
Any idea ?
EDIT : The code is now runnable
I think the problem in your code is these two lines at the end of your AddToCol method:
self.SetSizerAndFit(self.sizerAdd)
self.SetSizeHints(-1,self.GetSize().y,-1,self.GetSize().y )
At this point, you're changing the sizer of the AddToCollection panel from self.sizername to self.sizerAdd. The Enter Name: label and the textbox however are still within the self.sizername sizer. However, this sizer isn't the sizer for any window, nor has it been added to any other sizer.
Generally, in wxPython, every sizer should be set as the sizer for a window, or be added to another sizer. This other sizer would then be the sizer for a window, or be contained within another sizer, and so on. In your case, your self.sizername sizer ends up being neither, and in this situation I would expect unpredictable behaviour. If your code works on Linux then I would say that it happens to work by accident.
I can think of a few things you could do here:
Add self.sizerAdd as a child of self.sizername. This can be done by replacing the two lines above with
self.sizername.Add(self.sizerAdd,(4,0),(1,1),wx.EXPAND)
self.sizername.Layout()
In AddToCol, add the widgets directly to the self.sizername sizer instead of adding them to self.sizerAdd.
Create a wx.BoxSizer() with vertical orientation, set that to be the sizer for the AddToCollection panel, and add the self.sizername and self.sizerAdd sizers to your BoxSizer.
In all three cases, after creating the new widgets you will need to call the Layout() method on the top-level sizer, be it either self.sizername or the top-level BoxSizer. The code snippet under option 1 includes this line already.
Additionally, you may need to modify your OnResetPanel() method. If you chose options 1 or 3, you will need to remove the self.sizerAdd sizer from whichever sizer you added it to. For example, in option 1, you would add the line
self.sizername.Remove(self.sizerAdd)
Another approach would be for your AddToCol method to create all the widgets within a Panel and add that to the main panel at the end. Your AddToCol method would then need to create a child panel, add the extra controls as children of this panel instead of the main panel (self), set the sizer of the child panel to self.sizerAdd and finally add this panel to the self.sizername sizer.
def AddToCol(self):
self.sizerAdd = wx.GridBagSizer(5, 5)
self.sizerAdd.AddGrowableCol(0, 0)
self.name.Enable(False)
self.child_panel = wx.Panel(self)
### Expansion
self.expansion = wx.Choice(self.child_panel, -1, choices=['test 1', 'test 2'])
self.expansion.SetSelection(0)
self.sizerAdd.Add(self.expansion,(5,0),(1,6),wx.EXPAND)
# Create other widgets as before but with the parent set to self.child_panel
# instead of self.
self.child_panel.SetSizer(self.sizerAdd)
self.sizername.Add(self.child_panel,(4,0),(1,1),wx.EXPAND)
self.sizername.Layout()
You would then also need to replace the line
self.sizername.Remove(self.sizerAdd)
in OnResetPanel() with the two lines:
self.sizername.Remove(self.child_panel)
self.child_panel.Destroy()
One thing which bugged me about my approach 1 above was that I saw the widgets briefly appear in the top-left corner before appearing in the correct place. This adaptation fixes this problem and so makes the GUI behave itself a bit better. I couldn't reproduce your black area issue you mention in your comment, but hopefully this approach fixes your problem as well.

What makes a wxpython TextCtrl fill horizontally

I have a TextCtrl in a horizontal BoxSizer. It is added with a weight of 1 (next to a button with a weight of 0). When I run the program, the TextCtrl will remain at a fixed width with empty space next to it, regardless of the size of the window. Why?
Here is the code:
wxapp = wx.App()
wxFrame = wx.Frame(None, -1 , 'Switch Time Zones', (0,0),(340,64))
wxFrame.Centre()
mainBox = wx.BoxSizer(wx.VERTICAL)
wxFrame.SetSizer(mainBox)
...#A few things are added to mainBox
outputBox = wx.BoxSizer(wx.HORIZONTAL)
mainBox.Add(outputBox, 1, wx.ALIGN_BOTTOM)
...#A few things are added to other boxes
submitButton = wx.Button(wxFrame,label='solve:')
outputBox.Add(submitButton,0)
outputText = wx.TextCtrl(wxFrame)
outputText.SetEditable(False)
outputBox.Add(outputText,1)
wxFrame.Show()
wxapp.MainLoop()
mainBox.Add(outputBox, 1, wx.ALIGN_BOTTOM|wx.EXPAND)

wxPython: How do you specify the width of a specific column using one of the wx.sizer classes?

I'm attempting to set up something like this:
However, I'm having a bunch of trouble in figuring it out. I made this general purpose function to wrap a list of objects in a wx.gridsizer, and then add that to a wx.StaticBoxSizer to get the border around everything. Then it return the staticBox sizer to main to be added to the main vertical boxsizer.
def buildHorizontalSizer(self, objects, label=None):
if label:
box = wx.StaticBox(self.panel, -1, label)
# for i in dir(box):
# print i
sizer = wx.StaticBoxSizer(box, wx.HORIZONTAL)
else:
sizer = wx.BoxSizer(wx.HORIZONTAL)
grid = wx.GridBagSizer(hgap=3, vgap=0)
for i in range(len(objects)):
if i==0:
grid.Add(objects[i], flag=wx.ALIGN_RIGHT)
else:
grid.Add(objects[i], flag=wx.ALIGN_LEFT)
sizer.Add(grid)
return sizer
In each field, from left to right, there is a StaticText, TextCtrl, and then a Button.
How do I configure the cells so that they have different widths?
use
objects[i].SetMinSize((width,height))
Should do what you want ... you may have to also call
objects[i].SetSize((width,height))
before adding it to the sizer

Grid and Sizers in wxPython

So I've got an issue with Grid and Sizers in wxPython, if i include this table the formating seems to mess up somehow, the entire window appears to have the correct size and the items appear at the correct locations.
But when this table is included the inner light grey which I guess is the panel ?
but items below the table are hidden because of this dark grey field
removing the setcolsize calls doesn't fix it either
Thanks for any replies
wx.Frame.__init__(self, parent, title=title)
panel = wx.Panel(self)
grid = wx.GridBagSizer(hgap=5, vgap=5)
# some other items comes before this
# FileGrid
fileF = wx.grid.Grid(panel)
fileF.CreateGrid(2,3)
fileF.SetColLabelSize(0)
fileF.SetRowLabelSize(0)
fileF.SetCellValue(0, 0, "Old")
fileF.SetCellValue(0, 1, "New")
fileF.SetCellValue(0, 2, "Update?")
grid.Add(fileF, pos=(5,0))
# FileGrid end
runBtn = wx.Button(panel, wx.ID_APPLY, "Apply")
self.Bind(wx.EVT_BUTTON, self.applyScRen, runBtn)
grid.Add(runBtn, pos=(6,0))
self.SetSizerAndFit(grid)
You do not provide the full frame structure but this should get you on the right track.
Change the last line to:
panel.SetSizerAndFit(grid)
That already improves matters. The container for the sizer is the panel and that, in turn, is contained inside the frame.
EDIT
This pastebin has the full working version. It produces this image followed by the key change I made to your code:
#panel.CreateStatusBar()
vSizer.Add(grid, 0, wx.ALL, 5)
panel.SetSizerAndFit(grid)
self.CreateStatusBar()
self.SetInitialSize()
self.Show(True)

Categories

Resources