问题
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 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 theEVT_SPLITTER_SASH_POS_CHANGED
ANDEVT_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
- Using an
- Overriding the
_OnMouse()
method to use the behavior that is normally associated with holding down theShift
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
viaSetMaxSize()
- Setting the maximum size on
RootPanel/BoxSizer
using bothSetMaxSize()
andSetSizeHints()
on theRootPanel
.- I've even done this with an event handler for
wx.EVT_SIZE
on the container so that theRootPanel
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.
- I've even done this with an event handler for
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()
回答1:
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; Phoenix docs here). 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()
回答2:
I would try setting the panel's MaxSize or even resort to SetSizeHints(). Both of these allow you to set some constraints on the size of the widget.
来源:https://stackoverflow.com/questions/17818940/constrain-wxpython-multisplitterwindow-panes