Cross-platform issues: Programmatically Scrolling a TreeCtrl

David Woods wrote:

Greetings.

In Transana, I do a lot with drag and drop in a wx.TreeCtrl. One of the
things I need to be able to do is to scroll the tree up or down when the
user needs to drop something on a tree node that is above or below the
visible area of the tree. (I'm using wxPython 2.5.3.1 on Windows and Mac
OS/X 10.3.)

I've gotten this to work on Windows using the wx.TreeCtrl.ScrollLines()
method, but unfortunately that method isn't supported on OS/X. I've messed
with the ScrollWindow(), SetScrollPos(), and ScrollTo() methods, all with no
success and with varying results on the different platforms.

(By the way, wx.TreeCtrl.GetPrevVisible() tells me it's not been implemented
on Mac OS/X, although GetNextVisible() is.)

I've included code below with some of my experiments. I'd really appreciate
it if someone can tell me what I'm doing wrong, and help me get one of these
methods (or something completely different) to work. Method 1 and Method 5
work on Windows but not Mac. Method 2 scrolls the tree on Windows but it
doesn't repaint properly, and does nothing on the Mac. Method 3 attempted
to fix this, but now it does nothing on either platform. Method 4 moves the
scroll bar, but not the tree, on both platforms.

The odd thing about wxTreeCtrl that you are running into here is that on Windows it is a wrapper around the native treeview control and derives from wxControl. But on the other platforms it is currently a generic class that is derived from wxScrolled window (although the wxPython class heirarchy still treats it as if it derived from wxControl.) So while most things work fine with this arraingment there are several differeces that do manifest.

OTOH, being aware of the difference can help with a workaround or two. Here is Method 6 which will send an event as if the window had been scrolled by the scrollbars.

Method 6 Up:
           if "wxMSW" in wx.PlatformInfo:
              self.tree.ScrollLines(-1)
           else:
              sp = self.tree.GetScrollPos(wx.VERTICAL)
              e = wx.ScrollEvent(wx.wxEVT_SCROLLWIN_LINEUP,
                                 self.tree.GetId(),
                                 max(sp-1, 0),
                                 wx.VERTICAL)
              self.tree.GetEventHandler().ProcessEvent(e)
Down:
           if "wxMSW" in wx.PlatformInfo:
              self.tree.ScrollLines(1)
           else:
              sp = self.tree.GetScrollPos(wx.VERTICAL)
              range = self.tree.GetScrollRange(wx.VERTICAL) - \
                      self.tree.GetScrollThumb(wx.VERTICAL)
              e = wx.ScrollEvent(wx.wxEVT_SCROLLWIN_LINEDOWN,
                                 self.tree.GetId(),
                                 min(sp+1, range),
                                 wx.VERTICAL)
              self.tree.GetEventHandler().ProcessEvent(e)
  
Unfortunatly I seem to be running into a bug with this approach that I don't quite understand yet. In most cases it works fine, but if you start out with the tree scrolled all the way to the top then you can't go more than three or four steps scrolling down. It may be something in the wxScrolledWindow class, or perhaps even deeper...

So method 7 is kind of a brute-force way to figure out what should be the next item visible if the tree is scrolled, and then uses ScrollTo to do it.

Method 6 Up:
           if "wxMSW" in wx.PlatformInfo:
              self.tree.ScrollLines(-1)
           else:
              first = self.tree.GetFirstVisibleItem()
              prev = self.tree.GetPrevSibling(first)
              if prev:
                 # drill down to find last expanded child
                 while self.tree.IsExpanded(prev):
                    prev = self.tree.GetLastChild(prev)
              else:
                 # if no previous sub then try the parent
                 prev = self.tree.GetItemParent(first)

              if prev:
                 self.tree.ScrollTo(prev)
              else:
                 self.tree.EnsureVisible(first)

Down:

           if "wxMSW" in wx.PlatformInfo:
              self.tree.ScrollLines(1)
           else:
              # first find last visible item by starting with the first
              next = None
              last = None
              item = self.tree.GetFirstVisibleItem()
              while item:
                 if not self.tree.IsVisible(item): break
                 last = item
                 item = self.tree.GetNextVisible(item)

              # figure out what the next visible item should be,
              # either the first child, the next sibling, or the
              # parent's sibling
              if last:
                 if self.tree.IsExpanded(last):
                    next = self.tree.GetFirstChild(last)[0]
                 else:
                    next = self.tree.GetNextSibling(last)
                    if not next:
                       prnt = self.tree.GetItemParent(last)
                       if prnt:
                          next = self.tree.GetNextSibling(prnt)

              if next:
                 self.tree.ScrollTo(next)
              elif last:
                 self.tree.EnsureVisible(last)

ยทยทยท

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!