Reputation: 151
I am trying to make two text widget's scrolling synchronize. So far I've achieved that using an Scrollbar, when using the scrollbar it works fine. But for example, when I have the focus on one of the text widgets and I use the mousewheel to scroll, only the text widget with the focus is scrolled, the scrollbar is also updated but the other text remains the same. The same behaviour occurs when using page down or page up keys and as fas as I know for every form of scrolling that doesn't use the scrollbar.
This is my code, I think only init is the relevant part where I bind the events, but just in case I decided to put all my code:
## HexText class
#
#
class HexText (tkk.Frame):
__POS_TEXT = "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"
__OFFSET_TEXT = "0x00000000"
__LINE_LENGTH = len(__POS_TEXT)
def __init__(self, master):
super(HexText, self).__init__(master)
self.__create_widgets()
self.__organize_widgets()
def __scrolls(self, *args):
self.__data.yview(*args)
self.__offset.yview(*args)
def __create_widgets(self):
self.__scrollbar = tkk.Scrollbar(self)
self.__scrollbar["orient"] = tk.VERTICAL
self.__scrollbar["command"] = self.__scrolls
self.__data = tk.Text(self)
self.__data["height"] = 8
self.__data["width"] = HexText.__LINE_LENGTH
self.__data["state"] = tk.DISABLED
self.__data["relief"] = tk.GROOVE
self.__data["yscrollcommand"] = self.__scrollbar.set
self.__offset = tk.Text(self)
self.__offset["height"] = 8
self.__offset["width"] = len(HexText.__OFFSET_TEXT)
self.__offset["state"] = tk.DISABLED
self.__offset["relief"] = tk.FLAT
self.__offset["bg"] = self.winfo_toplevel()["bg"]
self.__offset["yscrollcommand"] = self.__scrollbar.set
self.__pos = tk.Text(self)
self.__pos.insert(tk.CURRENT, HexText.__POS_TEXT)
self.__pos["height"] = 1
self.__pos["width"] = HexText.__LINE_LENGTH
self.__pos["state"] = tk.DISABLED
self.__pos["relief"] = tk.FLAT
self.__pos["bg"] = self.winfo_toplevel()["bg"]
def __organize_widgets(self):
self.__pos.grid(row = 0, column = 1, sticky = tk.N + tk.E + tk.W + tk.S)
self.__offset.grid(row = 1, column = 0, sticky = tk.N + tk.E + tk.W + tk.S)
self.__data.grid(row = 1, column = 1, sticky = tk.N + tk.E + tk.W + tk.S)
self.__scrollbar.grid(row = 1, column = 2, sticky = tk.N + tk.E + tk.W + tk.S)
@staticmethod
def __get_char_index(string):
i = str.find(string, '.')
if i >= 0:
i = int(string[i+1:])
else:
raise ValueError
return i
@staticmethod
def __get_line_index(string):
i = str.find(string, '.')
if i >= 0:
i = int(string[:i])
else:
raise ValueError
return i
@staticmethod
def __get_hex_value(string):
if (len(string) != 1):
raise ValueError
i = "%02X" % ord(string)
return i
def __update_offset(self, line_index):
i = "0x%08X\n" % ((line_index) * 0x10)
self.__offset["state"] = tk.NORMAL
self.__offset.insert(tk.CURRENT, i)
self.__offset["state"] = tk.DISABLED
def __append(self, string):
self.__data["state"] = tk.NORMAL
self.__data.insert(tk.CURRENT, string)
self.__data["state"] = tk.DISABLED
def __write_char(self, string):
str_index = self.__data.index(tk.CURRENT)
i = HexText.__get_char_index(str_index)
if (len(string) != 1):
raise ValueError
if (i == 0):
self.__update_offset(HexText.__get_line_index(str_index) - 1)
if (i == HexText.__LINE_LENGTH - 2):
self.__append(HexText.__get_hex_value(string) + '\n')
else:
self.__append(HexText.__get_hex_value(string) + ' ')
def write_str(self, string):
for chars in string:
self.__write_char(chars)
This is an image of the widget I'm trying to create, a simple hex viewer (both text widgets have the same amount of lines):
https://i.sstatic.net/Yb8IH.png
So my question is, should I handle all the page up, page down, mousewheel and every other form of scrolling independently? Isn't there a more simple way of having both text widgets have the same scrolling all the time?
Upvotes: 6
Views: 3288
Reputation: 319
I know this is a bit old but this solution works perfectly. The Text widgets react to rolling the mouse wheel by default so there's no need to bind anything.
import sys
if sys.version[0] < '3':
from Tkinter import *
else:
from tkinter import *
class ScrolledTextPair(Frame):
'''Two Text widgets and a Scrollbar in a Frame'''
def __init__(self, master, **kwargs):
Frame.__init__(self, master) # no need for super
# Different default width
if 'width' not in kwargs:
kwargs['width'] = 30
# Creating the widgets
self.left = Text(self, **kwargs)
self.left.pack(side=LEFT, fill=BOTH, expand=True)
self.right = Text(self, **kwargs)
self.right.pack(side=LEFT, fill=BOTH, expand=True)
self.scrollbar = Scrollbar(self)
self.scrollbar.pack(side=RIGHT, fill=Y)
# Changing the settings to make the scrolling work
self.scrollbar['command'] = self.on_scrollbar
self.left['yscrollcommand'] = self.on_textscroll
self.right['yscrollcommand'] = self.on_textscroll
def on_scrollbar(self, *args):
'''Scrolls both text widgets when the scrollbar is moved'''
self.left.yview(*args)
self.right.yview(*args)
def on_textscroll(self, *args):
'''Moves the scrollbar and scrolls text widgets when the mousewheel
is moved on a text widget'''
self.scrollbar.set(*args)
self.on_scrollbar('moveto', args[0])
# Example
if __name__ == '__main__':
root = Tk()
t = ScrolledTextPair(root, bg='white', fg='black')
t.pack(fill=BOTH, expand=True)
for i in range(50):
t.left.insert(END,"foo %s\n" % i)
t.right.insert(END,"bar %s\n" % i)
root.title("Text scrolling example")
root.mainloop()
Upvotes: 12
Reputation: 2202
To answer your question if the event handling should be done independently for each scrollbar - that is a decision you have to make. If you want to only make this widget for this purpose, you can handle both together not on their own. Create a custom event handler for that and call the setters/getters accordingly.
If you have the two widgets (including the two scrollbars) are in a main widget where you want to have the events bound, bind them over there using
widget.bind(<EVENT>, handler)
or using widget.bind_all(<EVENT>, handler)
to bind also the events raised from child widgets to these handlers.
As you are already using the same handler (self.__scrollbar.set
) in your code, you could just use a custom event handler to bind Page Up
/ Page Down
keys to scroll by a specific offset, and one custom to scroll by MouseWheel
- Event.
If you scroll independently and call both scrolling functions at these handlers or if you scroll both in one function is up to you, as mentioned above.
As the scrolling needs to be called for each tk.Text
-Widget I personally prefer calling both scrollings in one handler bound to the parent widget and all below (so using bind_all
), but that is a matter of personal preference I think.
Upvotes: 0