tkinter.Canvas
示例#
画布(canvas)小部件管理着 2D 图形对象(lines, circles, images, 其他更多小部件)组成的集合。Canvas 小部件是经典的 Tk 小部件,不是 Ttk 小部件。
创建方法是:
from tkinter import Canvas
canvas = Canvas(parent)
注意:在 Canvas 中的坐标系是以左上角作为原点 \((0,0)\),水平向右为 \(x\) 轴正方向,垂直向下为 \(y\) 轴正方向。
创建线段#
创建线段是以 \((x_0, y_0, x_1, y_1)\) 的形式传入 creata_line
函数的。其中 \((x_0, y_0)\),\((x_1, y_1)\) 分别为起点和终点。比如:
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
对应的点的配置。
绑定 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()
效果图:
修改 item#
我们可以使用 delete
方法删除 item,使用 coords
方法改变 item 的尺寸和位置(允许变换坐标系)。可以使用 move
方法移动 item。还有 "raise"
和 "lower"
方法可以改变不同画布的排列布局。比如:
改变 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()
删除 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()
缩放 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()
滚动鼠标#
在许多应用程序中,您希望画布大于屏幕上显示的内容。 您可以通过 “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()
显示效果:
更改线段的样式#
我们也可以设定参数 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()
效果图:
画布的其他设定#
Canvas 除了支持 “line”, “rectangle”,还支持 “oval”, “arc”, “polygon”, “bitmap” (位图,可用于分割物体), “image”,“text”,甚至还支持 “window” 的嵌入。
绘制位图#
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()
绘制多边形#
root = Tk()
self = Canvas(root)
# 点的坐标
points = (10, 10), (10, 200), (90, 200), (200, 160)
self.create_polygon(points, fill='red')
self.grid()
root.mainloop()
绘制文本#
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()
创建组件#
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()