一、说明
对画布的掌握分三个部分,将图形paint到画布、动画move、鼠标画;本篇将侧重于鼠标画的功能,提起鼠标画实现,将涉及一系列组合操作才能完成,这里将一一加以介绍。
Canvas 小部件具有大量功能,我们不会在这里介绍所有功能。相反,我们将从一个简单的示例(徒手绘制工具)开始,并逐步添加新的部分,每个部分都显示画布小部件的另一个功能。
二、画布基本实现
canvas = Canvas(parent, width=500, height=400, background='gray75')
您通常会提供宽度和高度,以像素或任何其他标准距离单位为单位。与往常一样,您可以要求几何管理器将其展开以填充窗口中的可用空间。您可以为画布提供默认背景颜色,并按照您在上一章中了解的方式指定颜色。画布小部件还支持其他外观选项,例如我们之前使用过的浮雕和边框宽度。
先看下列代码:
import tkinter as Tkinter
top = Tkinter.Tk()
C = Tkinter.Canvas(top, bg="blue", height=250, width=300)
coord = 10, 50, 240, 210
arc = C.create_arc(coord, start=0, extent=150, fill="red")
C.pack()
top.mainloop()
画布小部件管理图形对象的 2D 集合 - 线条、圆形、文本、图像、其他小部件等等。 Tk 的画布是一个非常强大且灵活的小部件,确实是 Tk 的亮点之一。它适用于广泛的用途,包括绘图或图表、CAD 工具、显示或监控模拟或实际设备,以及用更简单的小部件构建更复杂的小部件。
注意:Canvas 小部件是经典 Tk 小部件的一部分,而不是主题 Tk 小部件。
三、视野滚动
在许多应用程序中,您会希望画布比屏幕上显示的更大。您可以通过 xview 和 yview 方法以通常的方式将水平和垂直滚动条附加到画布。
您可以指定您希望它在屏幕上显示的大小及其完整尺寸(这需要滚动才能看到)。宽度和高度配置选项控制画布小部件从几何管理器请求多少空间。 scrollregion 配置选项通过指定画布表面的左、上、右和下坐标来告诉 Tk 画布表面有多大,例如 0 0 1000 1000。
根据您已知的知识,您应该能够修改画板程序以添加滚动。试一试。
完成后,将画布向下滚动一点,然后尝试绘图。您会看到您正在绘制的线出现在鼠标指向的上方!惊讶吗?
发生的事情是全局绑定命令不知道画布已滚动(它不知道任何特定小部件的详细信息)。因此,如果您将画布向下滚动 50 像素,然后单击左上角,bind 将报告您单击了 (0,0)。但我们知道,由于滚动,该位置实际上应该是 (0,50)。
canvasx 和 canvasy 方法将屏幕上的位置(绑定报告)转换为画布上的实际点(考虑到滚动)。
from tkinter import *
from tkinter import ttk
top = Tk()
h = ttk.Scrollbar(top, orient=HORIZONTAL)
v = ttk.Scrollbar(top, orient=VERTICAL)
canvas = Canvas(top, bg="blue", height=250, width=300,scrollregion=(0, 0, 1000, 1000), yscrollcommand=v.set, xscrollcommand=h.set)
h['command'] = canvas.xview
v['command'] = canvas.yview
canvas.grid(column=0, row=0, sticky=(N, W, E, S))
h.grid(column=0, row=1, sticky=(W, E))
v.grid(column=1, row=0, sticky=(N, S))
top.grid_columnconfigure(0, weight=1)
top.grid_rowconfigure(0, weight=1)
coord = 10, 50, 240, 210
arc = canvas.create_arc(coord, start=0, extent=150, fill="red")
top.mainloop()
语句 | 意义 |
---|---|
h = ttk.Scrollbar(top, orient=HORIZONTAL) | 生成水平滚条 |
v = ttk.Scrollbar(top, orient=VERTICAL) | 生成垂直滚条 |
canvas = Canvas(top, bg="blue", height=250, width=300,scrollregion=(0, 0, 1000, 1000), yscrollcommand=v.set, xscrollcommand=h.set) | 设定画布的滚动 |
h['command'] = canvas.xview | 滚动条对应画布位置 |
v['command'] = canvas.yview | 滚动条对应画布位置 |
canvas.grid(column=0, row=0, sticky=(N, W, E, S)) | 摆放画布,边界顶满 |
h.grid(column=0, row=1, sticky=(W, E)) v.grid(column=1, row=0, sticky=(N, S)) | 在画布周边摆放滚条 |
top.grid_columnconfigure(0, weight=1) top.grid_rowconfigure(0, weight=1) | 配置滚条 |
四、手动绘制
4.1 创建项目
当您创建一个新的画布小部件时,它本质上是一个没有任何内容的大矩形,换句话说,它实际上是一个空白画布。要使用它做任何有用的事情,您需要向其中添加项目。您可以添加多种不同类型的项目。在这里,我们将向画布添加一个简单的行项目。
要创建一条线,您需要指定其起始坐标和结束坐标。坐标表示为距左上角水平和垂直的像素数,即 (x,y)。左上角的像素(称为原点)的坐标为 (0,0)。 “x”值随着向右移动而增加,“y”值随着向下移动而增加。一条线由两个点描述,我们将其称为 (x0,y0) 和 (x1,y1)。此代码创建一条从 (10,5) 到 (200,50) 的线:
canvas.create_line(10, 5, 200, 50)
4.2 画图
一个简单的画板
让我们开始我们简单的画板示例。现在,我们将使用鼠标在画布上实现徒手绘制。我们创建一个画布小部件并将事件绑定附加到它以捕获鼠标单击和拖动。当第一次按下鼠标时,我们会记住该位置作为下一行的“开始”。当按住鼠标按钮移动鼠标时,我们从该“开始”位置到当前鼠标位置创建一个行项目。该当前位置成为下一个行项目的“开始”位置。每次鼠标拖动都会创建一个新的行项目。
from tkinter import *
from tkinter import ttk
def savePosn(event):
global lastx, lasty
lastx, lasty = event.x, event.y
def addLine(event):
canvas.create_line((lastx, lasty, event.x, event.y))
savePosn(event)
root = Tk()
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
canvas = Canvas(root)
canvas.grid(column=0, row=0, sticky=(N, W, E, S))
canvas.bind("<Button-1>", savePosn)
canvas.bind("<B1-Motion>", addLine)
root.mainloop()
五、绘画线属性属性
canvas.create_line(10, 10, 200, 50, fill='red', width=3)
创建项目时,您还可以指定一个或多个项目属性,从而影响其显示方式。例如,我们可以指定线条应为红色且宽度为三个像素。
确切的属性集将根据项目的类型而有所不同。一些常用的有:
fill
: 绘制对象的颜色
width
: 项目的线宽(或其轮廓)
outline
: 对于矩形等填充形状,绘制项目轮廓的颜色
dash
: 绘制虚线而不是实线,例如,2 4 6 4 交替短(2 个像素)和长(6 个像素)虚线,中间有 4 个像素
stipple
:使用图案代替纯色填充颜色,通常为gray75、gray50、gray25或gray12; macOS 目前不支持点画
state
: 分配正常(默认)、禁用(项目事件绑定被忽略)或隐藏(从显示中删除)状态
禁用填充,禁用宽度,...:
disabledfill, disabledwidth, ...
:如果项目的状态设置为禁用,则该项目将使用常用属性的这些变体进行显示
activefill, activewidth, ...
:当鼠标指针位于该项目上时,它将使用常用属性的这些变体进行显示
id = canvas.create_line(0, 0, 10, 10, fill='red')
...
canvas.itemconfigure(id, fill='blue', width=2)
六、物品类型
画布小部件支持多种项目类型。
6.1 线
我们的画板创建了简单的行项目,每个项目都是一个带有起点和终点的线段。行项目也可以由多个段组成。
canvas.create_line(10, 10, 200, 50, 90, 150, 50, 80)
线条有几个有趣的附加属性,允许绘制曲线、箭头等。
箭:在起点(第一个)、终点(最后一个)或两端(两者)放置一个箭头;默认为无
箭头形状:允许更改任何箭头的外观
帽型:对于没有箭头的宽线,这控制如何绘制线的末端;对接(默认)、突出或圆形之一
连接方式:对于具有多个线段的宽线,这控制每个顶点的绘制;圆形(默认)、斜角或斜接之一
光滑的:如果指定为 true(或贝塞尔曲线),则在多个线段之间绘制平滑曲线(通过二次样条)而不是使用直线; raw 指定不同类型的曲线(三次样条)
样条步骤:控制曲线的平滑度,即那些设置了 smooth 选项的曲线
6.2 矩形
矩形由对角的坐标指定,例如左上角和右下角。它们可以用一种颜色填充(通过填充),而轮廓则用不同的颜色填充。
canvas.create_rectangle(10, 10, 200, 50, fill='red', outline='blue')
6.3 椭圆形
椭圆形项目的工作原理与矩形完全相同。
canvas.create_oval(10, 10, 200, 150, fill='red', outline='blue')
6.4 多边形
多边形项目允许您创建由一系列点定义的任意形状。坐标的给出方式与多点线相同。 Tk 确保多边形是“闭合的”,如果需要的话将最后一个点附加到第一个点。与椭圆形和矩形一样,它们可以具有单独的填充颜色和轮廓颜色。它们还支持订单项的 joinstyle、smooth 和 splinesteps 属性。
canvas.create_polygon(10, 10, 200, 50, 90, 150, 50, 80, 120, 55, fill='red', outline='blue')
6.5 弧
弧形项目绘制椭圆形的一部分;想想一张饼图。它的显示由三个属性控制:
开始:弧线应沿着椭圆的距离开始,以度为单位(0 是 3 点钟位置)
范围:弧线应该“宽”多少度,从开始处逆时针方向为正值,顺时针方向为负值
样式:pieslice(默认)、arc(仅绘制外周长)或弦(绘制连接圆弧起点和终点的直线与外周长之间的区域)之一。
canvas.create_arc(10, 10, 200, 150, fill='yellow', outline='black', start=45, extent=135, width=5)
6.6 图像
图像项可以显示任意图像。默认情况下,该项目以您指定的坐标为中心,但这可以使用锚点选项进行更改,例如,nw 表示坐标是放置图像左上角的位置。
myimg = PhotoImage(file='pretty.png')
还有一种位图项目类型,用于仅具有两种颜色的图像,可以通过前景和背景进行更改。如今它们已不常用。
6.7 文本
文本项可以显示文本块。定位文本的方式与定位图像项目的方式相同。使用文本属性指定要显示的文本。项目中的所有文本都将具有相同的颜色(由填充属性指定)和相同的字体(由字体属性指定)。
如果在文本中嵌入 \n,则文本项可以显示多行文本。或者,您可以通过提供表示行的最大宽度的宽度属性来将项目自动换行为多行。多行文本的对齐方式可以使用 justify 属性来设置,可以是左对齐(默认)、右对齐或居中对齐。
canvas.create_text(100, 100, text='A wonderful story', anchor='nw', font='TkMenuFont', fill='red')
6.8 小部件
使用画布小部件可以做的最酷的事情之一就是在其中嵌入其他小部件。这可以是一个低级按钮、一个条目(想想文本项的就地编辑)、一个列表框、一个本身包含一组复杂小部件的框架......任何东西!还记得我们很久以前说过画布小部件可以充当几何管理器吗?这就是我们的意思。
显示其他小部件的画布项称为窗口项(Tk 的小部件长期术语)。它们的位置类似于文本和图像项目。您可以给它们明确的宽度和高度属性;它们默认为小部件的首选大小。最后,重要的是您放置在画布上(通过窗口)属性的小部件是画布的子小部件。
b = ttk.Button(canvas, text='Implode!')
6.9 修改项目
我们已经了解了如何修改项目的配置选项 - 其颜色、宽度等。您还可以对项目执行其他一些操作。
要删除项目,请使用delete 方法。
要更改项目的大小和位置,可以使用 coords 方法。您为该项目提供新坐标,指定方式与首次创建该项目时相同。在没有新坐标集的情况下调用此方法将返回项目的当前坐标。您可以使用 move 方法将一项或多项从其当前位置水平或垂直偏移。
所有项目均按照所谓的堆叠顺序从上到下排序。如果堆叠顺序中靠后的项目与其下方的项目重叠,则第一个项目将绘制在第二个项目的顶部。 raise(Tkinter 中的 lift)和 lower 方法允许您调整项目在堆叠顺序中的位置。
参考手册中还详细介绍了其他几个操作,用于修改项目并检索有关它们的信息。
七、事件绑定
我们已经看到,画布小部件作为一个整体,像任何其他 Tk 小部件一样,可以使用绑定命令捕获事件。
您还可以将绑定附加到画布中的单个项目(或它们的组,我们将在下一节中使用标签看到)。因此,如果您想知道某个特定项目是否已被单击,则无需监视整个画布的鼠标单击事件,然后确定该单击是否发生在您的项目上。 Tk 将为您处理这一切。
要捕获这些事件,您可以使用画布中内置的绑定命令。它的工作方式与常规绑定命令完全相同,采用事件模式和回调。唯一的区别是您指定此绑定适用的画布项。
canvas.tag_bind(id, '<1>', ...)
让我们在画板示例中添加一些代码以允许更改绘图颜色。我们将首先创建几个不同的矩形项目,每个矩形项目填充不同的颜色。然后我们将为其中每一个附加一个绑定。单击它们时,它们会将全局变量设置为新的绘图颜色。创建线段时,我们的鼠标运动绑定将查看该变量。
color = "black"
def setColor(newcolor):
global color
color = newcolor
def addLine(event):
global lastx, lasty
canvas.create_line((lastx, lasty, event.x, event.y), fill=color)
lastx, lasty = event.x, event.y
id = canvas.create_rectangle((10, 10, 30, 30), fill="red")
canvas.tag_bind(id, "<Button-1>", lambda x: setColor("red"))
id = canvas.create_rectangle((10, 35, 30, 55), fill="blue")
canvas.tag_bind(id, "<Button-1>", lambda x: setColor("blue"))
id = canvas.create_rectangle((10, 60, 30, 80), fill="black")
canvas.tag_bind(id, "<Button-1>", lambda x: setColor("black"))
正如您所看到的,像 withtag 这样的方法接受单个项目或标签;在后一种情况下,它们将应用于具有该标签的所有项目(也可能没有)。 addtag 和 find 方法有许多其他选项,允许您指定某个点附近的项目、重叠特定区域等。
让我们首先使用标签为当前选择的调色板中的任何项目添加边框。
def setColor(newcolor):
global color
color = newcolor
canvas.dtag('all', 'paletteSelected')
canvas.itemconfigure('palette', outline='white')
canvas.addtag('paletteSelected', 'withtag', 'palette%s' % color)
canvas.itemconfigure('paletteSelected', outline='#999999')
id = canvas.create_rectangle((10, 10, 30, 30), fill="red", tags=('palette', 'palettered'))
id = canvas.create_rectangle((10, 35, 30, 55), fill="blue", tags=('palette', 'paletteblue'))
id = canvas.create_rectangle((10, 60, 30, 80), fill="black", tags=('palette', 'paletteblack', 'paletteSelected'))
setColor('black')
canvas.itemconfigure('palette', width=5)
我们还可以使用标签来使当前绘制的笔画显得更加突出。当释放鼠标按钮时,我们将使线路恢复正常。
def addLine(event):
global lastx, lasty
canvas.create_line((lastx, lasty, event.x, event.y), fill=color, width=5, tags='currentline')
lastx, lasty = event.x, event.y
def doneStroke(event):
canvas.itemconfigure('currentline', width=1)
canvas.bind("<B1-ButtonRelease>", doneStroke)
八、部件标签和访问
我们已经看到,每个画布项都可以通过唯一的 ID 号来引用。还有另一种方便而强大的方法来引用画布上的项目,即使用标签。
标签只是您创建的标识符,对您的程序有意义。您可以将标签附加到画布项目;每个项目可以有任意数量的标签。与每个项目唯一的项目 ID 号不同,许多项目可以共享相同的标签。
你可以用标签做什么?我们看到您可以使用项目 id 来修改画布项目(我们很快就会看到您可以对项目执行其他操作,例如移动它们、删除它们等)。只要您可以使用项目 ID,就可以使用标签。例如,您可以更改具有特定标签的所有项目的颜色。
标签是识别画布中的项目集合(绘制线中的项目、调色板中的项目等)的好方法。您可以使用标签将画布项与应用程序中的特定对象关联起来(例如,使用标签“robotX37”标记属于 ID #X37 的机器人的所有画布项)。使用标签,您不必跟踪画布项目的 id 来稍后引用项目组;标签让 Tk 为您做到这一点。
您可以在使用标签项目配置选项创建项目时分配标签。您可以稍后使用 addtag 方法添加标签,或使用 dtags 方法删除它们。您可以使用 gettags 方法获取项目的标签列表,或使用 find 命令返回具有给定标签的项目 ID 号列表。
>>> c = Canvas(root)
>>> c.create_line(10, 10, 20, 20, tags=('firstline', 'drawing'))
1
>>> c.create_rectangle(30, 30, 40, 40, tags=('drawing'))
2
>>> c.addtag('rectangle', 'withtag', 2)
>>> c.addtag('polygon', 'withtag', 'rectangle')
>>> c.gettags(2)
('drawing', 'rectangle', 'polygon')
>>> c.dtag(2, 'polygon')
>>> c.gettags(2)
('drawing', 'rectangle')
>>> c.find_withtag('drawing')
(1, 2)
九、实现白板做画
from tkinter import *
from tkinter import ttk
root = Tk()
h = ttk.Scrollbar(root, orient=HORIZONTAL)
v = ttk.Scrollbar(root, orient=VERTICAL)
canvas = Canvas(root, scrollregion=(0, 0, 1000, 1000), yscrollcommand=v.set, xscrollcommand=h.set)
h['command'] = canvas.xview
v['command'] = canvas.yview
canvas.grid(column=0, row=0, sticky=(N, W, E, S))
h.grid(column=0, row=1, sticky=(W, E))
v.grid(column=1, row=0, sticky=(N, S))
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)
lastx, lasty = 0, 0
def xy(event):
global lastx, lasty
lastx, lasty = canvas.canvasx(event.x), canvas.canvasy(event.y)
def setColor(newcolor):
global color
color = newcolor
canvas.dtag('all', 'paletteSelected')
canvas.itemconfigure('palette', outline='white')
canvas.addtag('paletteSelected', 'withtag', 'palette%s' % color)
canvas.itemconfigure('paletteSelected', outline='#999999')
def addLine(event):
global lastx, lasty
x, y = canvas.canvasx(event.x), canvas.canvasy(event.y)
canvas.create_line((lastx, lasty, x, y), fill=color, width=5, tags='currentline')
lastx, lasty = x, y
def doneStroke(event):
canvas.itemconfigure('currentline', width=1)
canvas.bind("<Button-1>", xy)
canvas.bind("<B1-Motion>", addLine)
canvas.bind("<B1-ButtonRelease>", doneStroke)
id = canvas.create_rectangle((10, 10, 30, 30), fill="red", tags=('palette', 'palettered'))
canvas.tag_bind(id, "<Button-1>", lambda x: setColor("red"))
id = canvas.create_rectangle((10, 35, 30, 55), fill="blue", tags=('palette', 'paletteblue'))
canvas.tag_bind(id, "<Button-1>", lambda x: setColor("blue"))
id = canvas.create_rectangle((10, 60, 30, 80), fill="black", tags=('palette', 'paletteblack', 'paletteSelected'))
canvas.tag_bind(id, "<Button-1>", lambda x: setColor("black"))
setColor('black')
canvas.itemconfigure('palette', width=5)
root.mainloop()
十、结论
以上我们叙述如何产生一个可画用户图像的画板,因为围绕Canvs有一系列的操作,在本文将它们一一展开。至于更深刻的知识,可以查看相关官网文档。
TkDocs Tutorial - Canvas