Harvey
Harvey

Reputation: 2222

python tkinter - want to call a function when scrollbar clicked in ScrolledText widget

[Edits noted:] I want to hook into the ScrolledText widget so that when a user clicks anywhere in the scrollbar (or even scrolls it with the mouse wheel, hopefully) I can have it call a callback function where I can [Add: "set a flag, and then return to let the ScrolledText widget do its thing."] [Delete: " do something first (like turn off automatic scrolling) before the scrolling action takes place."]

Is this possible?

Thanks

Upvotes: 0

Views: 2642

Answers (2)

Bryan Oakley
Bryan Oakley

Reputation: 385950

Do you want to do something like turning off automatic scrolling, or is that actually what you want to do?

If you want to turn automatic scrolling on or off, just check the position of the text before inserting text. If it's at the end, add the text and autoscroll. If it's not at the end, add the text but don't scroll. This will work even if they scrolled by some other mechanism such as by using the page up / page down keys.

You can check a couple of different ways. I think the way I've always done it (not at my desktop right now to check, and it's been a few years...) is to call dlineinfo on the last character. If the last character is not visible, this command will return None. You can also use the yview command to see if the viewable range extends to the bottom. It returns two numbers that are a fraction between zero and one, for the first and last visible line.

While the user can't turn auto-scrolling on or off by clicking a button, this is arguably better because it will "just happen" when they scroll back to see something.

Upvotes: 2

abarnert
abarnert

Reputation: 365707

Not without reaching inside the ScrolledText to get at the Scrollbar and the Text and hook their bindings.

And, while you can do that, at that point, why even use ScrolledText? The whole point is that it's does the scroll bindings automagically without you having to understand them. If you don't want that, just use a Scrollbar and a Text directly. Tkinter Scrollbar Patterns explains how to do this in detail, but really, if you don't want to do anything unusual, it's just connecting a message from each one to a method on the other.

For example:

from Tkinter import *

def yscroll(*args):
    print('yscroll: {}'.format(args))
    scrollbar.set(*args)

def yview(*args):
    print('view: {}'.format(args))
    textbox.yview(*args)

root = Tk()    
scrollbar = Scrollbar(root)
scrollbar.pack(side=RIGHT, fill=Y)    
textbox = Text(root, yscrollcommand=yscroll)
for i in range(1000):
    textbox.insert(END, '{}\n'.format(i))
textbox.pack(side=LEFT, fill=BOTH)
scrollbar.config(command=yview)
mainloop()

If you can't muddle out the details from the (sometimes confusing and incomplete) docs, play around with it. Basically, yview is called whenever the scrollbar is moved, and yscroll is called whenever the view is scrolled. The arguments to yscroll are obvious; those to yview less so, but the docs do explain them pretty well.

Note that, when you've set things up normally, dragging the scrollbar or swiping the trackpad or rolling the mousewheel over the scrollbar sends a yview, which makes our code call textbox.yview, which then sends a yscroll, and that does not cause a new yview (otherwise, there would be an infinite loop). So, you see both methods get called. On the other hand, swiping the trackpad or rolling the mousewheel over the text, or using the keyboard to move off the bottom, sends yscroll, which again does not cause a yview, so in this case you only see one of the two methods.

So, for example, if you change yview to not call textbox.yview, you can drag the scrollbar all you want, but the text view won't move. And if you change yscroll to not call scrollbar.set, you can swipe around the text all you want, but the scrollbar won't move.

If you want a horizontal scrollbar as well, everything is the same except with x in place of y. But ScrolledText doesn't do horizontal scrolling, so I assume you don't want it.


If you really do want to dig into ScrolledText, you can look at the source for your version, which is pretty trivial if you understand the example above. In fact, it's basically just an OO wrapper around the example above.

In at least 2.7 and 3.3, the ScrolledText is itself the Text, and its self.vbar is the Scrollbar. It sets yscrollcommand=self.vbar.set in its superclass initialization, and sets self.vbar['command'] = self.yview after vbar is constructed. And that's it.

So, just remove the explicit scrollbar creation, and access it as textbox.vbar, and the same hooking code as above works the same way:

from Tkinter import *
from ScrolledText import *

def yscroll(*args):
    print('yscroll: {}'.format(args))
    textbox.vbar.set(*args)

def yview(*args):
    print('yview: {}'.format(args))
    textbox.yview(*args)

root = Tk()    
textbox = ScrolledText(root)
for i in range(1000):
    textbox.insert(END, '{}\n'.format(i))
textbox.pack(side=LEFT, fill=BOTH)
textbox['yscrollcommand'] = yscroll
textbox.vbar.config(command=yview)
mainloop()

Just be aware that this (the fact that textbox is a normal Text, and textbox.vbar is its attached Scrollbar) isn't documented anywhere, so it could theoretically change one day.

Upvotes: 2

Related Questions