不仅是在CSDN的Markdown编辑器中,在很多编辑器里,编辑文本框和呈现文本框是可以同步滚动的。这样既能体现即时呈现,也能方便编写者找到需要修改的位置。
因此,我希望实现用tkinter实现至少两个文本框的同步滚动。
介于需要绑定事件,为了方便,我在这里仅绑定
在网络上,其实也有一些文章关于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("" , Wheel)
text2.bind("" , Wheel)
root.mainloop()
实现的效果如下:
这段代码确实实现了同步滚动,但是,我们不难发现,这只能实现同时向上或向下滚动,而无法准确地显示文本框对应的位置,两个文本框内容差距甚远。
既然
事实上,最简单的办法就是得到文本框可视范围相对整个文本框内容的位置,同时调整另一个被绑定同步滚动的文本框。举个栗子:
#...
#假设代表位置的参数为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("" ,lambda event: Wheel(y,[text1,text2]))
text2.bind("" ,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("" ,lambda event: Wheel(text1,[text1,text2]))
text2.bind("" ,lambda event: Wheel(text2,[text1,text2]))
#...
改进后效果如下
Tin知识库
这样就可以使编辑框和呈现框处在内容大致对应的可是范围了
其实这是两种截然不同的同步滚定方法,只不过我改进后可以实现显示同一个可视范围。
第一种同步滚动虽然不便用于编辑器,但也有其相应的应用场景,如应用于显示行数基本一致的两段对比材料,方便读者比对文本具体差别。
☀tkinter创新☀