python-2.6.2, wxpython-2.8.12.1, windows-xp
hi,
i'm experiencing undesirable behaviour when programmatically setting the
sash position of a vertically split SplitterWindow that is nested inside
another vertically split SplitterWindow (but only on windows).
my application saves the sash positions of both SplitterWindows between
invocations so that it can remember where the user left things and
restore them the way they were.
when the nested SplitterWindow is split horizontally, all is ok. on a
mac, it's ok whether the nested SplitterWindow is split vertically or
horizontally. but on windows it only works properly when the nested
SplitterWindow is split horizontally.
on windows, when the nested SplitterWindow is split vertically, and i
call SetSashPosition with a particular number of pixels, the actual
sash position gets set to that number of pixels minus the sash position
of the outer SplitterWindow plus 10 pixels. i have no idea why. it's
as though it's doing a coordinate transformation interpreting the sash
position that i supply as though it were a coordinate relative to the
outer SplitterWindow rather than just a sash position for the nested
SplitterWindow. the 10 pixel offset might have something to do with the
width of the sash and/or border.
i have a workaround in place so i'm ok with it now but i thought i
should mention it and supply a demonstration (below).
when the demo is run multiple times with no arguments (to demonstrate
the undesirable behaviour) and the window is just closed immediately
without changing any sash positions, the nested sash position moves to
the left with each invocation, it outputs:
Run1:
default v200 h400
saving v200 h210
Run2:
loading v200 h210
saving v200 h20
Run3:
loading v200 h20
saving v200 h3
Run4:
loading v200 h3
saving v200 h3
when the demo is run with the argument "f", the workaround is in place and
it works as expected (delete the "delete.me" file first):
Run1:
default v200 h400
saving v200 h400
Run2:
loading v200 h400
saving v200 h400
Run3:
loading v200 h400
saving v200 h400
Run4:
loading v200 h400
saving v200 h400
the demo can also be run with the argument "h" to split the nested SplitterWindow
horizontally rather than vertically to show that the undesirable behaviour is not
exhibited in that situation.
when finished, don't forget to delete the "delete.me" file that the demo creates.
cheers,
raf
#!/usr/bin/env python
# Demonstrate undesirable behaviour on Windows where SetSashPosition
# on a vertically-split SplitterWindow that is nested inside another
# vertically-split SplitterWindow doesn't set the position to the number
# supplied. Instead, it is the expected position minus the width of
# the outer SplitterWindow's sash position minus 10 pixels. This only
# happens when both SplitterWindows are vertical and only on Windows
# (i.e. not on Mac, haven't checked Linux).
···
#
# usage: grr.py [h|f]
# With "h", the nested SpliterWindow is horizontal. Otherwise it is vertical.
# Horizontal works on MacOSX and Windows. Vertical only works on MacOSX.
#
# With "f" (only in vertical mode), a hacky "fix" is applied to ounteract
# the strange behaviour. It seems to work but only by chance.
#
# Run the program multiple times. On MacOSX, it remembers where the sashes were.
# On Windows, the rightmost sash position keeps moving left with each invocation.
# When finished, manually delete the "delete.me" file that is left behind.
#
# 20120314 raf <raf@raf.org>
import wx, sys
class TestApp(wx.App):
def OnInit(self):
self.vertical_mode = False if len(sys.argv) == 2 and sys.argv[1] == 'h' else True
self.fix_mode = True if len(sys.argv) == 2 and sys.argv[1] == 'f' else False
self.mainframe = MainFrame(self)
self.SetTopWindow(self.mainframe)
return True
class MainFrame(wx.Frame):
def __init__(self, app):
wx.Frame.__init__(self, None, title='Test', size=(800, 600), name='mainframe')
self.app = app
self.build_panel()
self.Bind(wx.EVT_CLOSE, self.OnClose)
self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self.OnSplitterChange)
self.Show()
def build_panel(self):
splitter_flags = wx.SP_LIVE_UPDATE | wx.SP_3D
self.vsplit = wx.SplitterWindow(self, style=splitter_flags)
self.vsplit.SetMinimumPaneSize(1)
self.vsplit.SetSashGravity(0.0)
self.hsplit = wx.SplitterWindow(self.vsplit, style=splitter_flags)
self.hsplit.SetMinimumPaneSize(1)
self.hsplit.SetSashGravity(1.0)
def create_panel(parent, label):
p = wx.Panel(parent, style=wx.BORDER_SUNKEN)
sz = wx.BoxSizer(wx.VERTICAL)
t = wx.StaticText(p, label=label)
sz.Add(t, 1, wx.EXPAND | wx.ALL, 3)
p.SetSizer(sz)
return p, t
vsplitpos, hsplitpos = self.load_pos(200, 400)
p1, self.t1 = create_panel(self.vsplit, 'P1 V%s' % vsplitpos)
p2, self.t2 = create_panel(self.hsplit, 'P2 H%s' % hsplitpos)
p3, self.t3 = create_panel(self.hsplit, 'P3')
if self.app.vertical_mode:
self.hsplit.SplitVertically(p2, p3, hsplitpos)
else:
self.hsplit.SplitHorizontally(p2, p3, hsplitpos)
# Initial sash position for a nested SplitterWindow doesn't seem to work so...
wx.CallAfter(self.hsplit.SetSashPosition, hsplitpos)
self.vsplit.SplitVertically(p1, self.hsplit, vsplitpos)
def OnClose(self, event):
self.save_pos(self.vsplit.GetSashPosition(), self.hsplit.GetSashPosition())
event.Skip()
def OnSplitterChange(self, event):
self.t1.SetLabel('P1 V%s' % self.vsplit.GetSashPosition())
self.t2.SetLabel('P2 H%s' % self.hsplit.GetSashPosition())
event.Skip()
def save_pos(self, vpos, hpos):
print('saving v%s h%s' % (vpos, hpos))
f = open('delete.me', 'w')
f.write('%s\n' % vpos)
f.write('%s\n' % hpos)
f.close()
def load_pos(self, default_vpos, default_hpos):
try:
f = open('delete.me', 'r')
vpos = int(f.readline()[:-1])
hpos = int(f.readline()[:-1])
f.close()
print('loading v%s h%s' % (vpos, hpos))
except IOError:
print('default v%s h%s' % (default_vpos, default_hpos))
vpos, hpos = default_vpos, default_hpos
# Why does this fix it?
if self.app.fix_mode and wx.Platform == '__WXMSW__' and self.app.vertical_mode:
hpos += vpos - 10
print('fixed v%s h%s' % (vpos, hpos))
return vpos, hpos
def main(redirect=False):
TestApp(redirect=redirect).MainLoop()
if __name__ == '__main__':
main()
# vi:set ts=4 sw=4: