Search code examples
pythonwxpythonwxwidgetsaspect-ratio

How to center panel with controlled aspect ratio


In wxPython, I'm having trouble centering a sub-panel that I've created to have a fixed aspect ratio.

To control the aspect ratio, the sub-panel needs to capture the Size event and then do an explicit SetSize. So, I've done that and it works well. Unfortunately, when I embed this sub-panel into another panel (using a sizer), the wx.ALIGN_CENTER_HORIZONTAL flag doesn't work.

Apparently, the sizer tells my sub-panel that it has the whole width to fit within. When my window doesn't use the whole width, then the sizer doesn't adjust. Here is a simplified version of my code that shows the problem:

import wx

ROWS = 6
COLS = 25

class TestPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)

        label = wx.StaticText(self, -1, 'Label')
        grid  = TestGrid(self)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(label, 0, flag=wx.ALIGN_CENTER_HORIZONTAL)
        sizer.Add(grid,  1, flag=wx.ALIGN_CENTER_HORIZONTAL|wx.EXPAND)
        self.SetSizer(sizer)

class TestGrid(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)
        self.SetBackgroundColour(wx.BLUE)
        self.Bind(wx.EVT_SIZE,  self.OnSize)

    def OnSize(self, event):
        w, h = self.GetSizeTuple()
        delta = min(w // COLS, h // ROWS)
        self.SetSize((COLS*delta, ROWS*delta))
        self.Refresh()

if __name__ == '__main__':
    app = wx.App()
    frame = wx.Frame(None, -1, "Test", size=(600, 200))
    panel = TestPanel(frame)
    frame.Show(True)
    app.MainLoop()

This is a screen capture: Demonstration of problem


Solution

  • As VZ explained in his answer, your wxEVT_SIZE handler isn't a very good idea. Remove it. When using sizers, you have to work within the sizer infrastructure to achieve what you want.

    You want your TestGrid to fill as much space as possible within its parent, while maintaining a certain aspect ratio; that's exactly what wx.SHAPED is for (combined with a proportion greater than 0, which you already have). So, you should add grid to the sizer like this:

    sizer.Add(grid,  1, flag=wx.ALIGN_CENTER_HORIZONTAL|wx.SHAPED)
    

    The only other thing you need to do is tell the sizer what your desired aspect ratio is. The easiest way is to set an initial size for your TestGrid (before it's added to the sizer); for example, set the size of its wxPanel base:

    wx.Panel.__init__(self, parent, -1, size=(COLS*7, ROWS*7))
    

    That's all there is to it.

    (I'm just guessing the Python syntax, so ignore any obvious errors :-) )