Maintaining proper application shape

I’ve worked around this problem before but my hack is no longer acceptable. It was barely OK when I new little about wxPython but now, after gaining some level of expertise, it offends me. I’m hoping someone can suggest an elegant way to manage this. If you run the below code you will see a frame/panel with two subpanels. The left panel contains a 3x3 grid, and the right panel contains a stack of buttons.

With the current code, the app cannot be resized any smaller. That’s what I want. What I also want is that when I resize the app, it resizes so that the grid and buttons are always displayed with the same spacing and there is no extra blank space on the right or bottom.

Grab the right border and drag to the right. Nothing happens except the frame gets extended to the right. Drag the border back to its original size.

Now grab the bottom border and drag down. The 3x3 grid expands until the buttons disappear. The first thing I’d like to do is prevent that from happening. Return the border to its original size.

Grab the bottom right corner and drag straight down until the buttons disappear. Now drag slowly to the right. Shortly after the buttons re-appear the grid suddenly shrinks. Keep dragging to the right until the buttons are completely visible. At this point, dragging straight down no longer causes the buttons to be over-written by the grid. I am at a loss as to why this happens.

Ideally the main panel would be able to have a shaped attribute but since the main panel does not go in a sizer this is not possible.

import wx

class MyFrame(wx.Frame):

    def __init__(self, title=''):

        super().__init__(None, wx.ID_ANY)

        self.Title = title
        self.SetSize(400, 380)

        self.pnlMain = wx.Panel(self,         wx.ID_ANY, style=wx.BORDER_SIMPLE)
        self.pnlGrid = wx.Panel(self.pnlMain, wx.ID_ANY, style=wx.BORDER_SIMPLE)
        self.pnlCtrl = wx.Panel(self.pnlMain, wx.ID_ANY, style=wx.BORDER_SIMPLE)

        #Create grid
        gsizer = wx.GridSizer(3, 3, 0, 0)

        for i in range(1,10):
            panel = wx.Panel(self.pnlGrid, wx.ID_ANY)
            panel.SetMinSize((100, 100))
            panel.SetBackgroundColour(wx.Colour(160,160,100))
            gsizer.Add(panel, 0, wx.ALL | wx.EXPAND | wx.SHAPED, 3)

        self.pnlGrid.SetSizer(gsizer)

        #Create control array
        csizer = wx.BoxSizer(wx.VERTICAL)

        for i in range(1,10):
            button = wx.Button(self.pnlCtrl, wx.ID_ANY, "button" + str(i))
            button.SetMinSize(button.Size)
            csizer.Add(button, 0, wx.ALL, 5)

        self.pnlCtrl.SetSizer(csizer)

        #Final sizer
        msizer = wx.BoxSizer(wx.HORIZONTAL)

        msizer.Add(self.pnlGrid, 0, wx.EXPAND | wx.SHAPED, 0)
        msizer.Add(self.pnlCtrl, 0, 0, 0)

        self.pnlMain.SetSizer(msizer)

        #Status Bar
        self.frame_statusbar = self.CreateStatusBar(1)
        self.frame_statusbar.SetStatusWidths([-1])
        frame_statusbar_fields = ["frame_statusbar"]
        for i in range(len(frame_statusbar_fields)):
            self.frame_statusbar.SetStatusText(frame_statusbar_fields[i], i)

        self.Layout()
        self.SetMinSize(self.Size)
        self.pnlGrid.SetMinSize(self.pnlGrid.Size)

        self.Bind(wx.EVT_CLOSE, self.OnClose)

    def OnClose(self, event):
        event.Skip()

class MyApp(wx.App):
    def OnInit(self):
        self.SetAppName("Sizer Test")
        self.frame = MyFrame(title=self.AppName)
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

if __name__ == "__main__":
    app = MyApp(0)
    app.MainLoop()