画布(canvas)小部件管理着 2D 图形对象(lines, circles, images, 其他更多小部件)组成的集合。Canvas 小部件是经典的 Tk 小部件,不是 Ttk 小部件。
创建方法是:
from tkinter import Canvas
canvas = Canvas(parent)
注意:在 Canvas 中的坐标系是以左上角作为原点 ,水平向右为 轴正方向,垂直向下为 轴正方向。
1 创建线段
创建线段是以 的形式传入 creata_line
函数的。其中 , 分别为起点和终点。比如:
item_id = canvas.create_line(10, 10, 200, 50)
函数 creata_line
的返回值 item_id
是一个整数,被用来作为该对象的引用的唯一标识。
下面看一个例子:
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) # 绑定鼠标左键
self.bind("<B1-Motion>", self.add_line) # 拖动鼠标左键
def xy(self, event):
'''更新坐标'''
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()
该例子实现了在画布上拖动鼠标左键来画线段的目标。其中的参数 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:
只要写入文本框数字,然后点击按钮 modify
,便可 使用 Canvas.itemconfigure
函数修改 item_id
对应的点的配置。
2 绑定 item_id
除了可以使用 bind
函数绑定事件之外,还可以使用 tag_bind
函数绑定 item_id 来触发事件。
下面的代码可以使用鼠标点击颜色选择块来切换画笔的颜色:
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):
'''更新坐标'''
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):
# 创建 3 个颜色选择块
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 标记:Tags
使用配置选项 "tags"
可以为任何一个 item_id
代表的对象做标记,这里的标记可以是一个或者多个。
为什么要做标记呢?这是因为,做了标记的 item_id 将会更方便管理。比如,您可以指定所有被标记为 "line"
的 item_id 统一修改其画笔颜色。
除了使用配置选项 "tags"
创建标记之外,您可以在 item_id 创建之后使用 add_tag
函数创建或者添加新的标记。移除标记可以使用方法 dtag
。您也可以使用 gettags(item_id)
方式获取 item_id 的全部标记列表。可以使用 find_withtag
函数获取指定的标记的全部 item_id 列表。
直接看一个例子:
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)
# 设定调色板的线宽
self.itemconfigure('调色板', width=5)
# 释放鼠标触发事件
self.bind('<B1-ButtonRelease>', self.done_stroke)
@property
def color_map(self):
return {
'红色': 'red',
'蓝色': 'blue',
'黑色': 'black'
}
def set_color(self, new_color):
self.color = new_color
self.dtag('all', '被选中的调色板')
self.itemconfigure('调色板', outline='white')
self.addtag('被选中的调色板', 'withtag', f"{self.color}调色板")
self.itemconfigure('被选中的调色板', outline='#999999')
def xy(self, event):
'''更新坐标'''
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='当前的线段')
self.xy(event)
def palette(self, loc, color):
kw = {
'fill': color,
'tags': ('调色板', f'{color}调色板')
}
return self.create_rectangle(loc, **kw)
def change(self):
# 创建 3 个颜色选择块
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")
# 添加标记
self.addtag('被选中的调色板', '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("蓝色"))
self.tag_bind(black_id, "<Button-1>", lambda x: self.set_color("黑色"))
def done_stroke(self, event):
self.itemconfigure('当前的线段', width=1)
root = Tk()
seg = Segment(root)
seg.grid()
seg.change()
root.mainloop()
显示的效果图:
该例子展示了一种使用 tags 修改 item 的手段,达到的效果是:在鼠标释放之前线条比较粗,释放之后线条会变细。
4 修改 item
我们可以使用 delete
方法删除 item,使用 coords
方法改变 item 的尺寸和位置(允许变换坐标系)。可以使用 move
方法移动 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 滚动鼠标
在许多应用程序中,您希望画布大于屏幕上显示的内容。 您可以通过 "xview" 和 "yview" 方法以通常的方式将水平和垂直滚动条附加到画布上。
至于画布的大小,您既可以指定希望在屏幕上显示的大小,也可以指定需要滚动才能看到的画布的完整大小。 画布小部件的 "width" 和 "height" 配置选项将从几何管理器请求给定的空间量。"scrollregion" 配置选项(例如 "0 0 1000 1000")告诉 Tk 画布表面有多大。
针对鼠标滚动,"canvasx" 和 "canvasy" 方法会将屏幕上的位置(正在报告的绑定)转换为画布上的实际点。如果将它们直接添加到事件绑定中(而不是从事件绑定中调用),请注意引用和替换,以确保在事件触发时完成转换。
下面的例子很好的说明了这种机制:
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='当前的线段')
self.xy(event)
root = Tk()
seg = SegmentScroll(root)
seg.change()
seg.scroll()
seg.custom()
root.mainloop()
显示效果:
6 更改线段的样式
我们也可以设定参数 arrow
和 arrowshape
来改变线段的样式:
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 画布的其他设定
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)
# 点的坐标
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='一个文本:永不言败!', anchor='sw', fill='blue', font='italic 15')
# 选中文本
self.select_from(text, 5)
self.select_to(text, 8)
self.grid()
root.mainloop()
7.4 创建组件
from tkinter import Canvas, ttk, Tk
root = Tk()
self = Canvas(root)
def print_text():
print("你好")
bt = ttk.Button(self, text='点我', command=print_text)
self.create_window((10, 10), window=bt, anchor='w')
self.create_line(30, 30, 50, 90)
self.grid()
root.mainloop()