Search code examples
pythontexttkintersynchronizationarrow-keys

Python tkinter scrolling two TEXT widgets at the same time with arrow keys


I'm building a GUI that have 2 text widgets. (I mean it has a bunch of things in it but the sake of this question lets leave it at 2 text widgets). What I want to do is that when I scroll the one text widget with the arrow key the other text widget also scrolls at the same time. I was able to accomplish this with the scrollbar (not shown in code) but, not with the arrow keys. I want the arrow key normal behaviour on both text areas at the same time. That is to say that when it gets to the bottom of the viewable text it scrolls down but if I scroll back up the text doesn't move just the arrow. You know, like any normal text editor. So the question is how do I accomplish this? Here is a snippet of my code.

#create Text widgets

descriptionTextField = Text(mainframe, width=40, height=10)
descriptionTextField.grid(column=2, row=5, sticky=(W))
descriptionTextField.bind("<Down>", OnEntryDown)
descriptionTextField.bind("<Up>", OnEntryUp)

pnTextField = Text(mainframe, width=40, height=10)
pnTextField.grid(column=3, row=5, sticky=(W))
pnTextField.bind("<Down>", OnEntryDown)
pnTextField.bind("<Up>", OnEntryUp)

#here are what I have for code that **DOESN'T** do what I want.
def OnEntryDown(event):
    descriptionTextField.yview_scroll(1,"units")
    pnTextField.yview_scroll(1,"units")

def OnEntryUp(event):
    descriptionTextField.yview_scroll(-1,"units")
    pnTextField.yview_scroll(-1,"units")

There has to be a way to find out when the next arrow key will be greater than the viewable area (in this case 10) and then scroll other wise just move the cursor.

NOTE: I can't get the code for up "< Up >" and down "< Down >" arrow to show up in my code above but believe me it is there.


Solution

  • Instead of trying to duplicate what the arrow key does, a different method would be to sync the two windows after the key has been processed (ie: set the yview of one to the yview of the other)? You can move the insertion cursor at the same time if you want. This technique will only work if the two widgets have the same number of lines.

    While the right way would be to adjust the bindtags so that you create a binding after the class bindings, you can avoid that complication with the knowledge that tkinter processes the key press events. This means you can add bindings to key release events. It yields a tiny lag though.

    It would look something like this:

    descriptionTextField("<KeyRelease-Up>", OnArrow)
    descriptionTextField("<KeyRelease-Down>", OnArrow)
    pnTextField("<KeyRelease-Up>", OnArrow)
    pnTextField("<KeyRelease-Down>", OnArrow)
    ...
    def OnArrow(event):
        widget = event.widget
        other = pnTextField if widget == descriptionTextField else descriptionTextField
        other.yview_moveto(widget.yview()[0])
        other.mark_set("insert", widget.index("insert"))
    

    Using bindtags eliminates the lag. You can set it up like this:

       for widget in (descriptionTextField, pnTextField):
            bindtags = list(widget.bindtags())
            bindtags.insert(2, "custom")
            widget.bindtags(tuple(bindtags))
    
            widget.bind_class("custom", "<Up>", OnArrow)
            widget.bind_class("custom", "<Down>", OnArrow)