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

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.

   1         #self.split1 = ProportionalSplitter(self,-1, 0.66)
   2         #self.split2 = ProportionalSplitter(self.split1,-1, 0.50)
   3 
   4         self.split1 = wx.SplitterWindow(self)
   5         self.split2 = wx.SplitterWindow(self.split1)

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.

ProportionalSplitterWindow (last edited 2009-12-04 01:23:13 by 123)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.