19. Progressbar
进度条,主要用来当做一个工作进度指针,在这个控件中会有一个指针,由此指针可以了解工作进度。
Progressbar(父对象,options,…)
- length:进度条的长度,默认100像素;
- mode:两种模式;
- determinate:默认,一个指针会从起点移至终点,通常当我们知道所需工作时间时,可以使用此模式;
- indeterminate:一个指针会在起点和终点之间来回移动,通常当我们不知道工作所需时间时,可以使用此模式;
- maximum:进度条的最大值,默认是100;
- name:进度条的名称,供程序参考引用;
- orient:进度条的方向;
- value:进度条的当前值;
- variable:记录进度条当前值的变量。
使用update方法更新value值,可以达到动画效果。
from tkinter import * from tkinter.ttk import * import time def running(): for i in range(100): pb["value"] = i+1 root.update() time.sleep(0.05) root = Tk() root.title("Ex") pb = Progressbar(root, length = 200, mode = "determinate", orient = HORIZONTAL) pb.pack(padx = 10, pady = 10) pb["maximum"] = 100 pb["value"] = 0 btn = Button(root, text = "Running", command = running) btn.pack(pady = 10) root.mainloop()
Progressbar的方法:
- start(interval):每隔interval时间移动一次指针,默认50ms,每次指针移动时调用一次step(delta);
- step(delta):每次增加delta,默认值1.0;
- stop():停止start()的运行。
20. Menu
Menu(父对象,options,…)
- bd,bg,cursor,font,fg;
- activebackground:当光标移至此菜单列表上时的背景色彩;
- activeborderwidth:当光标移至此菜单列表上时的前景色彩;
- disabledforeground:菜单列表是DISABLED时的色彩;
- image:菜单的图标;
- tearoff:菜单上方的分割线,True或False;
- add_cascade():建立分层菜单,同时让此子功能列表与父菜单建立链接;
- add_command():增加菜单列表;
- add_separator():增加菜单列表的分割线。
def hello(): messagebox.showinfo("Hello", "欢迎使用菜单") root = Tk() root.title("Ex") root.geometry("300x160") menubar = Menu(root) menubar.add_command(label = "Hello", command = hello) menubar.add_command(label = "Exit", command = root.destroy) root.config(menu = menubar) root.mainloop()
20.1 下拉菜单
def File(): messagebox.showinfo("File", "New File") root = Tk() root.title("Ex") root.geometry("300x160") menubar = Menu(root) filemenu = Menu(menubar, tearoff = False) menubar.add_cascade(label = "File", menu = filemenu) filemenu.add_command(label = "New File", command = File) filemenu.add_command(label = "Exit", command = root.destroy) root.config(menu = menubar) root.mainloop()
分割线:在add_command()中间穿插使用filemenu.add_separator()即可。
20.2 Alt快捷键
快捷键是在某个菜单类别或列表指定的英文字符串内为单一字母增加下划线,然后可以用Alt键先启动根菜单,在菜单中可以直接按字母键启动相应功能。
设计方法是在add_command()和add_cascade()中使用underline参数。
menubar.add_cascade(label = "File", menu = filemenu, underline = 0)
filemenu.add_command(label = "New File", command = File, underline = 0)
filemenu.add_command(label = "Exit", command = root.destroy, underline = 0)
20.3 Ctrl快捷键
filemenu.add_command(label = "New File", command = File, underline = 0,
accelerator = "Ctrl+N")…
root.bind("<Control-n>",
lambda event:messagebox.showinfo("File", "New File"))
20.4 弹出式菜单
右键快捷菜单。
建立Menu对象后直接与右键绑定即可。
def minimizeIcon(): root.iconify() def showPopupMenu(event): popupmenu.post(event.x_root, event.y_root) root = Tk() root.title("Ex") root.geometry("300x160") popupmenu = Menu(root, tearoff = False) popupmenu.add_command(label = "Minimize", command = minimizeIcon) popupmenu.add_command(label = "Exit", command = root.destroy) root.bind("<Button-3>", showPopupMenu) root.mainloop()
20.5 add_checkbutton()
def status(): if demoStatus.get(): statusLabel.pack(side = BOTTOM, fill = X) else: statusLabel.pack_forget() root = Tk() root.title("Ex") root.geometry("300x160") menubar = Menu(root) filemenu = Menu(menubar, tearoff = False) menubar.add_cascade(label = "File", menu = filemenu) filemenu.add_command(label = "Exit", command = root.destroy) viewmenu = Menu(menubar, tearoff = False) menubar.add_cascade(label = "View", menu = viewmenu) demoStatus = BooleanVar() demoStatus.set(True) viewmenu.add_checkbutton(label = "Status", command = status, variable = demoStatus) root.config(menu = menubar) statusVar = StringVar() statusVar.set("显示") statusLabel = Label(root, textvariable = statusVar, relief = "raised") statusLabel.pack(side = BOTTOM, fill = X) root.mainloop()
21. Text
Text控件可以看做是Entry的扩充,可以处理多行输入,也可以在文字中嵌入图像或提供格式化功能,实际上可以将Text当做文字处理软件,甚至当做网页浏览器。
Text(父对象,options,…)
- bg,bd,cursor,fg,font,height,highlightbackground,highlightcolor,highlightthickness,padx,pady,relief,selectbackground,selectborderwidth,selectforeground,width,wrap,xscrollcommand,yscrollcommand;
- exportselection:执行选择操作时,所选择的字符串会自动输出至剪贴板,设置exportselection=0以避免;
- insertbackground:插入光标的色彩,默认是黑色;
- insertborderwidth:围绕插入游标的3D厚度,默认为0;
- state:默认NORMAL,DISABLED则是无法编辑;
- tab:可设置按Tab键时,如何定位插入点。
21.1 insert()
使用insert(index, string)可以将字符串插入到指定位置。
root = Tk() root.title("Ex") text = Text(root, height = 3, width = 30) text.pack() text.insert(END, "Python Tkinter ") text.insert(END, "I like you.") root.mainloop()
21.2 Scrollbar
root = Tk() root.title("Ex") yscrollbar = Scrollbar(root) yscrollbar.pack(side = RIGHT, fill = Y) text = Text(root, height = 5, width = 30) text.pack() yscrollbar.config(command = text.yview) text.config(yscrollcommand = yscrollbar.set) string = """Tkinter模块("Tk 接口")是Python的标准Tk GUI工具包的接口. Tk和Tkinter可以在大多数的Unix平台下使用,同样可以应用在Windows和 Macintosh系统里.Tk8.0的后续版本可以实现本地窗口风格,并良好地运行 在绝大多数平台中.""" text.insert(END, string) root.mainloop()
是Text文字区域随着窗口的扩充而扩充:
text.pack(fill = BOTH, expand = True)
21.3 字形
- family:Arial(默认)、Times、Courier;
- weight:norma(默认)、bold;
- size:字号。
from tkinter import * from tkinter.ttk import * from tkinter.font import Font def familyChanged(event): f = Font(family = familyVar.get()) text.configure(font = f) def weightChanged(event): f = Font(weight = weightVar.get()) text.configure(font = f) def sizeSelected(event): f = Font(size = sizeVar.get()) text.config(font = f) root = Tk() root.title("Ex") root.geometry("300x160") toolbar = Frame(root, relief = RAISED, borderwidth = 1) toolbar.pack(side = TOP, fill = X, padx = 2, pady = 1) familyVar = StringVar() familyFamily = ("", "Arial", "Times", "Courier") familyVar.set(familyFamily[1]) family = OptionMenu(toolbar, familyVar, *familyFamily, command = familyChanged) family.pack(side = LEFT, pady = 2) weightVar = StringVar() weightFamily = ("", "normal", "bold") weightVar.set(weightFamily[1]) weight = OptionMenu(toolbar, weightVar, *weightFamily, command = weightChanged) weight.pack(side = LEFT, pady = 2) sizeVar = IntVar() size = Combobox(toolbar, textvariable = sizeVar) sizeFamily = [x for x in range(8, 31)] size["value"] = sizeFamily size.current(4) size.bind("<<ComboboxSelected>>", sizeSelected) size.pack(side = LEFT, pady = 2) text = Text(root) text.pack(fill = BOTH, expand = True, padx = 2, pady = 2) text.focus_set() root.mainloop()
21.4 选取文字
在使用Text文字区域时,如果有选取文字的操作发生时,Text对象会将所选文字的起始索引放在SEL_FIRST,结束索引放在SEL_LAST,将它们当做get()的参数,就可以获得目前所选的文字。
def selectedText(): try: selText = text.get(SEL_FIRST, SEL_LAST) print("选取文字:", selText) except TclError: print("没有选取文字!") root = Tk() root.title("Ex") root.geometry("300x160") btn = Button(root, text = "Print selection", command = selectedText) btn.pack(pady = 3) text = Text(root) text.pack(fill = BOTH, expand = True, padx = 3, pady = 3) text.insert(END, "Love You Like A Love Song") root.mainloop()
认识Text的索引:
Text对象的索引并不是单一数字,而是一个字符串,索引的目的是让Text控件处理更进一步的文件操作。
- line/column("line.column"):计数方式line从1开始。column从0开始,中间用句点分隔;
- INSERT:目前插入点的位置;
- CURRENT:光标目前为止相对于字符的位置;
- END:缓冲区最后一个字符的位置;
- Expression:索引使用表达式;
- "+count chars":count是数字,例如"+2c"索引往后移动两个字符;
- "-count chars":count是数字,例如"-2c"索引往前移动两个字符。
21.5 书签
在编辑文件时,可以在文件特殊位置建立书签,方便查询。书签是无法显示的,但会在编辑系统内被记录,如果书签内容被删除,则此书签也将自动被删除。
- index(mark):传回指定书签的line和column;
- mark_names():传回这个Text对象所有的书签;
- mark_set(mark, index):在指定的index位置设置书签;
- mark_unset(mark):取消指定书签。
text.mark_set("mark1", "1.9")
text.mark_set("mark2", "1.13")
print(text.get("mark1", "mark2"))
21.6 标签
标签是一个文字区域,我们可以为这个区域去一个名字,有了标签后,我们可以针对此标签做更进一步的工作,例如,将字形、色彩等应用在此标签上。
- tag_add(tagname, startindex[, endindex] …):将startindex和endindex之间的文字命名为tagname标签;
- tag_config(tagname, options, …):可以为标签执行特定的编辑,或动作绑定;
- background,borderwidth,font,foreground,justify;
- overstrike:如果为True,加上删除线;
- underline:如果为True,加上下划线;
- wrap:当使用wrap模式时,可以使用NONE、CHAR、WORD;
- tag_delete(tagname):删除此标签,同时会移除此标签特殊的编辑或绑定;
- tag_remove(tagname[, startindex[, endindex]] …):删除标签,但是不移除此标签特殊的编辑或绑定。
除了可以使用tag_add()自行定义标签外,系统还有一个内建标签SEL,代表所选取的区间。
text.tag_add("tag1", "mark1", "mark2")
text.tag_config("tag1", foreground = "blue", background = "pink")
21.7 Cut/Copy/Paste
编辑文件时剪切/复制/粘贴(Cut/Copy/Paste)是很常用的功能,这些功能已经被内建在tkinter中了。
设计弹出菜单。
def cutJob(): copyJob() text.delete(SEL_FIRST, SEL_LAST) def copyJob(): try: text.clipboard_clear() #清除剪切板 copyText = text.get(SEL_FIRST, SEL_LAST) text.clipboard_append(copyText) except TclError: print("没有选取") def pasteJob(): try: copyText = text.clipboard_get() text.insert(INSERT, copyText) except: print("剪贴板没有数据") def showPopupMenu(event): popupmenu.post(event.x_root, event.y_root) root = Tk() root.title("Bouncing Ball") root.geometry("300x180") popupmenu = Menu(root, tearoff = False) popupmenu.add_command(label = "Cut", command = cutJob) popupmenu.add_command(label = "Copy", command = copyJob) popupmenu.add_command(label = "Paste", command = pasteJob) root.bind("<Button-3>", showPopupMenu) text = Text(root) text.pack(fill = BOTH, expand = True, padx = 3, pady = 2) text.insert(END, "Five Hundred Miles ") text.insert(END, "If you miss the train I'm on, ") text.insert(END, "You will know that I am gone. ") text.insert(END, "You can hear the whistle blow ") text.insert(END, "A hundred miles.") root.mainloop()
由于没有选取文字或剪贴板没有数据就进行读取会发生TclError,所以设计时增加了try… except设计。
直接引用tkinter的虚拟事件:
def cutJob():
text.event_generate("<<Cut>>")
def copyJob():
text.event_generate("<<Copy>>")
def pasteJob():
text.event_generate("<<Paste>>")
21.8 undo/redo
def undoJob():
try:
text.edit_undo()
except:
print("先前没有动作")
def redoJob():
try:
text.edit_redo()
except:
print("先前没有动作")
toolbar = Frame(root, relief = RAISED, borderwidth = 1)
toolbar.pack(side = TOP, fill = X, padx = 2, pady = 1)
undoBtn = Button(toolbar, text = "Undo", command = undoJob).pack(side = LEFT)
redoBtn = Button(toolbar, text = "Redo", command = redoJob).pack(side = LEFT)text = Text(root, undo = True)
…
21.9 查找文字
在Text控件内可以使用search()方法查找特定的字符串。
pos = text.search(key, startindex, endindex)
- pos:传回所到的字符串的索引位置,如果查找失败则传回空字符串;
- key:所查找的字符串;
- startindex:查找起始位置;
- endindex:查找结束位置,如果查找到文档末尾可以使用END。
def mySearch(): text.tag_remove("found", "1.0", END) start = "1.0" key = entry.get() if len(key.strip()) == 0: return while True: pos = text.search(key, start, END) if pos == "": break text.tag_add("found", pos, "%s+%dc" % (pos, len(key))) start = "%s+%dc" % (pos, len(key)) root = Tk() root.title("Bouncing Ball") root.geometry("300x180") root.rowconfigure(1, weight = 1) root.columnconfigure(0, weight = 1) btn = Button(root, text = "查找", command = mySearch) btn.grid(row = 0, column = 1, padx = 5, pady = 5) entry = Entry() entry.grid(row = 0, column = 0, padx = 5, pady = 5, sticky = W+E) text = Text(root, undo = True) text.grid(row = 1, column = 0, columnspan = 2, padx = 3, pady = 5, sticky = N+S+W+E) text.insert(END, "Five Hundred Miles ") text.insert(END, "If you miss the train I'm on, ") text.insert(END, "You will know that I am gone. ") text.insert(END, "You can hear the whistle blow ") text.insert(END, "A hundred miles.") text.tag_configure("found", background = "yellow") root.mainloop()
21.10 存储Text控件内容
使用tkinter.filedialog模块中的asksaveasfilename可以启动“另存为”对话框,然后选择文档存储的位置和文件名。
from tkinter.filedialog import asksaveasfilename def saveAsFile(): global filename textContent = text.get("1.0", END) filename = asksaveasfilename() if filename == "": return with open(filename, "w") as output: output.write(textContent) root.title(filename) root = Tk() root.title("Ex") root.geometry("300x180") menubar = Menu(root) filemenu = Menu(menubar, tearoff = False) menubar.add_cascade(label = "File", menu = filemenu) filemenu.add_command(label = "Save", command = saveAsFile) filemenu.add_command(label = "Exit", command = root.destroy) root.config(menu = menubar) text = Text(root, undo = True) text.pack(fill = BOTH, expand = True) text.insert(END, "Five Hundred Miles ") text.insert(END, "If you miss the train I'm on, ") text.insert(END, "You will know that I am gone. ") text.insert(END, "You can hear the whistle blow ") text.insert(END, "A hundred miles.") root.mainloop()
21.11 新建文档
def newFile():
text.delete("1.0", END)
root.title("Untitled")
filemenu.add_command(label = "New", command = newFile)
21.12 打开文档
from tkinter.filedialog import asksaveasfilename, askopenfilename
def openFile():
global filename
filename = askopenfilename()
if filename == "":
return
with open(filename, "r") as fileObj:
content = fileObj.read()
text.delete("1.0", END)
text.insert(END, content)
root.title(filename)
filemenu.add_command(label = "Open", command = openFile)
21.13 默认滚动条
from tkinter.scrolledtext import ScrolledText
text = ScrolledText(root, undo = True)
21.14 插入图像
Text控件是允许插入图像文件的,所插入的图像文件会被视为一个字符,所呈现的大小是图像文件的实际大小。
from PIL import Image, ImageTk root = Tk() root.title("Ex") root.geometry("300x160") img = Image.open("mystar.jpg") pic = ImageTk.PhotoImage(img) text = Text() text.image_create(END, image = pic) text.insert(END, " ") text.insert(END, "A yellow star") text.pack(fill = BOTH, expand = True) root.mainloop()
22. Treeview
tkinter.ttk中的控件,用于提供多栏的显示功能,可以成为树状表格数据。在设计时也可以在左边栏设计成树状结构(层次结构),用户可以隐藏任何部分。
Treeview(父对象,options,…)
- cursor,height;
- columns:栏位的字符串。其中,第一个栏位的图标栏,这是默认的,不在此进行设置,如果设置columns = ("Name", "Age"),则控件有3栏,其中,第一个栏位是最左栏的图标栏,可以进行展开(expand)和隐藏(collapse)操作,另两栏是Name和Age;
- displaycolumns:可以设置栏位显示顺序;
- 如果参数是"#all"表示显示所有栏,依照建立顺序显示;
- 如果设置columns = ("Name", "Age", "Date"),使用insert()插入元素时需要依次插入元素,同样状况如果使用columns(2, 0),则图标栏在最前面,紧跟着是Date栏,然后是Name栏;
- padding:可以用1~4个参数设置内容与控件框的间距,规则如下,
- selectmode:用户可以使用鼠标选择项目的方式;
- selectmode = BROWSE:一次选择一项(默认);
- selectmode = EXTENDED:一次可以选择多项;
- selectmode = NONE:无法用鼠标执行选择;
- show:默认为"tree",显示图标栏,设为"headings"以不显示;
- takefocus:默认为True,设为False以不被访问。
from tkinter.ttk import * root = Tk() root.title("Ex") tree = Treeview(root, columns = ("cities")) tree.heading("#0", text = "State") tree.heading("#1", text = "Cities") tree.insert("", index = END, text = "伊利诺", values = "芝加哥") tree.insert("", index = END, text = "加州", values = "洛杉矶") tree.insert("", index = END, text = "江苏", values = "南京") tree.pack() root.mainloop()
#0是指最左栏图标栏位,#1是指第一个栏位,当所建立的栏是最顶层时,父对象id可以使用""处理。
tree = Treeview(root, columns = ("cities"), show = "headings")
text:图标栏的内容;
values:一般栏位的内容,有多栏时使用元组输入,如果所设置的内容数太少时其他栏将是空白,如果所设置的内容太多时多出的内容将被抛弃。
使用列表方法建立栏位内容:
root = Tk() root.title("Ex") list1 = ["芝加哥", "800"] list2 = ["洛杉矶", "1000"] list3 = ["南京", "900"] tree = Treeview(root, columns = ("cities", "populations")) tree.heading("#0", text = "State") tree.heading("#1", text = "Cities") tree.heading("#2", text = "Populations") tree.insert("", index = END, text = "伊利诺", values = list1) tree.insert("", index = END, text = "加州", values = list2) tree.insert("", index = END, text = "江苏", values = list3) tree.pack() root.mainloop()
22.1 格式化
column(id, options)
id是指出特定栏位,可以用字符串表达,或是用"#index"索引方式,下列options参数:
- anchor:设置栏位内容参考位置;
- minwidth:最小栏宽,默认20像素;
- stretch:默认为1,当控件大小改变时栏宽将随之改变;
- width:默认200像素。
设置栏宽和对齐方式:
tree.column("#1", anchor = CENTER, width = 100)
如果此方法不含参数,将以字典方式传回特定栏所有参数的内容。
ret = column(id)
cityDict = tree.column("cities")
print(cityDict)
22.2 不同颜色
fag_configure("tagName",options,…)
- background,font,foreground,image。
root = Tk() root.title("Ex") stateCity = { "伊利诺":"芝加哥","加州":"洛杉矶", "德州":"休斯顿","华盛顿":"西雅图", "江苏":"南京","山东":"青岛", "广东":"广州","福建":"厦门" } tree = Treeview(root, columns = ("cities")) tree.heading("#0", text = "State") tree.heading("cities", text = "City") tree.column("cities", anchor = CENTER) tree.tag_configure("evenColor", background = "lightblue") rowCount = 1 for state in stateCity.keys(): if rowCount%2 == 1: tree.insert("", index = END, text = state, values = stateCity[state]) else: tree.insert("", index = END, text = state, values = stateCity[state], tags = ("evenColor")) rowCount +=1 tree.pack() root.mainloop()
22.3 层级式Treeview
root = Tk() root.title("Ex") asia = {"中国":"北京","日本":"东京","泰国":"曼谷","韩国":"首尔"} euro = {"英国":"伦敦","法国":"巴黎","德国":"柏林","挪威":"奥斯陆"} tree = Treeview(root, columns = ("capital")) tree.heading("#0", text = "国家") tree.heading("capital", text = "首都") idAsia = tree.insert("", index = END, text = "Asia") idEuro = tree.insert("", index = END, text = "Europe") for country in asia.keys(): tree.insert(idAsia, index = END, text = country, values = asia[country]) for country in euro.keys(): tree.insert(idEuro, index = END, text = country, values = euro[country]) tree.pack() root.mainloop()
22.4 事件触发
def treeSelect(event):
widgetObj = event.widget
itemselected = widgetObj.selection()[0]
col1 = widgetObj.item(itemselected, "text")
col2 = widgetObj.item(itemselected, "value")[0]
Str = "{0} : {1}".format(col1, col2)
var.set(Str)
tree.bind("<<TreeviewSelect>>", treeSelect)
var = StringVar()
label = Label(root, textvariable = var, relief = "groove")
label.pack(fill = BOTH, expand = True)
22.5 删除项目
def removeItem():
ids = tree.selection()
for iid in ids:
tree.delete(iid)
tree = Treeview(root, columns = ("cities"), selectmode = EXTENDED)
rmBtn = Button(root, text = "Remove", command = removeItem).pack(pady = 5)
def removeItem(): ids = tree.selection() for iid in ids: tree.delete(iid) root = Tk() root.title("Ex") stateCity = { "伊利诺":"芝加哥","加州":"洛杉矶", "德州":"休斯顿","华盛顿":"西雅图", "江苏":"南京","山东":"青岛", "广东":"广州","福建":"厦门" } tree = Treeview(root, columns = ("cities"), selectmode = EXTENDED) tree.heading("#0", text = "State") tree.heading("cities", text = "City") for state in stateCity.keys(): tree.insert("", index = END, text = state, values = stateCity[state]) tree.pack() rmBtn = Button(root, text = "Remove", command = removeItem).pack(pady = 5) root.mainloop()
22.6 插入项目
def insertItem():
state = stateEntry.get()
city = cityEntry.get()
if (len(state.strip()) == 0 or len(city.strip()) == 0):
return
tree.insert("",END,text=state,values=city)
stateEntry.delete(0,END)
cityEntry.delete(0,END)
# 建立上层插入项目
stateLeb = Label(root,text="State:")
stateLeb.grid(row=0,column=0,padx=5,pady=3,sticky=W)
stateEntry = Entry()
stateEntry.grid(row=0,column=1,sticky=W+E,padx=5,pady=3)
cityLab = Label(root,text="City:")
cityLab.grid(row=0,column=3,sticky=E)
cityEntry = Entry()
cityEntry.grid(row=0,column=3,sticky=W+E,padx=5,pady=3)
# 建立Insert按钮
InBtn = Button(root,text="插入",command=insertItem)
InBtn.grid(row=0,column=4,padx=5,pady=3)
22.7 双击项目
def doubleClick(event):
e = event.widget #取得事件控件
iid = e.identify("item",event.x,event.y) #取得双击项目id
state = e.item(iid,"text") #取得State
city = e.item(iid,"values")[0] #取得City
Str = "{0}:{1}".format(state,city) #格式化
messagebox.showinfo("Double Clicked",Str) # 输出
tree.bind("<Double-1>",doubleClick)
22.8 绑定滚动条
tree = Treeview(root, columns = ("cities"), selectmode = EXTENDED)
yscrollbar = Scrollbar(root)
yscrollbar.pack(side = RIGHT, fill = Y)
tree.pack()
yscrollbar.config(command = tree.yview)
tree.configure(yscrollcommand = yscrollbar.set)
22.9 排序
单击栏标题可以排序,再次单击反向排序。
def treeview_sortSolumn(col): global reverseFlag lst = [(tree.set(st, col), st) for st in tree.get_children("")] #print(lst) lst.sort(reverse = reverseFlag) #print(lst) for index, item in enumerate(lst): tree.move(item[1], "", index) reverseFlag = not reverseFlag root = Tk() root.title("Ex") states = { "伊利诺", "加州", "德州", "华盛顿", "江苏", "山东", "广东", "福建" } tree = Treeview(root, columns = ("states"), show = "headings", selectmode = EXTENDED) tree.pack() tree.heading("#1", text = "State", command = lambda c = "states": treeview_sortSolumn(c)) for state in states: tree.insert("", index = END, values = (state,)) root.mainloop()
23. Canvas
这个模块可以绘图,也可以制作动画,而动画也是设计游戏的基础。
23.1 绘图功能
1. 建立画布
canvas = Canvas(父对象, width = xx, height = yy)
画布建立完成后,左上角是坐标(0,0),向右x轴递增,向下y轴递增。
2. create_line()
绘制线条。
create_line(x1, y1, x2, y2, …, xn, yn, options)
options:
- arrow:默认是没有箭头,使用arrow = FIRST在起始线末端有箭头,arrow = LAST在最后一条线末端有箭头,使用arrow = BOTH在两端有箭头;
- arrowshape:使用元组(d1, d2, d3)代表三角形三边长度来控制箭头形状,默认是(8, 10, 3);
- capstyle:线条终点的样式,默认为BUTT,可以选择PROJECTING,ROUND;
- dash:建立虚线,使用元组存储数字数据, 第一个数字是实线,第二个数字是空白,如此循环完当前所有元组数字又重新开始;
- dashoffset:与dash一样产生虚线,但是一开始是空白的宽度;
- fill:设置线条颜色;
- joinstyle:线条相交的设置,默认为ROUND,也可以选择BEVEL、MITER;
- stipple:绘制位图线条;
- width:线条宽度。
import math root = Tk() root.title("Ex") canvas = Canvas(root, width = 640, height = 480) canvas.pack() x_center, y_center, r = 320, 240, 100 x, y = [], [] for i in range(12): x.append(x_center + r * math.cos(i*math.pi/6)) y.append(y_center + r * math.sin(i*math.pi/6)) for i in range(12): for j in range(12): canvas.create_line(x[i], y[i], x[j], y[j]) root.mainloop()
3. create_rectangle()
绘制矩形。
create_rectangle(x1, y1, x2, y2, options)
- fill:为矩阵填充颜色;
- outline:设置矩阵线条颜色。
4. create_arc()
绘制圆弧。
create_arc(x1, y1, x2, y2, extent = angle, options)
- extent:1~359,绘制圆弧的角度;
- start:圆弧起点位置;
- style:ARC、CHORD、PIESLICE。
绘制方向是逆时针。
5. create_oval()
绘制原或椭圆。
create_oval(x1, y1, x2, y2, options)
6. create_polygon()
绘制多边形。
create_polygon(x1, y1, x2, y2, …, xn, yn, options)
7. create_text()
输出文字。
create_text(x, y, text = "字符串", options)
(x, y)是字符串输出的中心坐标。
- anchor:默认为CENTER;
- fill,font,justify,stipple,text,width。
8. 画布色彩
Canvas(…, bg = "yellow")
9. 插入图像
create_image(x, y, options)
(x, y)是图像左上角的位置。
23.2 鼠标拖曳绘制线条
圆的左上角坐标与右下角坐标相同,可以认为是点(极小化的圆)。
def paint(event): x, y = (event.x, event.y) canvas.create_oval(x, y, x, y, fill = "blue") def clear(): canvas.delete("all") root = Tk() root.title("Ex") canvas = Canvas(root, width = 640, height = 300) canvas.pack() btn = Button(root, text = "清除", command = clear) btn.pack(pady = 5) canvas.bind("<B1-Motion>", paint) root.mainloop()
23.3 动画设计
1. 基本动画
动画设计所使用的的方法是move()。
canvas.move(id, xMove, yMove) #id是对象标号
canvas.update() #重绘画布
xMove、yMove分别是对象沿x和y轴移动的距离,单位是像素。
import time root = Tk() root.title("Ex") canvas = Canvas(root, width = 500, height = 150) canvas.pack() canvas.create_oval(10, 50, 60, 100, fill = "yellow", outline = "blue") for x in range(0,80): canvas.move(1, 5, 0) root.update() time.sleep(0.05) root.mainloop()
2. 反弹球游戏
from tkinter import * from random import * import time class Ball: def __init__(self, canvas, color, winW, winH, racket): self.canvas = canvas self.racket = racket self.id = canvas.create_oval(winW/2-10, winH/2-10, winW/2+10, winH/2+10, fill = color) self.x = step self.y = -step self.notTouchBottom = True def hitRacket(self, ballPos): racketPos = self.canvas.coords(self.racket.id) if ballPos[2] >= racketPos[0] and ballPos[0] <= racketPos[2]: if ballPos[3] >= racketPos[1] and ballPos[3] <= racketPos[3]: return True return False def ballMove(self): self.canvas.move(self.id, self.x, self.y) ballPos = self.canvas.coords(self.id) if ballPos[0] <= 0: self.x = step if ballPos[1] <= 0: self.y = step if ballPos[2] >= winW: self.x = -step if ballPos[3] >= winH: self.y = -step if self.hitRacket(ballPos) == True: self.y = -step if ballPos[3] >= winH: self.notTouchBottom = False class Racket: def __init__(self, canvas, color): self.canvas = canvas self.id = canvas.create_rectangle(270, 400, 370, 415, fill = color) self.x = 0 self.canvas.bind_all("<KeyPress-Right>", self.moveRight) self.canvas.bind_all("<KeyPress-Left>", self.moveLeft) def racketMove(self): self.canvas.move(self.id, self.x, 0) racketPos = self.canvas.coords(self.id) if racketPos[0] <= 0: self.x = 0 elif racketPos[2] >= winW: self.x = 0 def moveLeft(self, event): self.x = -3 def moveRight(self, event): self.x = 3 #定义画布尺寸 winW = 640 winH = 480 #定义位移步长 step = 3 #定义移动速度 speed = 100 root = Tk() root.title("Bouncing Ball") root.wm_attributes('-topmost', 1) #确保游戏窗口在屏幕最上层 canvas = Canvas(root, width = winW, height = winH) #建立画布 canvas.pack() root.update() racket = Racket(canvas, "purple") ball = Ball(canvas, "yellow", winW, winH, racket) while ball.notTouchBottom: #如果球未接触画布底端 try: ball.ballMove() except: print("单击关闭按钮终止程序执行") break racket.racketMove() root.update() time.sleep(1/speed) root.mainloop()