In wxPython, how can I use sizers to left- and right-justify statictext beneath a slider?

186 views Asked by At

I'm trying to make a GUI like so:

picture of my intended layout

The big square is a wxGrid, the small ones are wxButtons, and they all act fine. The drawing at left is intended to be a wxSlider, with text labels "slow" and "fast" beneath each end of the slider.

So I lay out a bunch of BoxSizers, like this:

hierarchy of BoxSizers

From outside in:

  • Blue is vertical, and contains a wxGrid and the green BoxSizer
  • Green is horizontal, and contains the orange BoxSizer and two buttons
  • Orange is vertical, and contains a wxSlider and the purple BoxSizer
  • Purple is horizontal, and contains two StaticTexts, with the words "slow" and "fast"

But the closest I can get it to render is this. Notice especially how the slider is tiny, and the slow and fast labels (intended to mark the ends of the slider!) are a mess. my crappy outcome

I've messed with alignments and expands, I've read a bunch of posts from other people complaining about BoxSliders, and I've gotten nowhere. I thought for sure I had it when I read about wx.ST_NO_AUTORESIZE, but that didn't do anything. I even rebuilt my window with wxGlade, and got the same thing, especially with the static text laid out far left.

The one thing I haven't done is specified the size of anything in pixels. It doesn't seem like any layout should require that, because who knows what size screen I'll be running on or what a reasonable number of pixels is. And if I've understood proportions correctly in sizers, I don't have to specify sizes in pixels.

But I'm out of ideas, and I haven't even found a good example, just similar veins of frustration. How do I make my slider take up the full width of the orange boxsizer, and how do I get the slow and fast text to label the ends of the slider?

What I'm running, stripped to the layout essentials (and it's got the slider and labels problem):

import wx
import wx.grid

app = wx.App()

frame = wx.Frame(None, title="MyUnhappyLayout")

blue_sizer = wx.BoxSizer(wx.VERTICAL)

grid = wx.grid.Grid(frame)

blue_sizer.Add(grid, 6, wx.EXPAND, 8)

green_sizer = wx.BoxSizer()
blue_sizer.Add(green_sizer, 1, wx.EXPAND)

button1 = wx.Button(frame)
button2 = wx.Button(frame)

slider = wx.Slider(frame, name="Speed", value=1, minValue=1, maxValue=100)

purple_sizer = wx.BoxSizer()
label_slow = wx.StaticText(frame, label="Slow")
label_fast = wx.StaticText(frame, label="Fast")
purple_sizer.Add(label_slow, wx.ALIGN_LEFT)
purple_sizer.Add(label_fast, wx.ALIGN_RIGHT)

orange_sizer = wx.BoxSizer(wx.VERTICAL)
green_sizer.Add(orange_sizer, 2)
orange_sizer.Add(slider)

orange_sizer.Add(purple_sizer, wx.EXPAND)
green_sizer.Add(button1, 1, wx.EXPAND)
green_sizer.Add(button2, 1, wx.EXPAND)

frame.SetSizerAndFit(blue_sizer)
frame.Show()

app.MainLoop()
2

There are 2 answers

4
catalin On BEST ANSWER

There is a "built-in" option for showing min and max labels for a slider: use wxSL_MIN_MAX_LABELS when creating it (unless you are using wxWidgets older than 2.9.1).

Otherwise, for your specific sizer layout (it might be easier to review if you create each sizer just before using it):

purple_sizer = wx.BoxSizer()
purple_sizer.Add(label_slow)
purple_sizer.AddStretchSpacer()
purple_sizer.Add(label_fast)

orange_sizer = wx.BoxSizer(wx.VERTICAL)
# when adding to a sizer, the second argument would be proportion;
# use SizerFlags to avoid mistakenly skipping an argument
orange_sizer.Add(slider, wx.SizerFlags().Expand())
orange_sizer.Add(purple_sizer, wx.SizerFlags().Expand())

green_sizer = wx.BoxSizer()
green_sizer.Add(orange_sizer, wx.SizerFlags(1)) # no need for proportion=2, 1 should do
green_sizer.Add(button1) # you probably meant to enlarge the slider, not the buttons
green_sizer.Add(button2)

blue_sizer = wx.BoxSizer(wx.VERTICAL)
blue_sizer.Add(grid, wx.SizerFlags(1).Expand().Border(8)) # no need for proportion=6, 1 should do
blue_sizer.Add(green_sizer, wx.SizerFlags().Expand())
2
Rolf of Saxony On

As @VZ has pointed out to me, the old Align within a boxsizer in it's orientation, never worked but now throws an error in newer iterations of wxWidgets.
Now, the old way to achieve the same result, is to insert dummy entries into the sizer and ask for them to be expanded.

For the new way see the answer from @catalin.

Some of the changes to your code are cosmetic to help me understand what is what, by explicit rather than implicit with the defaults for widgets.

import wx
import wx.grid

app = wx.App()

frame = wx.Frame(None, title="MyUnhappyLayout")

blue_sizer = wx.BoxSizer(wx.VERTICAL)

grid = wx.grid.Grid(frame)

blue_sizer.Add(grid, 6, wx.EXPAND, 8)

green_sizer = wx.BoxSizer(wx.HORIZONTAL)
blue_sizer.Add(green_sizer, 1, wx.EXPAND)

button1 = wx.Button(frame)
button2 = wx.Button(frame)

slider = wx.Slider(frame, name="Speed", value=1, minValue=1, maxValue=100)

purple_sizer = wx.BoxSizer(wx.HORIZONTAL)
label_slow = wx.StaticText(frame, label="Slow")
label_fast = wx.StaticText(frame, label="Fast")
purple_sizer.Add(label_slow, 0, 0, 0)
purple_sizer.Add((-1,-1), 1, wx.EXPAND, 0)
purple_sizer.Add(label_fast, 0, 0, 0)

orange_sizer = wx.BoxSizer(wx.VERTICAL)
orange_sizer.Add(slider, 0, wx.EXPAND)
orange_sizer.Add(purple_sizer, 0, wx.EXPAND)

green_sizer.Add(orange_sizer, 2, 0, 0)
green_sizer.Add(button1, 1, wx.EXPAND)
green_sizer.Add(button2, 1, wx.EXPAND)

frame.SetSizerAndFit(blue_sizer)
frame.Show()

app.MainLoop()

enter image description here

Note:
This is the dummy entry: purple_sizer.Add((-1,-1), 1, wx.EXPAND, 0)