畫布(canvas)小部件管理著 2D 圖形對(duì)象(lines, circles, images, 其他更多小部件)組成的集合。Canvas 小部件是經(jīng)典的 Tk 小部件,不是 Ttk 小部件。
創(chuàng)建方法是:
from tkinter import Canvas
canvas = Canvas(parent)
注意:在 Canvas 中的坐標(biāo)系是以左上角作為原點(diǎn) ,水平向右為
軸正方向,垂直向下為
軸正方向。
1 創(chuàng)建線段
創(chuàng)建線段是以 的形式傳入
creata_line 函數(shù)的。其中 ,
分別為起點(diǎn)和終點(diǎn)。比如:
item_id = canvas.create_line(10, 10, 200, 50)
函數(shù) creata_line 的返回值 item_id 是一個(gè)整數(shù),被用來(lái)作為該對(duì)象的引用的唯一標(biāo)識(shí)。
下面看一個(gè)例子:
class Segment(Canvas):
def __init__(self, master=None, **kw):
super().__init__(master=master, **kw)
self.lastx, self.lasty = 0, 0
self.bind("<Button-1>", self.xy) # 綁定鼠標(biāo)左鍵
self.bind("<B1-Motion>", self.add_line) # 拖動(dòng)鼠標(biāo)左鍵
def xy(self, event):
'''更新坐標(biāo)'''
self.lastx, self.lasty = event.x, event.y
def add_line(self, event):
'''畫一條線段'''
self.create_line(self.lastx, self.lasty, event.x, event.y, fill='red', width=3)
self.xy(event)
def layout(self):
self.grid(column=0, row=0, sticky='nwes')
root = Tk()
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
segment = Segment(root)
segment.layout()
root.mainloop()
該例子實(shí)現(xiàn)了在畫布上拖動(dòng)鼠標(biāo)左鍵來(lái)畫線段的目標(biāo)。其中的參數(shù) width 表示線段的寬度,fill 表示使用的畫筆的顏色。
可以做如下修改:
from tkinter import IntVar
class App(Tk):
def __init__(self):
super().__init__()
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.segment = Segment(self)
self.id_var = IntVar()
def modify(self):
item_id = self.id_var.get()
self.segment.itemconfigure(item_id, fill='blue', width=10)
def layout(self):
entry = ttk.Entry(textvariable=self.id_var)
button = ttk.Button(text='modify', command=self.modify)
self.segment.layout()
entry.grid(column=0, row=1)
button.grid(column=1, row=1)
app = App()
app.layout()
app.mainloop()
效果圖見圖1:

只要寫入文本框數(shù)字,然后點(diǎn)擊按鈕 modify,便可 使用 Canvas.itemconfigure 函數(shù)修改 item_id 對(duì)應(yīng)的點(diǎn)的配置。
2 綁定 item_id
除了可以使用 bind 函數(shù)綁定事件之外,還可以使用 tag_bind 函數(shù)綁定 item_id 來(lái)觸發(fā)事件。
下面的代碼可以使用鼠標(biāo)點(diǎn)擊顏色選擇塊來(lái)切換畫筆的顏色:
class Segment(Canvas):
def __init__(self, master=None, **kw):
super().__init__(master=master, **kw)
self.lastx, self.lasty = 0, 0
self.color = "black"
self.bind("<Button-1>", self.xy)
self.bind("<B1-Motion>", self.add_line)
def set_color(self, new_color):
self.color = new_color
def xy(self, event):
'''更新坐標(biāo)'''
self.lastx, self.lasty = event.x, event.y
def add_line(self, event):
self.create_line((self.lastx, self.lasty, event.x, event.y), fill=self.color)
self.xy(event)
def change(self):
# 創(chuàng)建 3 個(gè)顏色選擇塊
red_id = self.create_rectangle((10, 10, 30, 30), fill="red")
blue_id = self.create_rectangle((10, 35, 30, 55), fill="blue")
black_id = self.create_rectangle((10, 60, 30, 80), fill="black")
# 綁定事件
self.tag_bind(red_id, "<Button-1>", lambda x: self.set_color("red"))
self.tag_bind(blue_id, "<Button-1>", lambda x: self.set_color("blue"))
self.tag_bind(black_id, "<Button-1>", lambda x: self.set_color("black"))
root = Tk()
seg = Segment(root)
seg.grid()
seg.change()
root.mainloop()
效果圖:

