tkinter文本框同步滚动

tkinter文本框同步滚动

引言

不仅是在CSDN的Markdown编辑器中,在很多编辑器里,编辑文本框和呈现文本框是可以同步滚动的。这样既能体现即时呈现,也能方便编写者找到需要修改的位置。
因此,我希望实现用tkinter实现至少两个文本框的同步滚动。
介于需要绑定事件,为了方便,我在这里仅绑定<MouseWheel>事件,即鼠标滚动。

尝试

在网络上,其实也有一些文章关于tkinter文本框的同步滚动,其中都给出了如下代码:

from tkinter import *

def Wheel(event):#鼠标滚轮动作
    print(str(-1*(event.delta/120)))#windows系统需要除以120
    text2.yview_scroll(int(-1*(event.delta/120)), "units")
    text1.yview_scroll(int(-1*(event.delta/120)), "units")
root=Tk()
frame1=Frame(root)
frame1.pack()
text1=scrolledtext.ScrolledText(frame1)
text1.grid(row=0,column=0)
text2=scrolledtext.ScrolledText(frame1)
text2.grid(row=0,column=2)
for ii in range(50):
    text1.insert(tk.INSERT,str(ii)+'\n')#便于展示效果
    text2.insert(tk.INSERT,str(ii)+'\n')
text1.bind("<MouseWheel>", Wheel)
text2.bind("<MouseWheel>", Wheel)
root.mainloop()

实现的效果如下:

在这里插入图片描述

这段代码确实实现了同步滚动,但是,我们不难发现,这只能实现同时向上或向下滚动,而无法准确地显示文本框对应的位置,两个文本框内容差距甚远。

改进

既然<MouseWheel>返回的event没有用,那么我们干脆不用event作为参数,另辟蹊径。
事实上,最简单的办法就是得到文本框可视范围相对整个文本框内容的位置,同时调整另一个被绑定同步滚动的文本框。举个栗子:

#...
#假设代表位置的参数为y
def Wheel(y,other:list):
    #other是需要绑定同步滚动的文本框名称列表
    for i in other:
        i.yview('moveto',y)#调整纵向可视范围

#...
text1=scrolledtext.ScrolledText(frame1)
text1.grid(row=0,column=0)
text2=scrolledtext.ScrolledText(frame1)
text2.grid(row=0,column=2)
#y=??
text1.bind("<MouseWheel>",lambda event: Wheel(y,[text1,text2]))
text2.bind("<MouseWheel>",lambda event: Wheel(y,[text1,text2]))
#...

由此看来,只要获得了“y”,就可以实现同步滚动了。

~~本着能懒就懒的原则,~~先来找找Text组件源码中是否有能够返回目标文本框可视范围的函数。我目前学到初二,英语水平大致能够看懂源码中的注释,但是能够确定的是,文本框组件没有这类函数……

不过别忘了,我们用的是ScrolledText,还有滚动条可用。在Python的tkinter\scrolledtext.py中,我们可以看到文本框的滚动条绑定:

#...
self.vbar = Scrollbar(self.frame)
self.vbar.pack(side=RIGHT, fill=Y)
kw.update({'yscrollcommand': self.vbar.set})
Text.__init__(self, self.frame, **kw)
self.pack(side=LEFT, fill=BOTH, expand=True)
self.vbar['command'] = self.yview
# Copy geometry methods of self.frame without overriding Text
# methods -- hack!
#意思是:复制Frame的外观和属性,但不覆盖文本框(Frame是底层组件,这里省略)
text_meths = vars(Text).keys()
methods = vars(Pack).keys() | vars(Grid).keys() | vars(Place).keys()
methods = methods.difference(text_meths)

for m in methods:
    if m[0] != '_' and m != 'config' and m != 'configure':
        setattr(self, m, getattr(self.frame, m))
#...

滚动条是 self.vbar,在例子中是 text1.vbar

在滚动条中,使用 get() 函数即可获得滚动条的位置(0.0~1.0)。
现在我们可以继续实现真正的同步滚动了。

实现

总结前面的内容,最终编写以下代码:

#导入、窗口创建、窗口虚幻部分省略
#...
def Wheel(text,other:list):
    #text是作为可视范围依据的文本框
    #other是需要绑定同步滚动的文本框名称列表
    first,end=text.vbar.get()
    #get会返回滚动条距离顶端和低端相对位置,一般使用距离顶端的相对位置
    for i in other:
        i.yview('moveto',first)#调整纵向可视范围

#...
text1=scrolledtext.ScrolledText(frame1)
text1.grid(row=0,column=0)
text2=scrolledtext.ScrolledText(frame1)
text2.grid(row=0,column=2)

text1.bind("<MouseWheel>",lambda event: Wheel(text1,[text1,text2]))
text2.bind("<MouseWheel>",lambda event: Wheel(text2,[text1,text2]))
#...

改进后效果如下

在这里插入图片描述
Tin知识库
这样就可以使编辑框和呈现框处在内容大致对应的可是范围了

总结

其实这是两种截然不同的同步滚定方法,只不过我改进后可以实现显示同一个可视范围。
第一种同步滚动虽然不便用于编辑器,但也有其相应的应用场景,如应用于显示行数基本一致的两段对比材料,方便读者比对文本具体差别。

☀tkinter创新☀

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值