GUI的最终选择 Tkinter(五):Text用法
Text组件
绘制单行文本使用Label组件,多行选使用Listbox,输入框使用Entry,按钮使用Button组件,还有Radiobutton和Checkbutton组件用于提供单选或多选的情况,多个组件可以使用Frame组件先搭建一个框架,这样组合起来显示好看点,最后还学习了Scrollbar和Scale,Scrollbar组件用于实现滚动条,而Scale则是让用户在一个范围内选择一个确定的值。
Text(文本)组件用于显示和处理多行文本。在Tkinter的所有组件中,Text组件显得异常强大和灵活,它适用于处理多种任务,虽然该组件的蛀牙牡蛎是显示多行文本,但它长城被用于作为简单的文本编辑器和网页浏览器使用。
当创建一个Text组件的时候,它里面是没有内容的,为了给其插入内容,可以利用insert()方法以及INSERT或END索引号:
1 from tkinter import * 2 3 root = Tk() 4 text = Text(root,width=20,height=15) 5 text.pack() 6 text.insert(INSERT,"Python3 \n") #INSERT索引表示插入光标当前的位置 7 text.insert(END,"python算法") 8 mainloop()
执行结果:
Text组件不仅支持插入和编辑文本,它还支持插入image对象和windows组件。
1 from tkinter import * 2 3 root = Tk() 4 text = Text(root,width=20,height=15) 5 text.pack() 6 text.insert(INSERT,"I love Python3") #INSERT索引表示插入光标当前的位置 7 def show(): 8 print("被点了一下。。。") 9 b1 = Button(text,text="点我",command=show) 10 text.window_create(INSERT,window=b1) 11 mainloop()
执行结果:
下面代码将实现单击显示一张图片
1 from tkinter import * 2 3 root = Tk() 4 text = Text(root,width=20,height=15) 5 text.pack() 6 photo = PhotoImage(file='bg.gif') 7 def show(): 8 text.image_create(END,image=photo) 9 b1 = Button(text,text="点我",command=show) 10 text.window_create(INSERT,window=b1) 11 mainloop()
执行结果:
Indexes用法
Indexes(索引)是用来指向Text组件中文本的位置,跟Python的序列索引一样,Text组件索引也是对应实际字符之间的位置。
Tkinter提供一系列不同的索引类型:
- “line.column”(行/列)
- “line.end”(某一行的末尾)
- INSERT
- CURRENT
- END
- user-defined marks
- user-defined tags("tag.first","tag.last")
- selection(SELFIRST.SELLAST)
- window coordinate("@x,y")
- embedded object name(window,images)
- expressions
1. "line.column‘’
用行号和列号组成的字符串是常用的索引方法,它们将索引位置的行号和列号以字符串的形式表示出来(中间以“.”分隔,例如“1.0”)。需要注意的是:行号以1开始,列号则以0开始,还可以使用一下语法建索引:
“%d.%d”%(line,column)
指定超出现有文本的最后一行的行号,或超出一行中列数的列号都不会引发错误。对于这样的指定,Tkinter解释为已有内容的末尾的下一个位置。
需要注意的是,使用“行/列”的索引方式看起来像是浮点值,其实在需要指定索引的时候使用浮点值代替也是可以的:
1 from tkinter import * 2 3 root = Tk() 4 text = Text(root,width=20,height=15) 5 text.pack() 6 def show(): 7 text.insert(INSERT, "i love python") 8 print(text.get("1.2", 1.6)) 9 b1 = Button(text,text="点我",command=show) 10 text.window_create(INSERT,window=b1)
执行结果:
2. “line.end”
行号加上字符串“.end”的格式表示该行最后一个字符串的位置:
from tkinter import * root = Tk() text = Text(root,width=20,height=15) text.pack() def show(): text.insert(INSERT, "i love python") print(text.get("1.2", "1.end")) b1 = Button(text,text="点我",command=show) text.window_create(INSERT,window=b1) mainloop()
执行结果:
3. INSERT(或“insert”)
对应插入光标的位置
4. CURRENT(或“current”)
对应与鼠标坐标最接近的位置。不过,如果你紧按鼠标任何一个按钮,会直接到你松开才相应。
5. END(或“end”)
对应Text组件的文本缓冲区最后一个字符的下一个位置。
6. user-defined marks
user-defined marks是对Text组件中位置的命名。INSERT和CURRENT是两个预先命名好的marks,除此之外可以自定义marks。
7. User-defined tags
User-defined tags代表可以分配给Text组件的特殊事件绑定和风格。、
可以使用“tag.first”(使用tag的未必能是第一个字符之间)和“tag.last”(使用tag的文本的最后一个字符之后)语法表示标签的范围:
“%s.first”%tagname
“%s.last”%tagnam
8. selection(SELFIRST,SELLAST)
selection是一个名为SEL(或“sel”)的特殊tag,表示当前被选中的范围,可以使用SELFIRST和SELLAST来表示这个范围如果没有选中的内容,那么Tkinter会抛出一个TclError异常。
9. windows coordinate("@x.y")
可以使用串口坐标作为索引。例如在一个时间绑定中,你可以使用以下代码找到最接近的鼠标字符:
“@%d,%d”%(event.x,event.y)
10. embedded object name(window,images)
embedden object name 用于指向在Text组件中嵌入的window和image对象。要引用一个window,只要简单地讲一个Tkinter组件实例作为索引即可。引用一个嵌入的image,只需要使用相应的PhotoImage和BitmapImage对象。
11. expressions
expressions用于修改任何格式的索引,用字符串的形式实现修改索引的表达式,具体表达式实现如表:
表达式 | 含义 |
"+count chars" | 将索引向前(->)移动count个字符。可以越过换行符,但不能超过END的位置 |
"-count chars" | 将索引向后(<-)移动count个字符。可以越过换行符,但不能超过"1.0"的位置 |
"+count lines" | 将索引向前(->)移动count行,索引会尽量保持与移动前在同一列上,但如果移动后的那一行字符太少,将移动到该行的末尾。 |
"-count lines" | 将索引向后(,-)移动count行,索引会尽量保持与移动前在同一列上,但如果移动后的那一行字符太少,将移动到该行的末尾。 |
"linestart" | 将索引移动到当前索引所在行的起始位置。注意:使用的该表达式前面必须用一个空格隔开 |
"lineend" | 将索引移动到当前索引所在行的末尾位置。注意:使用的该表达式前面必须用一个空格隔开 |
"wordstart" | 将索引移动到当前索引指向的单词的开头。单词的定义是一系列字母、数字、下划线或任何为空白字符的组合。注意:使用该表达式前面必须用一个空格隔开 |
"wordend" | 将索引移动到当前索引指向的单词的末尾。单词的定义是一系列字母、数字、下划线或任何为空白字符的组合。注意:使用该表达式前面必须用一个空格隔开 |
提示:只要结果不产生歧义,关键字可以被缩写,空格可以是省略的,例如:“+ 5chars”可以缩写为“+5c”
在实现中,为了确保表达式为普通字符串,你可以使用str或格式化操作来创建一个表达式字符串。来看下面列子,删除插入光标前面的一个字符。
def backspace(event): event.widgt.delete("%s-1c"%INSERT,INSERT)
Marks用法
Marks通常是指嵌入到Text组件文本中的不可见对象,简单的说就是指定字符串间的位置,它会跟着相应的字符一起移动。Marks有INSERT,CURRENT和user-defined marks(用户自定义的Marks)。其中,INSERT和CURRENT是Tkinter预定义的特殊Marks,它不能够被删除。
INSERT(或insert)用于指定当前插入的光标的而为之,Tkinter会在该位置绘制一个闪烁的光标(因此并不是所有的Marks都不可见)。
CURRENT(或current)用于指定与与鼠标坐标最接近的位置。不过,如果你紧按鼠标任何一个按钮,它会知道你松开才响应。
还可以自定义任意数量的Marks,Marks的名字是由普通字符串组成,可以是除了空白字符的任何字符(为了避免歧义,你应该起一个有意义的名字),使用mark_ser()方法创建和移动Marks.
如果在一个Marks标记的位置之前插入或删除文本,那么Marks跟着一并移动,删除Marks需要使用mark_unset()方法,删除Marks周围的文本并并不会删除Marks本身。
其实Marks事实上就是索引,用于表示位置:
1 from tkinter import * 2 root = Tk() 3 text = Text(root,width=30,height=10) 4 text.pack() 5 text.insert(INSERT,'I love python') 6 text.mark_set("here","1.2") 7 text.insert('here',"插") 8 mainloop()
执行结果:
再如,如果Marks前边的内容发生改变,那么Marks的位置也会跟着移动(实际上,就是Mark会记住它后面的内容)
1 from tkinter import * 2 root = Tk() 3 text = Text(root,width=30,height=10) 4 text.pack() 5 text.insert(INSERT,'I love python') 6 text.mark_set("here","1.2") 7 text.insert('here',"插") 8 text.insert('here',"入") 9 mainloop()
执行结果:
再如,如果Marks周围的文本被删了,Mark仍然在:
1 from tkinter import * 2 root = Tk() 3 text = Text(root,width=30,height=10) 4 text.pack() 5 text.insert(INSERT,'I love python') 6 text.mark_set("here","1.2") 7 text.insert('here',"插") 8 text.delete('1.0',END) 9 text.insert('here',"入") 10 mainloop()
执行结果:
再如,只有mark_unset()方法可以解除Mark的封印;
1 from tkinter import * 2 root = Tk() 3 text = Text(root,width=30,height=10) 4 text.pack() 5 text.insert(INSERT,'I love python') 6 text.mark_set("here","1.2") 7 text.insert('here',"插") 8 text.mark_unset("here") 9 text.delete('1.0',END) 10 text.insert('here',"入") 11 mainloop()
错误信息:
--------------------------------------------------------------------------- TclError Traceback (most recent call last) <ipython-input-7-2c32c5656dea> in <module>() 8 text.mark_unset("here") 9 text.delete('1.0',END) ---> 10 text.insert('here',"入") 11 mainloop() c:\python36\lib\tkinter\__init__.py in insert(self, index, chars, *args) 3264 """Insert CHARS before the characters at INDEX. An additional 3265 tag can be given in ARGS. Additional CHARS and tags can follow in ARGS.""" -> 3266 self.tk.call((self._w, 'insert', index, chars) + args) 3267 def mark_gravity(self, markName, direction=None): 3268 """Change the gravity of a mark MARKNAME to DIRECTION (LEFT or RIGHT). TclError: bad text index "here"
默认插入内容到Mark,是插入到他的左侧(就是说插入一个字符的话,Mark向后移动了一个字符的位置)。那么能不能插入到Mark的右侧了?答案是可以的,通过mark_gravity()方法就可以实现。
Tags用法
Tags(标签)通常用于改变Text组件中内容的样式和功能。可以用来修改文本的字体,尺寸和颜色。另外,Tags还允许将文本,嵌入的组件和图片与键盘鼠标等事件相关联。除了user-defined tags(用户自定义的Tags),还有一个预定义的特殊Tags:SEL.
SEL(或sel)用于表示对象的选中内容(如果有的话)
可以自定义任意数量的Tags,Tags的名字是有普通字符串组成,可以是除了空白字符以外的额任意字符。另外,任何文本内容都支持多个Tags描述,任何Tags也可以用于描述多个不同的文本内容。
为指定文本添加Tags可以使用tag_add()方法:
1 from tkinter import * 2 root = Tk() 3 text = Text(root,width=30,height=10) 4 text.pack() 5 text.insert(INSERT,'I love python3.6') 6 text.tag_add("tag1","1.7","1.12","1.14") 7 text.tag_config("tag1",background="yellow",foreground="red") 8 mainloop()
执行结果:
如上,使用tag_config()方法可以设置Tags的样式。下表列举了tag_config()可以使用的选项。
如上,使用 tag_config() 方法可以设置 Tags 的样式。下面罗列了 tag_config() 方法可以使用的选项:
选项 | 含义 |
---|---|
background |
①指定该Tag所描述的内容的背景颜色 ②注意:bg并不是该选项的缩写,在这里bg被解释成bgstipple选项的缩写 |
bgstipple |
①指定一个位图作为背景,并使用background选项指定的颜色填充 ②只有设定了background选项,该选项才会生效 ③默认的标准位图有:'error', 'gray75', 'gray50', 'gray25', 'gray12', 'hourglass', 'info', 'questhead', 'question'和'warning' |
borderwidth |
①指定文本框的宽度 ②默认值是0 ③只有设定了relief选项该选项才会生效 ④注意:该选项不能使用bd缩写 |
fgstipple |
①指定一个位图作为前景色 ②默认的标准位图有:'error', 'gray75', 'gray50', 'gray25', 'gray12', 'hourglass', 'info', 'questhead', 'question'和'warning' |
font | ①指定该Tag所描述的内容使用的字体 |
foreground |
①指定该Tag所描述的内容使用的前景色 ②注意:fg并不是该选项的缩写,在这里fg被解释为fgstipple的缩写 |
justify |
①控制文本的对齐方式 ②默认是LEFT(左对齐),还可以选择RIGHT(右对齐)和CENTER(居中) ③注意:需要将Tag指向该行的第一个字符,该选项才能生效 |
Imargin1 |
①设置Tag指向的文本块第一行的缩进 ②默认值是0 ③注意:需要将Tag指向该行的第一个字符或整个文本块,该选项才能生效 |
Imargin2 |
①设置Tag指向的文本块除了第一行其他行的缩进 ②默认值是0 ③注意:需要将Tag指向整个文本块,该选项才能生效 |
offset |
①设置Tag指向的文本相对于基线的偏移距离 ②可以控制文本相对于基线是升高(正数值)或者降低(负数值) ③默认值是0 |
overstrike |
①在Tag指定的文本范围画一条删除线 ②默认值是False |
relief |
①指定Tag对应范围的文本的边框样式 ②可以使用的值有:SUNKEN,RAISED,GROOVE,RIDGE或FLAT ③默认值是FLAT(没有边框) |
margin |
①设置Tag指向的文本块右侧的缩进 ②默认值是0 |
spacing1 |
①设置Tag所描述的文本块中每一行与上方的文本间隔 ②注意:自动换行不算 ③默认值是0 |
spacing2 |
①设置Tag所描述的文本块中自动换行的各行间的空白间隔 ②注意:换行符("\n")不算 ③默认值是0 |
spacing3 |
①设置Tag所描述的文本块中每一行与下方的文本间隔 ②注意:自动换行不算 ③默认值是0 |
tabs |
①定制Tag所描述的文本块中Tab按键的功能 ②默认Tab被定义为8个字符的宽度 ③你还可以定制多个制表位:tabs=('3c', '5c', '12c')表示前三个Tab的宽度分别为3cm,5cm,12cm,接着的Tab按照最后两个的差值计算,即:19cm,26cm,33cm ④你应该注意到,它上边'c'的含义是“厘米”而不是“字符”,还可以选择的单位有"i"(英寸),"m"(毫米),"p"(DPI,大约是'1i'等于'72p') ⑤如果是一个整型值,则单位是像素 |
underline |
①该选项设置为True的话,则Tag所描述的范围内的文本将被画上下划线 ②默认值是False |
wrap |
①设置当一行文本的长度超过width选项设置的宽度时,是否自动换行。 ②该选项的值可以是:NONE(不自动换行),CHAR(按字符自动换行)和WORD(按单词自动换行) |
如果对于一个范围内容的文本加上多个Tags,并且相同的选项,那么新创建的Tag样式会覆盖比较旧的Tag:
1 from tkinter import * 2 root = Tk() 3 text = Text(root,width=30,height=10) 4 text.pack() 5 text.tag_config("tag1",background="yellow",foreground="red") 6 text.tag_config("tag2",foreground="blue") 7 #tag2中foreground将覆盖tag1中的foreground,tag2中没有background,所以用tag1的 8 text.insert(INSERT,'I love python3.6',("tag1","tag2")) 9 mainloop()
执行结果:
不过,这里可以使用tagraise()和tag_lower()方法来提高和降低某个Tag的优先级:
1 from tkinter import * 2 root = Tk() 3 text = Text(root,width=30,height=10) 4 text.pack() 5 text.tag_config("tag1",background="yellow",foreground="red") 6 text.tag_config("tag2",foreground="blue") 7 text.tag_lower("tag1") 8 text.insert(INSERT,'I love python3.6',("tag1","tag2")) 9 mainloop()
执行结果:
Tag还可以支持时间的绑定,绑定时间使用的是tag_bind()方法。下面举个例子:让文本(“python3.6”)与鼠标事件进行绑定,当鼠标进入该文本段的时候,鼠标样式切换“arrow”状态,离开文本段的时候切换回“xterm”形态。当触发鼠标“左键单击操作”时间的时候,使用默认浏览器打开绑定的链接:
1 from tkinter import * 2 root = Tk() 3 text = Text(root,width=30,height=10) 4 text.pack() 5 text.insert(INSERT,'I love python3.6') 6 text.tag_add("link","1.7","1.16") 7 text.tag_config("link",foreground="blue",underline=True) 8 def show_hand_cursor(event): 9 text.config(cursor="arrow") 10 def show_arrow_cursor(event): 11 text.config(cursor="xterm") 12 def click(event): 13 webbrowser.open("http://www.baidu.com") 14 text.tag_bind("link","<Enter>",show_hand_cursor) 15 text.tag_bind("link","<Leave>",show_arrow_cursor) 16 text.tag_bind("link","<Button-1>",click) 17 mainloop()
执行结果:
下面给大家介绍几个比较实用的Text组件
第一个是判断内容是否发生变化,例如做一个记事本,当用户关闭的时候,程序需要检查内容是否有改动,如果有,需要提醒用户保存。下面的例子中,可以通过小燕Text组件中文本的MD5摘要来判断内筒是否发生改变。
1 from tkinter import * 2 import hashlib 3 root = Tk() 4 text = Text(root,width=20,height=5) 5 text.pack() 6 text.insert(INSERT,"I love Python3.x") 7 contents = text.get(1.0,END) 8 def getSig(contents): 9 m = hashlib.md5(contents.encode()) 10 return m.digest() 11 sig = getSig(contents) 12 def check(): 13 contents = text.get(1.0,END) 14 if sig != getSig(contents): 15 print("内容没有保存") 16 else: 17 print("文件没有改动") 18 Button(root,text="检查",command=check).pack() 19 mainloop()
执行结果:
第二个是查找操作,是使用search()方法可以搜索Text组件中的内容,可以提供一个确切的目标进行搜索(默认),也可以使用Tcl格式的正则表达式进行搜索需要设置regexp选项为True(:):
1 from tkinter import* 2 root = Tk() 3 text = Text(root,width=30,height=5) 4 text.pack() 5 text.insert(INSERT,"I love python3.6") 6 #将任何格式的索引号统一为元组(行,列)的格式输出 7 def getIndex(text,index): 8 return tuple(map(int,str.split(text.index(index),"."))) 9 start = 1.0 10 while True: 11 pos = text.search("o",start,stopindex=END) 12 if not pos: 13 break 14 print("找到了,位置是:",getIndex(text,pos)) 15 start = pos + "+1c" #将start指向下一个字符 16 mainloop()
执行结果:
找到了,位置是: (1, 3) 找到了,位置是: (1, 11)
这里第一是找出“o"的位置,然后以"o"为起点,继续找第二个位置,直到找完
注意:这里如果忽略stopindex选项,表示知道文本的末尾结束搜索。设置backwards选项为True,则是修改搜索的方向(变为向后搜索,那么start遍历应该设置为END,stopindex选项设置为1.0,最后+1c设置为-1c )
最后,Text组件还支持“恢复”和“撤销”操作,这使得Text组件显得相当高大上。通过设置undo选项为True,可以开启Text组件的撤销功能,然后利用editundo可以开启实现撤销操作,用editredo()方法可以实现恢复操作。
1 from tkinter import * 2 import hashlib 3 root = Tk() 4 text = Text(root,width=20,height=5) 5 text.pack() 6 text.insert(INSERT,"I love Python3.x") 7 def show(): 8 text.edit_undo() 9 Button(root,text="撤销",command=show).pack() 10 mainloop()
执行结果:
点击一次撤销后,插入进去的数据已经没有 了
这是因为Text组件内部有一个栈专门用于记录每次变动,所以每次“撤销”操作就是一次弹栈操作,“恢复”就是再次压栈。
默认情况下,每次完整的操作都会放入栈中。但怎么样算是一次完整的操作了?Tkinter觉得每次焦点切换、用户按下回车键、删除/插入操作的转换等之前的操作算是一次完整的操作。也就是说,你连续输入“python”的话,一次“撤销”操作就会将所有内容删除。
但是我们可以自定义,做法就是先将autoseparators选项设置为False(因为这个选项是让Tkinter在人为一次完成的操作结束后自动插入“分隔符”,然后绑定键盘事件,每次有输入就用edit_separator()方法人为插入一个“分隔符”:
1 from tkinter import * 2 import hashlib 3 root = Tk() 4 text = Text(root,width=20,height=5,autoseparators=False,undo=True,maxundo=10) 5 text.pack() 6 def callback(): 7 text.edit_separator() 8 text.bind("<Key>",callback) 9 text.insert(INSERT,"I love Python3.x") 10 def show(): 11 text.edit_undo() 12 Button(root,text="撤销",command=show).pack() 13 mainloop()