3 標(biāo)記:Tags
使用配置選項(xiàng) "tags" 可以為任何一個(gè) item_id 代表的對(duì)象做標(biāo)記,這里的標(biāo)記可以是一個(gè)或者多個(gè)。
為什么要做標(biāo)記呢?這是因?yàn)椋隽藰?biāo)記的 item_id 將會(huì)更方便管理。比如,您可以指定所有被標(biāo)記為 "line" 的 item_id 統(tǒng)一修改其畫筆顏色。
除了使用配置選項(xiàng) "tags" 創(chuàng)建標(biāo)記之外,您可以在 item_id 創(chuàng)建之后使用 add_tag 函數(shù)創(chuàng)建或者添加新的標(biāo)記。移除標(biāo)記可以使用方法 dtag。您也可以使用 gettags(item_id) 方式獲取 item_id 的全部標(biāo)記列表。可以使用 find_withtag 函數(shù)獲取指定的標(biāo)記的全部 item_id 列表。
直接看一個(gè)例子:
class Segment(Canvas):
def __init__(self, master=None, **kw):
super().__init__(master=master, **kw)
self.lastx, self.lasty = 0, 0
self.color = "黑色"
self.bind("<Button-1>", self.xy)
self.bind("<B1-Motion>", self.add_line)
# 設(shè)定調(diào)色板的線寬
self.itemconfigure('調(diào)色板', width=5)
# 釋放鼠標(biāo)觸發(fā)事件
self.bind('<B1-ButtonRelease>', self.done_stroke)
@property
def color_map(self):
return {
'紅色': 'red',
'藍(lán)色': 'blue',
'黑色': 'black'
}
def set_color(self, new_color):
self.color = new_color
self.dtag('all', '被選中的調(diào)色板')
self.itemconfigure('調(diào)色板', outline='white')
self.addtag('被選中的調(diào)色板', 'withtag', f"{self.color}調(diào)色板")
self.itemconfigure('被選中的調(diào)色板', outline='#999999')
def xy(self, event):
'''更新坐標(biāo)'''
self.lastx, self.lasty = event.x, event.y
def add_line(self, event):
loc = (self.lastx, self.lasty, event.x, event.y)
color = self.color_map[self.color]
self.create_line(loc, fill=color, width=5, tags='當(dāng)前的線段')
self.xy(event)
def palette(self, loc, color):
kw = {
'fill': color,
'tags': ('調(diào)色板', f'{color}調(diào)色板')
}
return self.create_rectangle(loc, **kw)
def change(self):
# 創(chuàng)建 3 個(gè)顏色選擇塊
red_id = self.palette((10, 10, 30, 30), "red")
blue_id = self.palette((10, 35, 30, 55), "blue")
black_id = self.palette((10, 60, 30, 80), "black")
# 添加標(biāo)記
self.addtag('被選中的調(diào)色板', 'withtag', black_id)
# 綁定事件
self.tag_bind(red_id, "<Button-1>", lambda x: self.set_color("紅色"))
self.tag_bind(blue_id, "<Button-1>", lambda x: self.set_color("藍(lán)色"))
self.tag_bind(black_id, "<Button-1>", lambda x: self.set_color("黑色"))
def done_stroke(self, event):
self.itemconfigure('當(dāng)前的線段', width=1)
root = Tk()
seg = Segment(root)
seg.grid()
seg.change()
root.mainloop()
顯示的效果圖:

