wxPython: TextCtrl in pop up window - python

I have created a pop up window, but the TextCtrl is not fully expanded to fill up the window. It works great if I use StaticText instead, (but if content too large then I would need the scroll bar, that is why I am using TextCtrl now). Please provide some guidance.
self.description = WindowPopup(self, wx.SIMPLE_BORDER, content)
btn = event.GetEventObject()
dw = wx.DisplaySize()[0]
width = self.description.GetSize()[0]
y = btn.ClientToScreen((0,0))[1]
height = btn.GetSize()[1]
x = dw - width - 20 - 10
self.description.Position((x, y), (0, height))
self.description.Show(True)
class WindowPopup(wx.PopupWindow):
""" Pops up a window to provide description for the selection """
def __init__(self, parent, style, content):
wx.PopupWindow.__init__(self, parent, style)
self.SetSize((700, 287))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
st = wx.TextCtrl(self, -1, style = wx.TE_MULTILINE | wx.TE_READONLY)
st.SetValue(content)
sizer.Add(st, 0, wx.EXPAND)
panel.SetSizer(sizer)

I suspect your problem is that the panel is not as big as the popupwindow ... so even though the textfield is expanding to fill its sizer area it is not filling the popup its self.
try using something like
def __init__(...):
...
self.SetMinSize((700,287))
sizer2 = wx.BoxSizer()
sizer2.Add(panel)
self.SetSizer(sizer2)
also make sure that you are calling layout on it at some point (note this is totally untested... so it may need some tweeks, or even worse just be wrong...)

The actual answer is:
sizer = wx.BoxSizer(wx.VERTICAL)
st = wx.TextCtrl(self, -1, style = wx.TE_MULTILINE | wx.TE_READONLY, size = (500, 174))
st.SetValue(content)
self.SetSize((500, 174))
sizer.Add(st, 0, wx.EXPAND)
self.SetSizer(sizer)
self.Layout()
self.Show(True)
Credits to Joran for noticing Layout().
PopupWindow does not require an additional panel, because the window itself can have sizer set to it. This has been realized by using the wxPython Widget Inspection Tool.
Make sure TextCtrl and PopupWindow have the same size.

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()

buttons and a refreshing display area in wxpython

