wxPython 201: Syncing Scrolling Between Two Grids

This week I saw a question on StackOverflow about putting two grids into a SplitterWindow which itself was in a Notebook page. Personally I think that’s a little convoluted, but I thought it was an interesting challenge and I came up with a solution. Then the fellow wanted to know how to sync the scrolling of the two grids. Well, I found an answer and modified my code and decided it was worth writing an article about. Here is a screenshot of the finished result:

wxScrollGrid

Yes, for some reason the person who wanted this also wanted a panel below the two grids. I think it’s a bit ugly, especially with the pink background, but to each their own. Let’s take a look at the code:

import wx
import wx.grid as gridlib

class ScrollSync(object):
    def __init__(self, panel1, panel2):
        self.panel1 = panel1
        self.panel2 = panel2
        self.panel1.grid.Bind(wx.EVT_SCROLLWIN, self.onScrollWin1)
        self.panel2.grid.Bind(wx.EVT_SCROLLWIN, self.onScrollWin2)
        
    def onScrollWin1(self, event):
        if event.Orientation == wx.SB_HORIZONTAL:
            self.panel2.grid.Scroll(event.Position, -1)
        else:
            self.panel2.grid.Scroll(-1, event.Position)
        event.Skip()

    def onScrollWin2(self, event):
        if event.Orientation == wx.SB_HORIZONTAL:
            self.panel1.grid.Scroll(event.Position, -1)
        else:
            self.panel1.grid.Scroll(-1, event.Position)
        event.Skip()

########################################################################
class RegularPanel(wx.Panel):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)
        self.SetBackgroundColour("pink")


########################################################################
class GridPanel(wx.Panel):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)
        self.grid = gridlib.Grid(self, style=wx.BORDER_SUNKEN)
        self.grid.CreateGrid(25,8)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.grid, 1, wx.EXPAND)
        self.SetSizer(sizer)


########################################################################
class MainPanel(wx.Panel):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)
        
        notebook = wx.Notebook(self)

        page = wx.SplitterWindow(notebook)
        notebook.AddPage(page, "Splitter")
        hSplitter = wx.SplitterWindow(page)

        panelOne = GridPanel(hSplitter)
        panelTwo = GridPanel(hSplitter)
        ScrollSync(panelOne, panelTwo)
        
        hSplitter.SplitVertically(panelOne, panelTwo)
        hSplitter.SetSashGravity(0.5)

        panelThree = RegularPanel(page)
        page.SplitHorizontally(hSplitter, panelThree)
        page.SetSashGravity(0.5)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(notebook, 1, wx.EXPAND)
        self.SetSizer(sizer)

########################################################################
class MainFrame(wx.Frame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, title="Nested Splitters",
                          size=(800,600))
        panel = MainPanel(self)
        self.Show()

#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.App(False)
    frame = MainFrame()
    app.MainLoop()

The piece we care about most is the ScrollSync class. It accepts the two panels that the grids are on as arguments. We then bind the grids to wx.EVT_SCROLLWIN and then during that event, we change the position of the opposite grid. Now this code has several limitations. It only works when you are physically moving the scrollbars with your mouse. If you use the mouse’s scroll wheel, the arrow keys or Page up/down, then the two grids no longer sync. I attempted to add mouse wheel support via the wx.EVT_MOUSEWHEEL event, but it doesn’t provide Orientation or Position in the same way as EVT_SCROLLWIN does. In fact, its Position is a wx.Point whereas EVT_SCROLLWIN returns an Integer. Adding those bits of functionality would be fun, but they are outside the scope of this article.

This code should get you started on your way in getting the syncing working though. I also found a couple of other articles related to this subject that you might find helpful should you need to sync scrolling up in a couple of widgets:

6 thoughts on “wxPython 201: Syncing Scrolling Between Two Grids”

  1. Pingback: Mike Driscoll: wxPython 201: Syncing Scrolling Between Two Grids | The Black Velvet Room

  2. Pingback: wxPython 201: Syncing Scrolling Between Two Grids | Hello Linux

  3. How about removing the grid on left’s vertical scrollbar, so that there is only one vertical scrollbar on the right edge of the window?

  4. The scrollbars you see are built into the grid widgets themselves. You will only see the scrollbar for the scrolled window if you change the frame’s size to a small enough size that it appears.

  5. David Vázquez Rodao

    Hi, I have made nearly the same thing as you and I have the same issues. To solve the sync problem when someone click the grid I use wx.grid.EVT_GRID_SELECT_CELL to launch the same event. I just want to know if you have move any forward with the wheel issue?
    Thanks a lot.

Comments are closed.