該例子展示了一種使用 tags 修改 item 的手段,達(dá)到的效果是:在鼠標(biāo)釋放之前線條比較粗,釋放之后線條會(huì)變細(xì)。
4 修改 item
我們可以使用 delete 方法刪除 item,使用 coords 方法改變 item 的尺寸和位置(允許變換坐標(biāo)系)??梢允褂?move 方法移動(dòng) item。還有 "raise" 和 "lower" 方法可以改變不同畫布的排列布局。比如:
4.1 改變 item 屬性
from tkinter import *
root=Tk()
cv=Canvas(root,bg='white')
rt1=cv.create_rectangle(10,10,110,110,tags=('r1','r2','r3'))
rt2=cv.create_rectangle(20,20,80,80,tags=('s1','s2','s3'))
rt3=cv.create_rectangle(30,30,70,70,tags=('y1','y2','y3'))
cv.tag_lower(rt3)
cv.tag_raise(rt1)
cv.itemconfig(cv.find_above(rt2),outline='red')
cv.itemconfig(cv.find_below(rt2),outline='green')
cv.pack()
root.mainloop()
4.2 刪除 item
root=Tk()
cv=Canvas(root,bg='white')
rt1=cv.create_rectangle(10,10,110,110,tags=('r1','r2','r3'))
rt2=cv.create_rectangle(20,20,80,80,tags=('s1','s2','s3'))
rt3=cv.create_rectangle(30,30,70,70,tags=('s1','y2','y3'))
cv.delete(rt1)
cv.delete('s1')
cv.pack()
root.mainloop()
4.3 縮放 item
root=Tk()
cv=Canvas(root,bg='white')
rt1=cv.create_rectangle(10,10,110,110,tags=('r1','r2','r3'))
cv.scale(rt1,0,0,1,2)
cv.pack()
root.mainloop()
5 滾動(dòng)鼠標(biāo)
在許多應(yīng)用程序中,您希望畫布大于屏幕上顯示的內(nèi)容。 您可以通過(guò) "xview" 和 "yview" 方法以通常的方式將水平和垂直滾動(dòng)條附加到畫布上。
至于畫布的大小,您既可以指定希望在屏幕上顯示的大小,也可以指定需要滾動(dòng)才能看到的畫布的完整大小。 畫布小部件的 "width" 和 "height" 配置選項(xiàng)將從幾何管理器請(qǐng)求給定的空間量。"scrollregion" 配置選項(xiàng)(例如 "0 0 1000 1000")告訴 Tk 畫布表面有多大。
針對(duì)鼠標(biāo)滾動(dòng),"canvasx" 和 "canvasy" 方法會(huì)將屏幕上的位置(正在報(bào)告的綁定)轉(zhuǎn)換為畫布上的實(shí)際點(diǎn)。如果將它們直接添加到事件綁定中(而不是從事件綁定中調(diào)用),請(qǐng)注意引用和替換,以確保在事件觸發(fā)時(shí)完成轉(zhuǎn)換。
下面的例子很好的說(shuō)明了這種機(jī)制:
class SegmentScroll(Segment):
def __init__(self, master=None, **kw):
super().__init__(master=master, **kw)
self.master = master
def scroll(self):
self._h = ttk.Scrollbar(orient='horizontal')
self._v = ttk.Scrollbar(orient='vertical')
# 告訴 Tk 畫布表面有多大
self['scrollregion'] = (0, 0, 1000, 1000)
self.itemconfig('scrollregion', )
self.configure(yscrollcommand=self._v.set, xscrollcommand=self._h.set)
self._h['command'] = self.xview
self._v['command'] = self.yview
def custom(self):
ttk.Sizegrip(root).grid(column=1, row=1, sticky=(S,E))
self.grid(column=0, row=0, sticky=(N,W,E,S))
self._h.grid(column=0, row=1, sticky=(W,E))
self._v.grid(column=1, row=0, sticky=(N,S))
self.master.grid_columnconfigure(0, weight=1)
self.master.grid_rowconfigure(0, weight=1)
def xy(self, event):
self.lastx, self.lasty = self.canvasx(event.x), self.canvasy(event.y)
def add_line(self, event):
x, y = self.canvasx(event.x), self.canvasy(event.y)
color = self.color_map[self.color]
self.create_line((self.lastx, self.lasty, x, y), fill=color, width=5, tags='當(dāng)前的線段')
self.xy(event)
root = Tk()
seg = SegmentScroll(root)
seg.change()
seg.scroll()
seg.custom()
root.mainloop()
顯示效果:

6 更改線段的樣式
我們也可以設(shè)定參數(shù) arrow 和 arrowshape 來(lái)改變線段的樣式:
root = Tk()
cv = Meta(root, bg='white')
d = [(0, 'none'), (1, 'first'), (2, 'last'), (3, 'both')]
for i in d:
cv.create_line((10, 10+i[0]*20, 110, 110+i[0]*20),
arrow=i[1], arrowshape='40 30 10')
cv.grid()
root.mainloop()
顯示效果圖:

還有:
root = Tk()
cv = Canvas(root, bg='white')
d = [(0, 'none', 'bevel'), (1, 'first', 'miter'),
(2, 'last', 'round'), (3, 'both', 'round')]
for i in d:
cv.create_line((10, 10+i[0]*20, 110, 110+i[0]*20),
arrow=i[1], arrowshape='8 10 3', joinstyle=i[2])
cv.grid()
root.mainloop()
效果圖:

7 畫布的其他設(shè)定
Canvas 除了支持 "line", "rectangle",還支持 "oval", "arc", "polygon", "bitmap" (位圖,可用于分割物體), "image","text",甚至還支持 "window" 的嵌入。
7.1 繪制位圖
root = Tk()
self = Canvas(root, background='white')
bitmap = ('error', 'info', 'question', 'hourglass',
"warning", "gray12",
"gray25", "gray50", "gray75", "questhead")
for k, name in enumerate(bitmap):
location = [20*(k+1)]*2
self.create_bitmap(location, bitmap=name)
self.grid()
root.mainloop()

7.2 繪制多邊形
root = Tk()
self = Canvas(root)
# 點(diǎn)的坐標(biāo)
points = (10, 10), (10, 200), (90, 200), (200, 160)
self.create_polygon(points, fill='red')
self.grid()
root.mainloop()

7.3 繪制文本
root = Tk()
self = Canvas(root)
location = 50, 50
text = self.create_text(location, text='一個(gè)文本:永不言敗!', anchor='sw', fill='blue', font='italic 15')
# 選中文本
self.select_from(text, 5)
self.select_to(text, 8)
self.grid()
root.mainloop()

7.4 創(chuàng)建組件
from tkinter import Canvas, ttk, Tk
root = Tk()
self = Canvas(root)
def print_text():
print("你好")
bt = ttk.Button(self, text='點(diǎn)我', command=print_text)
self.create_window((10, 10), window=bt, anchor='w')
self.create_line(30, 30, 50, 90)
self.grid()
root.mainloop()
