Constrain wxPython MultiSplitterWindow panes
Question: **Edit:** I'm leaving the question open as is, as it's still a good question
and the answer may be useful to others. However, I'll note that I found an
actual solution to _my_ issue by using a completely different approach with
`AuiManager`; see the [answer](http://stackoverflow.com/a/17837040/564181)
below.

I'm working on a `MultiSplitterWindow` setup (after spending a good deal of
time struggling against `SashLayoutWindow` layout quirks). Unfortunately, when
I create a `MultiSplitterWindow`, I see some unexpected behavior when dragging
the sashes around: the sashes can be dragged outside the containing window in
the direction of the layout. To say the least, this is behavior I'd like to
avoid.

Here is the basic setup (you can confirm the behavior below in the wxPython
demo, just substituting `leftwin` for `Panel1`, etc., also see below for an
example app). Where I have `RootPanel/BoxSizer`, there is a panel (or `Frame`,
or whatever kind of container element you like) with a `BoxSizer` to which the
`MultiSplitterWindow` is added – again, as in demo.

    
    
    +--------------------------------+
    |       RootPanel/BoxSizer       |
    |+------------------------------+|
    ||     MultiSplitterWindow      ||
    ||+--------++--------++--------+||
    ||| Panel1 || Panel2 || Panel3 |||
    |||        ||        ||        |||
    ||+--------++--------++--------+||
    |+------------------------------+|
    +--------------------------------+
    

When you drag, you can end up with something like this, where `~` and `!`
indicate that the panel "exists" there but isn't being displayed:

    
    
    +--------------------------------+
    |       RootPanel/BoxSizer       |
    |+-------------------------------|~~~~~~~~~~~~~+
    ||           MultiSplitterWindow |             !
    ||+-----------------++-----------|~~++~~~~~~~~+!
    |||      Panel1     ||   Panel2  |  !! Panel3 !!
    |||                 ||           |  !!        !!
    ||+-----------------++-----------|~~++~~~~~~~~+!
    |+-------------------------------|~~~~~~~~~~~~~+
    +--------------------------------+
    

If at this point, you drag the `RootPanel` to be wider than the overall set of
panels, you will see all the panels again. Likewise, if you drag the width
back down on `Panel1`, you can get access to the sash for `Panel3` again
(assuming the `Panel2` isn't already too wide, of course). Moreover, this is
precisely the situation reported by the Inspection Tool: the `RootPanel`
retains its size, but the `MultiSplitterWindow` grows beyond the size of the
`RootPanel/BoxSizer`.

Further examination with the Inspection Tool reveals that the virtual and
client width values are both 0, but the actual size value is _negative_ (by
the corresponding number of pixels it was dragged out of the window) whenever
it's out of range. Again, this is nutty behavior; I can't imagine why one
would _ever_ want a window to behave this way.

Now, if one holds down `Shift` so that the `_OnMouse` method in
`MultiSplitterWindow` adjusts neighbors, this doesn't happen. Thus, one of my
approaches was to simply override that method. It works, but I'd prefer to
override methods that way if absolutely necessary. Is there another, better
way to solve this problem? It doesn't seem like this would be expected or
desirable behavior in general, so I imagine there is a standard way of fixing
it.

## Other things I've tried:

  * Checking whether the sum of the values in the `MultiWindowSplitter` exceeds the width of the containing window, using each of the `EVT_SPLITTER_SASH_POS_CHANGED` AND `EVT_SPLITTER_SASH_POS_CHANGING` events, and then trying to fix the issue by: 
    * Using an `event.Veto()` call
    * Using the `SetSashPosition()` method on the splitter
  * Overriding the `_OnMouse()` method to use the behavior that is normally associated with holding down the `Shift` key. This works, but it ends up giving me other results I don't like.
  * Setting the minimum pane sizes via `SetMinimumPaneSize` method
  * Setting the maximum size on `MultiSplitterWindow` via `SetMaxSize()`
  * Setting the maximum size on `RootPanel/BoxSizer` using both `SetMaxSize()` and `SetSizeHints()` on the `RootPanel`. 
    * I've even done this with an event handler for `wx.EVT_SIZE` on the container so that the `RootPanel` _always_ has the appropriate maximum size from the parent frame element
    * I've attempted the same event handling approach for the `MultiSplitterWindow`, also to no effect.

## Version info

I have confirmed that this appears in Windows 32-bit and OS X 64-bit, with the
latest snapshot build of wxPython, against both Python 2.7 and 3.3.

## Working example (with Inspection tool included)

The following essentially duplicates (and slightly simplifies) the demo
source. It's a working demonstration of the problem.

    
    
    import wx, wx.adv
    import wx.lib.mixins.inspection as wit
    from wx.lib.splitter import MultiSplitterWindow
    
    
    class AppWInspection(wx.App, wit.InspectionMixin):
        def OnInit(self):
            self.Init()  # enable Inspection tool
            return True
    
    
    class MultiSplitterFrame(wx.Frame):
        def __init__(self, *args, **kwargs):
            super().__init__(size=(800, 800), *args, **kwargs)
            self.SetMinSize((600, 600))
    
            self.top_sizer = wx.BoxSizer(orient=wx.HORIZONTAL)
            self.SetSizer(self.top_sizer)
    
            self.splitter = MultiSplitterWindow(parent=self, style=wx.SP_LIVE_UPDATE)
            self.top_sizer.Add(self.splitter, wx.SizerFlags().Expand().Proportion(1).Border(wx.ALL, 10))
    
            inner_panel1 = wx.Panel(parent=self.splitter)
            inner_panel1.SetBackgroundColour('#999980')
            inner_panel1_text = wx.StaticText(inner_panel1, -1, 'Inner Panel 1')
            inner_panel1.SetMinSize((100, -1))
    
            inner_panel2 = wx.Panel(parent=self.splitter)
            inner_panel2.SetBackgroundColour('#999990')
            inner_panel2_text = wx.StaticText(inner_panel2, -1, 'Inner Panel 2')
            inner_panel2.SetMinSize((100, -1))
            inner_panel2.SetMaxSize((100, -1))
    
            inner_panel3 = wx.Panel(parent=self.splitter)
            inner_panel3.SetBackgroundColour('#9999A0')
            inner_panel3_text = wx.StaticText(inner_panel3, -1, 'Inner Panel 3')
            inner_panel3.SetMinSize((100, -1))
    
            self.splitter.AppendWindow(inner_panel1)
            self.splitter.AppendWindow(inner_panel2)
            self.splitter.AppendWindow(inner_panel3)
    
    if __name__ == '__main__':
        app = AppWInspection(0)
        frame = MultiSplitterFrame(parent=None, title='MultiSplitterFrame Test')
        app.SetTopWindow(frame)
        frame.Show()
        app.MainLoop()
    


Answer: Depending on what one needs this for, one possible option to use instead of a
custom-managed `MultiSplitterWindow` (or `SashLayoutWindow` combinations,
etc.) is the Advanced User Interface kit's `AuiManager` tool (documentation
for pre-Phoenix version [here](http://wxpython.org/docs/api/wx.aui.AuiManager-
class.html); Phoenix docs
[here](http://www.wxpython.org/Phoenix/docs/html/lib.agw.html#module-
lib.agw)). `AuiManager` automates a lot of these kinds of things for you. In
my case, I was attempting to use the `MultiSplitterWindow` as a way of
controlling collapsible and resizable panels for the UI in question, so the
`AuiManager` is a perfect fit: it already has all the controls and constraints
I need built in.

