wxPython how to move items to new row if they become hidden? - python

I have a collection of buttons arranged in a row.
Here is a simplified example of my code:
import wx
app = wx.App(False)
frame = wx.Frame(None)
panel = wx.Panel(frame, wx.ID_ANY)
box = wx.BoxSizer(wx.HORIZONTAL)
for i in range(10):
btn = wx.Button(panel, wx.ID_ANY, "Test")
box.Add(btn, 1, wx.FIXED_MINSIZE)
panel.SetSizer(box)
frame.Show()
app.MainLoop()
Making the window smaller leads to the buttons being hidden, as would be expected. However, I want the buttons to retain their dimensions and be moved to a second row instead of shrinking and finally disappearing. I have tried using different settings and different types of sizers but with no avail. How can I go about doing this?
Any help would be greatly appreciated.

I just found out that wxPython has a sizer that does this. For some reason I didn't notice it on the docs before. For anyone stumbling on this in the future, you'll want to use wx.WrapSizer instead of wx.BoxSizer.

Related

wxPython - Button size being ignored on OSX

I'm taking the first steps to move from .NET to Python but I'm already having a few headaches regarding the GUI design.
For some reason, passing the size attribute to a wx.Button seems to be kind of ignored. And I say "kind of" because the actual space seems to change but the actual button keeps occupying the same space:
import wx
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
self.InitUI()
def InitUI(self):
self.SetSize((800, 600))
self.SetTitle('Main Menu')
self.Centre()
self.Show(True)
''' Fill the form '''
self.lblUsername = wx.StaticText(self, size=(80, -1), pos=(20,20), label="Username:" )
self.txtUsername = wx.TextCtrl(self, size=(140, -1), pos=(100,20), style=wx.TE_PROCESS_ENTER)
self.lblPassword = wx.StaticText(self, size=(80, -1), pos=(20,50), label="Password:" )
self.txtPassword = wx.TextCtrl(self, size=(140, -1), pos=(100,50), style=wx.TE_PROCESS_ENTER)
self.btnOK = wx.Button( self, label="OK", pos=(260, 16), size=(50,50))
self.btnOK.Bind(wx.EVT_BUTTON, self.onClickOK)
self.statusbar = self.CreateStatusBar()
self.statusbar.SetStatusText('Ready')
def onClickOK(self, e):
print "Button triggered"
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
No matter what size I set, the Button won't stretch (it will be centered as if all the space was actually being used, but will still be small).
Can anyone spot what am I doing wrong?
This is a limit imposed by OSX. The way the native button widget is drawn only allows it to be stretched horizontally, and the vertical size is fixed. Or rather, as you've discovered, the widget itself can be larger than normal vertically, but it will only draw itself at a fixed height within that space. It seems less neccessary with modern versions of OSX, but if you look at buttons in OSX from a few years ago you can probably see why this is so. The esthetic graphical effect of the "tic-tack" or "capsule" buttons would be totally ruined if they were a non-standard vertical size, causing the images used to draw the buttons to be stretched. wxWidgets follows the native plaform look and feel standards where possible, in this case it happens that Apple's standard is imposed upon us and wx can't offer the same level of flexibility that it usually does.
You do have some options however if you really want taller than normal buttons. The native widgets have a few different standard sizes, which you can select using the SetWindowVariant method, although I don't think the variants would get as tall as you want. Or you could use a generic button widget instead of a native one, such as wx.lib.buttons.ThemedGenButton.
Same problem in my little Software EventSoundControl.
Just a workaround: Use a multiline label and sizes of wxButton will work as desired!
If you want the button to stretch when you resize the frame, then you cannot use static sizes and positioning. You will need to put your widgets in a sizer. Then the sizer will manage the position / size of the widget(s) as you change the size of the frame. There are many examples on the wxPython wiki that demonstrate how to use sizers. You might also find the following tutorial helpful:
http://zetcode.com/wxpython/layout/

Resizing wxPython Widgets

How do I change the size of wxPython widget after its creation? I am trying to dynamically change the width of a ComboBoxCtrl widget based on the elements in the popup. For the sake of simplicity lets say we create a button and try to resize it:
btn = wx.Button(self, wx.ID_ANY, 'Start', size=(25,-1))
btn.SetSize(wx.Size(25,25))
self.Layout()
In the above case, the new 25x25 size does not take. I can only get SetSize to work for a panel. I am assuming there is another call I should be using. How can I change the size of widget after creation?
The SetMinSize command does the trick.
btn = wx.Button(self, wx.ID_ANY, 'Start', size=(25,-1))
btn.SetMinSize(wx.Size(25,25))
self.Layout()

wxPython minimal size of Frame with a Panel

