最近在使用tkinter制作一个文字编辑器,然后花了好久时间终于研究了一个像pycharm那样显示文字行数的效果,如图所示: 直接放源代码,然后再详细说明
from tkinter import *
from tkinter import scrolledtext
from threading import Thread, RLock
class Main(Tk):
def __init__(self):
super().__init__()
self.thread_lock = RLock()
self.txt = ""
self._main()
def _main(self):
self.resizable(True, True)
self.geometry("800x600")
self.edit_frame = Canvas(self, height=600, width=800,
bg="white", highlightthickness=0)
self.edit_frame.pack()
self.line_text = Text(self.edit_frame, width=7, height=600, spacing3=5,
bg="#DCDCDC", bd=0, font=("等线等线 (Light)", 14), takefocus=0, state="disabled",
cursor="arrow")
self.line_text.pack(side="left", expand="no")
self.update()
self.edit_text = scrolledtext.ScrolledText(self.edit_frame, height=1, wrap="none", spacing3=5,
width=self.winfo_width() - self.line_text.winfo_width(), bg="white",
bd=0, font=("等线等线 (Light)", 14), undo=True, insertwidth=1)
self.edit_text.vbar.configure(command=self.scroll)
self.edit_text.pack(side="left", fill="both")
self.line_text.bind("<MouseWheel>", self.wheel)
self.edit_text.bind("<MouseWheel>", self.wheel)
self.edit_text.bind("<Control-v>", lambda e: self.get_txt_thread())
self.edit_text.bind("<Control-V>", lambda e: self.get_txt_thread())
self.edit_text.bind("<Key>", lambda e: self.get_txt_thread())
self.show_line()
def wheel(self, event):
self.line_text.yview_scroll(int(-1 * (event.delta / 120)), "units")
self.edit_text.yview_scroll(int(-1 * (event.delta / 120)), "units")
return "break"
def scroll(self, *xy):
self.line_text.yview(*xy)
self.edit_text.yview(*xy)
def get_txt_thread(self):
Thread(target=self.get_txt).start()
def get_txt(self):
self.thread_lock.acquire()
if self.txt != self.edit_text.get("1.0", "end")[:-1]:
self.txt = self.edit_text.get("1.0", "end")[:-1]
self.show_line()
else:
self.thread_lock.release()
def show_line(self):
sb_pos = self.edit_text.vbar.get()
self.line_text.configure(state="normal")
self.line_text.delete("1.0", "end")
txt_arr = self.txt.split("\n")
if len(txt_arr) == 1:
self.line_text.insert("1.1", " 1")
else:
for i in range(1, len(txt_arr) + 1):
self.line_text.insert("end", " " + str(i))
if i != len(txt_arr):
self.line_text.insert("end", "\n")
if len(sb_pos) == 4:
self.line_text.yview_moveto(0.0)
elif len(sb_pos) == 2:
self.line_text.yview_moveto(sb_pos[0])
self.edit_text.yview_moveto(sb_pos[0])
self.line_text.configure(state="disabled")
try:
self.thread_lock.release()
except RuntimeError:
pass
if __name__ == "__main__":
run = Main()
run.mainloop()
先讲下主体思路(有点儿硬核): 首先在_main()函数里创建了两个Text,一个用于显示行数(line_text),一个用于打字(edit_text)。 每在edit_text中每输入字符,就获取文本框里的内容(因为get()获取Text内容得到的字符串末尾自动回加上一个\n,所以用[:-1]截取到倒二个字符),然后用字符串split函数将"\n"回车分隔开,这时候得到列表长度就是有多少行。 然后用循环往line_text添加数字以显示行数。
细节方面(实现的关键!!): 1.我将line_text的cursor参数改为"arrow",为了让鼠标移上去是正常状态而不是输入状态。将takefocus设为0,防止line_text获得焦点。将state设为"disabled",在往里添加数字时再设为"normal",防止在line_text中误输入。
2.可以发现我用edit.text绑定了一个Key的键盘事件,执行的函数为get_txt_thread(),开启self.get_txt函数的线程,而不是将执行直接设置为self.get_txt(),那是因为我发现输入在执行事件后,意思就是当一个键按下时,先执行了键盘绑定的事件,再向文本框里输入东西,假设我先按下了1再按下2,文本框里是显示出了12,但实际获取的Text里的东西只有1,因此我就用多线程的方法硬核地让执行事件和输入同步执行,这样就能在每次输入时正确得到文本框里的内容了
3.光加一个多线程还不够,在执行线程里的代码前要加上线程锁,执行结束后再解开,这样让线程按顺序执行是为了防止用输入法一次性输入多个字符时造成line_text公用而显示错误
4.要实现滚轮同时控制line_text和edit_text,我运用了这篇文章提供的思路: tkinter实现一个滚动条同时控制两个部件
5.将edit_text也绑定键盘按下ctrl+v,这样在粘贴文字的时候也会触发一次show_line
那么这篇文章就到介绍这里,觉得不错的就给个一键三连吧!
|