A Proportional Splitter Window
This is code for a splitter window that keeps its relative pane sizes when the window is resized. Instead of an absolute sash position, you pass a proportion between 0.0 & 1.0.
The proportional value is altered when the user drags the sash. Also, it enforces a minimum pane size so that a pane will not disappear on resizing like the default splitter window behavior.
The code for the ProportionalSplitter class is below along with an example using it. See the original mailing list post at: http://aspn.activestate.com/ASPN/Mail/Message/wxPython-users/1718892
Tiziano Tissino updated the code below to work with the latest version of wxPython.
ProportionalSplitter Class
1 import wx
2
3 class ProportionalSplitter(wx.SplitterWindow):
4 def __init__(self,parent, id = -1, proportion=0.66, size = wx.DefaultSize, **kwargs):
5 wx.SplitterWindow.__init__(self,parent,id,wx.Point(0, 0),size, **kwargs)
6 self.SetMinimumPaneSize(50) #the minimum size of a pane.
7 self.proportion = proportion
8 if not 0 < self.proportion < 1:
9 raise ValueError, "proportion value for ProportionalSplitter must be between 0 and 1."
10 self.ResetSash()
11 self.Bind(wx.EVT_SIZE, self.OnReSize)
12 self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self.OnSashChanged, id=id)
13 ##hack to set sizes on first paint event
14 self.Bind(wx.EVT_PAINT, self.OnPaint)
15 self.firstpaint = True
16
17 def SplitHorizontally(self, win1, win2):
18 if self.GetParent() is None: return False
19 return wx.SplitterWindow.SplitHorizontally(self, win1, win2,
20 int(round(self.GetParent().GetSize().GetHeight() * self.proportion)))
21
22 def SplitVertically(self, win1, win2):
23 if self.GetParent() is None: return False
24 return wx.SplitterWindow.SplitVertically(self, win1, win2,
25 int(round(self.GetParent().GetSize().GetWidth() * self.proportion)))
26
27 def GetExpectedSashPosition(self):
28 if self.GetSplitMode() == wx.SPLIT_HORIZONTAL:
29 tot = max(self.GetMinimumPaneSize(),self.GetParent().GetClientSize().height)
30 else:
31 tot = max(self.GetMinimumPaneSize(),self.GetParent().GetClientSize().width)
32 return int(round(tot * self.proportion))
33
34 def ResetSash(self):
35 self.SetSashPosition(self.GetExpectedSashPosition())
36
37 def OnReSize(self, event):
38 "Window has been resized, so we need to adjust the sash based on self.proportion."
39 self.ResetSash()
40 event.Skip()
41
42 def OnSashChanged(self, event):
43 "We'll change self.proportion now based on where user dragged the sash."
44 pos = float(self.GetSashPosition())
45 if self.GetSplitMode() == wx.SPLIT_HORIZONTAL:
46 tot = max(self.GetMinimumPaneSize(),self.GetParent().GetClientSize().height)
47 else:
48 tot = max(self.GetMinimumPaneSize(),self.GetParent().GetClientSize().width)
49 self.proportion = pos / tot
50 event.Skip()
51
52 def OnPaint(self,event):
53 if self.firstpaint:
54 if self.GetSashPosition() != self.GetExpectedSashPosition():
55 self.ResetSash()
56 self.firstpaint = False
57 event.Skip()
Example Usage
This example may not work with the latest version of wxPython.
1 # Example window with two splitters:
2 # _________________
3 # | | |
4 # | | |
5 # |__________| |
6 # | | |
7 # | | |
8 # |__________|____|
9
10 from ProportionalSplitter import ProportionalSplitter
11
12 class MainFrame(wx.Frame):
13
14 def __init__(self,parent,id,title,position,size):
15 wx.Frame.__init__(self, parent, id, title, position, size)
16
17 ## example code that would be in your window's init handler:
18
19 ## create splitters:
20 self.split1 = ProportionalSplitter(self,-1, 0.66)
21 self.split2 = ProportionalSplitter(self.split1,-1, 0.50)
22
23 ## create controls to go in the splitter windows...
24
25 ## add your controls to the splitters:
26 self.split1.SplitVertically(self.split2, self.rightpanel)
27 self.split2.SplitHorizontally(self.topleftpanel, self.bottomleftpanel)
Comments
Great job!
Here is code rewritten for new wx namespace (wxWidget 2.5.3)
Tiziano Tissino
- Thanks, I replaced the old code with your code.
Comment by Franz Steinhaeusler
This doesn't run in my case. (My comment can be removed again, when it will work)
I created this sample:
1 class MainFrame(wx.Frame):
2
3 def __init__(self,parent,id,title,position,size):
4 wx.Frame.__init__(self, parent, id, title, position, size)
5
6 ## example code that would be in your window's init handler:
7
8 ## create splitters:
9 self.split1 = ProportionalSplitter(self,-1, 0.66)
10 self.split2 = ProportionalSplitter(self.split1,-1, 0.50)
11
12 #self.split1 = wx.SplitterWindow(self)
13 #self.split2 = wx.SplitterWindow(self.split1)
14 ## create controls to go in the splitter windows...
15 self.rightpanel = wx.Panel (self.split1)
16 self.rightpanel.SetBackgroundColour('cyan')
17 self.topleftpanel = wx.Panel (self.split2)
18 self.topleftpanel.SetBackgroundColour('pink')
19 self.bottomleftpanel = wx.Panel (self.split2)
20 self.bottomleftpanel.SetBackgroundColour('sky Blue')
21
22 ## add your controls to the splitters:
23 self.split1.SplitVertically(self.split2, self.rightpanel)
24 self.split2.SplitHorizontally(self.topleftpanel, self.bottomleftpanel)
25
26 app = wx.App(0)
27 win = MainFrame(None, -1, "Hello!", (50, 50), (600, 400))
28 win.Show()
29 app.MainLoop()
It doesn't show any splitter (wxPython 2.6.2.1). If I comment ProportionalSplitter out, and remove the comment of wx.Splitterwindow, than it seems ok.
Comment by Roger Wolf
@ Franz: Actually, the code does work - but you just didn't see it :). Contrary to the default behaviour of a SplitterWindow, the style by ProportionalSplitter was explicitly set to 0 - which means that the dividers were made invisible.
I think it makes more sense that ProportionalSplitter keeps to the default SplitterWindow style. I've removed the explicit style setting in the ProportionalSplitter code.
Roger
Comment by Nate Moon
When I tried the code, the dividers are still invisible at least on Windows 7. I was at the same conclusion of Franz Steinhaeusler, however, I looked for a cursor change inside the window to see if the dividers where invisible/same color of the panels and found that the dividers are actually there. So, I think its logical that you make the panels different colors to help avoid confusion. Besides, a little color in an example doesn't hurt does it?
I added SetBackgroundColour(x) to each panel in Franz Steinhaeusler's code above.