wxpython 2.8.11.0, python 2.7
If i put some Sizer with some controls directly into a Frame like
import wx
app=wx.App()
frm = wx.Frame(None, title='title')
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(wx.SpinCtrl(frm))
sizer.Add(wx.SpinCtrl(frm))
frm.SetSizerAndFit(sizer)
frm.Show()
app.MainLoop()
the Frame will automagically have a correct minimum size to contain the Sizer and it is not possible to make it smaller.
If there is a Panel in between (as needed for tabbing between controls) this does not work, the window can be made too small.
import wx
app=wx.App()
frm = wx.Frame(None, title='title')
pan = wx.Panel(frm)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(wx.SpinCtrl(pan))
sizer.Add(wx.SpinCtrl(pan))
pan.SetSizerAndFit(sizer)
frm.Show()
app.MainLoop()
Additionally frm.Fit() and frm.SetMinSize(frm.GetEffectiveMinSize()) are required to get the same behavior. Full Code:
import wx
app=wx.App()
frm = wx.Frame(None, title='title')
pan = wx.Panel(frm)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(wx.SpinCtrl(pan))
sizer.Add(wx.SpinCtrl(pan))
pan.SetSizerAndFit(sizer)
frm.Fit()
frm.SetMinSize(frm.GetEffectiveMinSize())
frm.Show()
app.MainLoop()
I am okay with frm.Fit(), but i dislike frm.SetMinSize(frm.GetEffectiveMinSize()). Isn't there a better solution so that the Frame automatically considers the minimum size of the Panel just like with the Sizer before? I'm considered with what happens if the EffectiveMinSize changes after another control is added to the sizer.
Edit:
Apparently
panel.SetSizerAndFit(sizer)
frm.Fit()
frm.SetMinSize(frm.GetEffectiveMinSize())
should be replaced by
panel.SetSizer(sizer)
sizer.SetSizeHints(frm)
which looks somewhat cleaner. So in total this looks like
import wx
app=wx.App()
frm = wx.Frame(None, title='title')
pan = wx.Panel(frm)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(wx.SpinCtrl(pan))
sizer.Add(wx.SpinCtrl(pan))
pan.SetSizer(sizer)
sizer.SetSizeHints(frm)
frm.Show()
app.MainLoop()
Is this the preferred way to do this?
If widgets are added later on even the first approach with the Sizer directly on the Frame doesn't get it right without intervention, so i think that is a totally different question.
The most elegant way (IMHO) is given in phineas' answer. It is slightly inefficient to have an extra otherwise unneeded sizer but I don't think it can really be noticeable.
Some people do call SetSizeHints() manually, as in your last example, and this works as well and might be more clear (whenever I use an extra sizer I feel the need to leave a comment explaining why it shouldn't be removed).
Unfortunately there is no better way and I am not sure if we can even add one because you need to do something with all of the panel, the sizer and the frame and this means that you can't do it with a single method call without passing unrelated parameters to it. I.e. we could have something like
panel.SetSizerAndFitParent(sizer, frame);
but it's not really clear whether this would be better.
What about introducing a sizer that manages the frame content (e.g. the panel)? Since you didn't include a minimal example, I haven't tested it yet.
frameSizer = wx.BoxSizer(wx.VERTICAL)
frameSizer.Add(panel)
frm.SetSizerAndFit(frameSizer)

How to add widgets inside a StaticBox, wxPython?

it is possible to add things into a StaticBox, in wxPython?
I know this is a rather simple question however I cannot seem to find anything on Google.
I would like to actually be able to add things into the StaticBox, such as buttons.
Looking at the code from one of my old projects, it looks like I used a StaticBoxSizer, and added the elements to this sizer.
Quick test:
app = wx.App(redirect=False)
frame = wx.Frame(None)
static_box = wx.StaticBox(frame, label='Label')
sizer = wx.StaticBoxSizer(static_box, wx.VERTICAL)
for i in range(5):
sizer.Add(wx.Button(frame, label='Button ' + str(i)))
frame.Sizer = sizer
frame.Sizer.Fit(frame)
frame.Show()
app.MainLoop()

How can I get consistent behavior on my wxPython StyledTextControl?

I am having a problem with wxPython. I am attempting to have a scrollable window without a visible scroll bar. I still want to be able to use the mouse wheel to scroll as well as use the keyboard shortcuts that I have written.
I have the following simplified code:
import wx
import wx.stc
app = wx.App(0)
frame = wx.Frame(None, wx.ID_ANY, "Sample Scroll pane")
textViewer = wx.stc.StyledTextCtrl(frame, wx.ID_ANY)
textViewer.Text = "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22"
textViewer.SetUseVerticalScrollBar(False)
textViewer.ScrollLines(1)
frame.Show()
app.MainLoop()
I am using the "ScrollLines" function to scroll my text programatically. On a windows machine this works as expected and scrolls down one line. However, on Ubuntu, the text does not scroll if the "SetUseVerticalScrollBar" is false.
How can I hide my scrollbar while maintaining it's functionality in a cross platform manner?
ScrollToLine seems to work consistently on Windows and Linux, so you could replace the ScrollLines call with something like this:
first = textViewer.GetFirstVisibleLine()
textViewer.ScrollToLine(first + n)
where n is the number of lines to scroll down.

Categories

Resources