Search code examples
pythonwxpythonwxwidgets

Filling available space with a column in GridBagSizer


I am trying to create a dialog in which there are a few long descriptions, each followed by a short question with radio button answers. I'm laying my controls out with a GridBagSizer (wxWidgets docs, wxPython docs), in which the long description spans all three columns, and the question and two radio button answers are each in their own columns.

enter image description here

I would like the answers to be aligned to the right, so I've tried passing flag=wx.EXPAND when adding the StaticText for the question to the layout. This doesn't seem to have the desired effect, however; the first column remains just large enough to fit the longest question, rather than filling the available space and pushing the radio buttons to the right.

import wx

class MyDialog(wx.Dialog):
    def __init__(self, *args, **kwargs):
        wx.Dialog.__init__(self, *args, **kwargs)

        sizer = wx.GridBagSizer(5, 5)
        sizer.SetEmptyCellSize((0,0))
        sizer.AddGrowableCol(0)

        for i in range(5):
            description = wx.StaticText(self, -1, "This is a long description \
that may span several lines. Filler filler filler filler filler. More filler \
filler filler filler filler.")
            description.Wrap(500)

            question = wx.StaticText(self, -1, "Are you sure?")
            yes = wx.RadioButton(self, -1, "Yes", style = wx.RB_GROUP)
            no = wx.RadioButton(self, -1, "No")

            sizer.Add(description, (i*3, 0), (1, 3))
            sizer.Add(question, (i*3+1, 0), flag=wx.EXPAND)
            sizer.Add(yes, (i*3+1, 1))
            sizer.Add(no, (i*3+1, 2))

        self.SetSizerAndFit(sizer)
        self.Show()


app = wx.App(False)
dialog = MyDialog(None)
app.MainLoop()

I've also tried adding an extra spacer column, and setting wx.EXPAND on that, but that doesn't help either. I've also tried setting wx.ALIGN_RIGHT on the radio buttons, but as expected, that just causes the "No" button to be aligned to the right, as it's the third column that is taking up the rest of the available space.

Am I missing something here? Is it not possible to have one column in a GridBagSizer expand to fill the available space, or am I doing it wrong? And if I'm doing it wrong, is it documented anywhere? References to appropriate documentation would be helpful.


Solution

  • I tried to solve your problem using wx.Sizer.AddStretchSpacer() to fill in the gap. http://wxpython.org/docs/api/wx.Sizer-class.html#AddStretchSpacer

    I got it to work but StretchSpacers appear to behave oddly in wx.GridBagSizers

    sizer.Add(description, (i*3, 0), (1, 3))
    sizer.Add(question, (i*3+1, 0), flag=wx.EXPAND)
    sizer.AddStretchSpacer((i*3+1,1))
    sizer.Add(yes, (i*3+1, 2), flag=wx.ALIGN_RIGHT)
    sizer.Add(no, (i*3+1, 3))
    

    Note how I need to align the Yes button to the right. I don't know why this is. No matter what column I insert the stretch spacer into I had to align the Yes button to the right (even if the spacer was inserted into column 0!). Also, the No button appears a bit too far to the right. I don't understand why the code behaves like this and it all seems a bit hackish so I strongly suggest you don't use this soltuion.

    However, using a stretch spacer should allow you to create wx.BoxSizers with width relative to the width of the window. This would allow you to line up your buttons (since they are all the same width and the stretch spacer pushes them to the right side). So, just like @Mike Discoll breifly suggested, I refactored your code to use wx.BoxSizers

    class MyDialog(wx.Dialog):
        def __init__(self, *args, **kwargs):
            wx.Dialog.__init__(self, *args, **kwargs)
    
            sizer = wx.BoxSizer(wx.VERTICAL)
    
            for i in range(5):
                description = wx.StaticText(self, -1, "This is a long description \
    that may span several lines. Filler filler filler filler filler. More filler \
    filler filler filler filler.")
                description.Wrap(500)
    
                questionSizer = wx.BoxSizer(wx.HORIZONTAL)
                question = wx.StaticText(self, -1, "Are you sure?")
                yes = wx.RadioButton(self, -1, "Yes", style = wx.RB_GROUP)
                no = wx.RadioButton(self, -1, "No")
    
                sizer.Add(description)
                questionSizer.Add(question)
                questionSizer.AddStretchSpacer()
                questionSizer.Add(yes)
                questionSizer.Add(no)
                sizer.Add(questionSizer, flag=wx.EXPAND)
    
            self.SetSizerAndFit(sizer)
            self.Show()
    

    Which I think you'll find gives you exactly the result you wanted:

    Stretch Spacer and wx.BoxSizer

    Note that you must add questionSizer with the wx.EXPAND flag because stretch spacers expand to fill te unused area. If you call sizer.Add() without wx.EXPAND then the width is set be default to just the length of the text and buttons (i.e. with no extra space for the stretch spacer to fill). Using wx.Expand sets the width to the width of sizer (which, because of description, we know will be around 500 pixels)

    You can play round with the borders to get the spacing between items the way you like it.

    ---EDIT---

    I just tried an experiment where I changed the description text to be a very short string (much less than 500 pixels) and due to the call self.SetSizerAndFit() call, the resulting window is very very small and visually unpleasing:

    Small Description without Min Size

    I think it would be better if you explictly set the size of the sizer to ensure a consistent minium width.

    [...]
    width = 500 #the desired minimum width
    sizer = wx.BoxSizer(wx.VERTICAL)
    sizer.SetMinSize((width,1)) #ensures sizer will be minimum width
    """NOTE: the minimum size gets recalculated when new items are added.
    i.e. if height exceeds 1 pixel or if width exceeds wdith, the sizer
    will recalculate the minimum size"""
    
    
    for i in range(5):
        description = wx.StaticText(self, -1, "Short Description") #description is less than 500 pixels, Wrap() will have no effect.
        description.Wrap(width)
    [...]
    

    Which results in this:

    Small Descriptio with Min Size