I am a humanities teacher, trying to adapt a simple app to help teachers manage classroom interaction - it takes attendance, then allows for calling on random attending students in class, or breaking them into groups, or recording an excused absence, and so on. I've long had a working version in PHP / MySQL running locally on my laptop, but I want to make it a portable python / sqlite app, with an eye toward releasing into the wild for other teachers to use and improve.
But I'm totally new to wxPython, and not really strong on object-oriented programming generally. I have spent hours reading (or skimming through) a fair number of tutorials and introductions and StackOverflow questions, and I've played with wxFormBuilder, but I do not feel like I'm making progress - I'm still quite confused about panels and sizers and layouts, and which bits should belong to what parents.
I think if I could just get this minimal version working, that would go a long way toward my real app. The toolbar-like buttons along the top seem to work fine, but I'd like a "display" area below with a minimal (but expandable) vertical size, a centered text area, and a row of 2-3 buttons in the display area below that text. This display area should change and clear depending on the button pushed above. Here's what I've got:
#!/usr/bin/env python3
import wx
class MainWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(-1,-1))
# self.panel = wx.Panel(self, size=(-1,300))
# buttons bar
self.top_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.btn_left = wx.Button(self, -1, label="left")
self.btn_right = wx.Button(self, -1, label="right")
self.btn_left.Bind(wx.EVT_BUTTON, self.OnLeft)
self.btn_right.Bind(wx.EVT_BUTTON, self.OnRight)
self.top_button_sizer.Add(self.btn_left, 1, wx.EXPAND)
self.top_button_sizer.Add(self.btn_right, 1, wx.EXPAND)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.display_sizer = wx.BoxSizer(wx.VERTICAL)
self.text_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.display_text = wx.StaticText(self, label="Push a button!")
self.display_buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.text_sizer.Add(self.display_text, 0, wx.ALIGN_CENTER, 5)
self.display_sizer.Add(self.text_sizer, 1, wx.ALIGN_CENTER, 5)
self.display_sizer.Add(self.display_buttons_sizer, 0, wx.EXPAND)
self.sizer.Add(self.top_button_sizer, 0, wx.TOP | wx.EXPAND)
# self.sizer.Add(self.panel)
self.sizer.Add(self.display_sizer, 1, wx.EXPAND)
self.SetSizerAndFit(self.sizer)
self.Show()
def OnLeft(self,e):
# for child in self.display_buttons_sizer.GetChildren():
# child.Destroy()
# ^ my attempt to "clear" causes a SegFault
for child in self.display_buttons_sizer.GetChildren():
child.Destroy()
self.display_text.SetLabel("Hey lefty!")
self.btn_hey = wx.Button(self, -1, label="Hey yourself lefty.")
self.btn_whoa = wx.Button(self, -1, label="Whoa there lefty.")
self.display_buttons_sizer.Add(self.btn_hey, 1, wx.EXPAND)
self.display_buttons_sizer.Add(self.btn_whoa, 1, wx.EXPAND)
self.sizer.Layout()
def OnRight(self,e):
# for child in self.display_buttons_sizer.GetChildren():
# child.Destroy()
# ^ my attempt to "clear" causes a SegFault!
self.display_text.SetLabel("Hey righty!")
self.btn_hey = wx.Button(self, -1, label="Hey yourself righty.")
self.btn_whoa = wx.Button(self, -1, label="Whoa there righty.")
self.display_buttons_sizer.Add(self.btn_hey, 1, wx.EXPAND)
self.display_buttons_sizer.Add(self.btn_whoa, 1, wx.EXPAND)
self.sizer.Layout()
app = wx.App(False)
frame = MainWindow(None, "MWE")
app.MainLoop()
(I'm sure this is abhorrent code in lots of ways.) Should I put in a panel somewhere? Where, and how? Would it be easier to learn about "notebooks" instead of using my top buttons? How do I properly clear what's in the "display" area below for the next button push?
Bonus points: the most complicated of the top buttons on my real app is for taking attendance. It would display many lines of text (the students in that class, which I get from sqlite; I've worked that part out I think) with radio buttons for present / absent next to each line, and then record in the database. Hints about this would also be appreciated.
Thanks in advance for your patience.
You could do this sort of thing several different ways. You can swap out panels when a button is pushed or you could just use a wx.Notebook which is basically the same idea. I wrote up a tutorial on panel switching here that you might find helpful:
https://www.blog.pythonlibrary.org/2010/06/16/wxpython-how-to-switch-between-panels/
It uses a menu instead of buttons, but that wouldn't be hard to change. Here's a simple example that doesn't do panel switching, but does show how to add a multiline text control that clears itself when a button is pressed:
import wx
class MainPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
left_button = wx.Button(self, label='Left')
left_button.Bind(wx.EVT_BUTTON, self.on_left)
btn_sizer.Add(left_button, 0, wx.ALL, 5)
right_button = wx.Button(self, label='Right')
right_button.Bind(wx.EVT_BUTTON, self.on_right)
btn_sizer.Add(right_button, 0, wx.ALL, 5)
self.text_ctrl = wx.TextCtrl(self, style=wx.TE_MULTILINE)
self.main_sizer.Add(btn_sizer, 0, wx.CENTER)
self.main_sizer.Add(self.text_ctrl, 1, wx.ALL|wx.EXPAND, 5)
self.SetSizer(self.main_sizer)
def on_left(self, event):
self.text_ctrl.Clear()
self.text_ctrl.SetValue('Left')
def on_right(self, event):
self.text_ctrl.Clear()
self.text_ctrl.SetValue('Right')
class MainWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(-1,-1))
panel = MainPanel(self)
self.Show()
app = wx.App(False)
frame = MainWindow(None, "MWE")
app.MainLoop()
This also demonstrates how to add a panel, which is recommended as panels give you the right "look" on all platforms and it also enables tabbing between controls.
You could easily add another set of buttons to the bottom by using the same concepts shown here and then just adding the second set of button's sizer to the main sizer.
If you want to do lines of text with some kind of radio or check button, I would recommend using a wx.ListCtrl or (better), ObjectListView (https://objectlistview-python-edition.readthedocs.io/en/latest/recipes.html#recipe-checkbox) (see also https://www.blog.pythonlibrary.org/2009/12/23/wxpython-using-objectlistview-instead-of-a-listctrl/)
You can tie an event to the checkbox that can then update your database appropriately.

wxPython widgets are one on top of the other

I'm bulding a wxpython GUI.
When I'm running the code all the components are in the left-top corner, on top of each other.
When I resize the window, they align as planned.
The window before resize
The window after resize
How can I fix this?
my code:
class Screen(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(900,500))
self.SetBackgroundColour("#E4F1FE")
self.Show(True)
self.InitUI()
def InitUI(self):
pnlMain = wx.Panel(self, size=(900,500))
# Setup Font
font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
font.SetPointSize(9)
# Setup horizontal box sizer
self.bsMain = wx.BoxSizer(wx.HORIZONTAL)
self.bsMain.SetDimension(0,0,900,500)
# Setup LEFT box sizer
self.bsLeft = wx.BoxSizer(wx.VERTICAL)
self.bsLeft.SetMinSize((3*(self.GetSize()[0]/4),self.GetSize()[1]))
# Make add button
btnAdd = wx.Button(pnlMain, label="+", size=(50,50))
# Add all the components to the LEFT sizer
self.bsLeft.Add(btnAdd, flag = wx.ALIGN_BOTTOM | wx.ALIGN_LEFT )
# Setup RIGHT bsMain sizer
self.bsRight = wx.BoxSizer(wx.VERTICAL)
self.bsRight.SetMinSize((self.GetSize()[0]/4,self.GetSize()[1]))
# Make users headline
stUsers = wx.StaticText(pnlMain, label="USERS")
stUsers.SetFont(font)
# Make users list control
lcUsers = wx.ListCtrl(pnlMain,style=wx.LC_REPORT|wx.SUNKEN_BORDER)
lcUsers.Show(True)
lcUsers.InsertColumn(0,"user")
lcUsers.InsertColumn(1,"status")
# Add all the components to the RIGHT sizer
self.bsRight.Add((-1,10))
self.bsRight.Add(stUsers, flag=wx.LEFT | wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTRE, border=5)
self.bsRight.Add((-1,10))
self.bsRight.Add(lcUsers, flag=wx.EXPAND)
# Add the vertical sizers to the horizontal sizer
self.bsMain.Add(self.bsLeft)
self.bsMain.Add(self.bsRight)
# Add the vertical sizer to the panel
pnlMain.SetSizer(self.bsMain)
This is a common problem for new wxPython programmers. The solution is almost always a call to the top level sizer's Layout method. Occasionally you'll need to call the top-level parent's Layout instead. In your case, either will work. You can add:
pnlMain.Layout()
or
self.bsMain.Layout()
to the bottom of InitUI method and it should force your widgets to redraw in their correct locations. I've seen some widgets behave badly because they start out with a zero size until their shown. In those cases, I have usually needed to use wx.CallAfter to call Layout. Anyway, here's a complete example using your code:
import wx
class Screen(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(900,500))
self.SetBackgroundColour("#E4F1FE")
self.Show(True)
self.InitUI()
def InitUI(self):
pnlMain = wx.Panel(self, size=(900,500))
# Setup Font
font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
font.SetPointSize(9)
# Setup horizontal box sizer
self.bsMain = wx.BoxSizer(wx.HORIZONTAL)
self.bsMain.SetDimension(0,0,900,500)
# Setup LEFT box sizer
self.bsLeft = wx.BoxSizer(wx.VERTICAL)
self.bsLeft.SetMinSize((3*(self.GetSize()[0]/4),self.GetSize()[1]))
# Make add button
btnAdd = wx.Button(pnlMain, label="+", size=(50,50))
# Add all the components to the LEFT sizer
self.bsLeft.Add(btnAdd, flag = wx.ALIGN_BOTTOM | wx.ALIGN_LEFT )
# Setup RIGHT bsMain sizer
self.bsRight = wx.BoxSizer(wx.VERTICAL)
self.bsRight.SetMinSize((self.GetSize()[0]/4,self.GetSize()[1]))
# Make users headline
stUsers = wx.StaticText(pnlMain, label="USERS")
stUsers.SetFont(font)
# Make users list control
lcUsers = wx.ListCtrl(pnlMain,style=wx.LC_REPORT|wx.SUNKEN_BORDER)
lcUsers.Show(True)
lcUsers.InsertColumn(0,"user")
lcUsers.InsertColumn(1,"status")
# Add all the components to the RIGHT sizer
self.bsRight.Add((-1,10))
self.bsRight.Add(stUsers, flag=wx.LEFT | wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTRE, border=5)
self.bsRight.Add((-1,10))
self.bsRight.Add(lcUsers, flag=wx.EXPAND)
# Add the vertical sizers to the horizontal sizer
self.bsMain.Add(self.bsLeft)
self.bsMain.Add(self.bsRight)
# Add the vertical sizer to the panel
pnlMain.SetSizer(self.bsMain)
self.bsMain.Layout()
if __name__ == '__main__':
app = wx.App(False)
frame = Screen(None, 'Layout')
app.MainLoop()
Note: I've rarely needed to call Refresh. I think Layout does so automatically.

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.

ScrolledPanel with vertical scrollbar only and WrapSizer

I uses a WrapSizer in order to have an automatic layout (as thumbnail gallery) like this (see screenshot on the left) :
I would like that if there are two many elements, a (vertical only)-ScrollBar is added on the panel (see right screenshot). How to add such a vertical scrollbar to a panel using a WrapSizer?
I tried by mixing WrapSizer and ScrolledPanel, but I cannot get the desired layout.
class MyPanel(scrolled.ScrolledPanel):
def __init__(self, parent):
scrolled.ScrolledPanel.__init__(self, parent)
self.SetBackgroundColour('#f8f8f8')
sizer = wx.WrapSizer()
self.SetupScrolling()
# add some widgets btn1, btn2, etc. in the WrapSizer
sizer.Add(btn1, 0, wx.ALL, 10)
sizer.Add(btn2, 0, wx.ALL, 10)
Solution:
reset the width of the scroll panel virtual size to the displayable size.
import wx
import wx.lib.scrolledpanel as scrolled
class MyPanel(scrolled.ScrolledPanel):
def __init__(self, parent):
scrolled.ScrolledPanel.__init__(self, parent, style=wx.VSCROLL)
self.SetBackgroundColour('#f8f8f8')
self.sizer = wx.WrapSizer()
self.SetupScrolling(scroll_x = False)
self.parent = parent
self.addButton(self.sizer , 10)
self.SetSizer(self.sizer )
self.Bind(wx.EVT_SIZE, self.onSize)
def onSize(self, evt):
size = self.GetSize()
vsize = self.GetVirtualSize()
self.SetVirtualSize((size[0], vsize[1]))
evt.Skip()
def addButton(self, sizer, num):
for i in range(1, num):
btn =wx.Button( self, wx.ID_ANY, "btn"+str(i), wx.DefaultPosition, wx.DefaultSize, 0 )
sizer.Add(btn, 0, wx.ALL, 10)
if __name__=='__main__':
app = wx.App(redirect=False)
frame = wx.Frame(None)
MyPanel(frame)
frame.Show()
app.MainLoop()
It looks like you just forgot to include
self.SetSizer(sizer)
Since the WrapSizer takes the whole frame, I think that will work. Also, instead of SetupScrolling, you can use
self.SetScrollRate(horiz, vert)
to specify the increment (in pixels, i think) of the scroll, and that should work.
I can't test it here right now though, and WrapSizers are a little weird - they sometimes have trouble figuring out their proper size. You may need to wrap it in a BoxSizer going the other direction.

Categories

Resources