In that case, all one needs to do is create an `AuiManager` instance

(I'm leaving this here as _an_ answer in hopes that others who may be taking
the same naive approach I was taking will find it useful, but not selecting it
as the answer because it does _not_ directly answer the original question.)

## Sample use of AUI under Phoenix

This code sample does exactly what I was trying to do with the
`MultiSplitterWindow`, but managed automatically by the `AuiManager`.

    
    
    import wx, wx.adv
    import wx.lib.mixins.inspection as wit
    from wx.lib.agw import aui
    
    
    class AppWInspection(wx.App, wit.InspectionMixin):
        def OnInit(self):
            self.Init()  # enable Inspection tool
            return True
    
    
    class AuiFrame(wx.Frame):
        def __init__(self, *args, **kwargs):
            super().__init__(size=(800, 800), *args, **kwargs)
            self.SetMinSize((600, 600))
    
            # Create an AUI Manager and tell it to manage this Frame
            self._manager = aui.AuiManager()
            self._manager.SetManagedWindow(self)
    
            inner_panel1 = wx.Panel(parent=self)
            inner_panel1.SetBackgroundColour('#999980')
            inner_panel1.SetMinSize((100, 100))
            inner_panel1_info = aui.AuiPaneInfo().Name('inner_panel1').Caption('Inner Panel 1').Left().\
                CloseButton(True).MaximizeButton(True).MinimizeButton(True).Show().Floatable(True)
    
            inner_panel2 = wx.Panel(parent=self)
            inner_panel2.SetBackgroundColour('#999990')
            inner_panel2_info = aui.AuiPaneInfo().Name('inner_panel2').Caption('Inner Panel 2').Left().Row(1).\
                Show().Floatable(False)
    
            inner_panel3 = wx.Panel(parent=self)
            inner_panel3.SetBackgroundColour('#9999A0')
            inner_panel3.SetMinSize((100, 100))
            inner_panel3_info = aui.AuiPaneInfo().Name('inner_panel3').Caption('Inner Panel 3').CenterPane()
    
            self._manager.AddPane(inner_panel1, inner_panel1_info)
            self._manager.AddPane(inner_panel2, inner_panel2_info)
            self._manager.AddPane(inner_panel3, inner_panel3_info)
            self._manager.Update()
    
        def __OnQuit(self, event):
            self.manager.UnInit()
            del self.manager
            self.Destroy()
    
    if __name__ == '__main__':
        app = AppWInspection(0)
        frame = AuiFrame(parent=None, title='AUI Manager Test')
        app.SetTopWindow(frame)
        frame.Show()
        app.MainLoop()
    

