十六、用 Tkinter 创建很酷的应用
在前一章中,我们学习了用 Python 创建、打开和操作计算机文件的所有知识。在这一章中,我们正式回到 Python 的乐趣中。您将了解到 Tkinter ,这是一个可以用 Python 创建桌面应用(GUI——图形用户界面)的包。您将学习如何创建按钮、标签、框等等。
让我们开始吧!
还记得我们和海龟一起工作时做了什么吗?与 Tkinter 合作的一些流程是相同的。你现在是职业程序员了。你已经知道了 Python 的基础知识。你已经完成了迷你项目的整个章节。
所以,在这一章,我希望你穿上你的大男孩/女孩裤子。我不会给出很多实际的解释,因为你已经知道了很多这方面的东西。我们将在这一章中涉及很多内容,在这一章的最后,你将拥有和你在系统中看到的一样漂亮的应用,并且你将拥有创建更多应用的工具。激动吗?我也是!我们开始吧。
就像 turtle 一样,我们需要首先导入 Tkinter 。让我们打开一个新的脚本文件。不要保存为“tkinter.py”。您的 Python 安装中已经有一个类似的文件,它包含您将用来创建应用的所有预定义方法的代码。我将把我的文件保存为 tkPrograms.py。
我们先导入 Tkinter 。
from tkinter import *
我已经要求所有东西都从 Tkinter 包中导入。“*”代表一切。现在,我们需要创建一个包含我们的应用的窗口。我要调用我的 w,需要调用的函数是 Tk():
w = Tk()
让我们运行一下,看看我们会得到什么(图 16-1 )。
图 16-1
tkinter screen(tkinter screen)
看那个!一扇漂亮的小窗。它也有可以用来最小化、最大化和关闭窗口的按钮。你为什么不试试它们?
标题有点奇怪吧?上面只写着 tk。我不喜欢!我希望我的能说“我的第一个应用”。我该如何改变?当然是通过在我们刚刚创建的窗口上调用 title()方法!
w.title('My first Tkinter app')
再次运行,看看您会得到什么(图 16-2 ):
图 16-2
标题变更
看那个!上面写着我现在想要的。我稍微调整了一下窗口大小,这样我就可以看到整个标题。这太美了。我只写了三小段代码,现在我有了一个漂亮的小窗口。你能看出 Tkinter 有多厉害吗?O:
好吧,那么,这是它的设置。接下来,让我们看看如何创建小部件并将它们放在这个窗口上。这就是事情变得有趣的地方!
标签、纽扣和包装
Tkinter 有很多“小部件”,你可以创建它们来让应用变得生动。这些小部件包括按钮、文本框和单选按钮。一旦创建了一个小部件,就需要把它放在窗口上。所以,这个过程通常有两个步骤。现在让我们看看如何创建标签和按钮,好吗?
要创建标签,您需要使用" Label()“方法,并在第一个属性中提及您希望标签放置的窗口,并在” text "属性中提及您希望标签中的文本。我将创建一个变量 label1,并将我的标签放入其中。
label1 = Label(w,text='My Label')
如果我运行这个,我将再次以一个空白窗口结束。为什么呢?记得我之前告诉你的吗?小工具需要放在窗口内才可见。我们如何做到这一点?
最简单的方法之一是使用 pack()方法。它只是将你创建的窗口小部件打包或放入窗口,并根据窗口小部件的大小调整窗口大小。
这就是为什么我把我的标签放在一个变量中,这样我就可以在变量上调用 pack()方法。那样看起来很整洁。
label1.pack()
现在运行所有程序,看看会得到什么(图 16-3 )。
图 16-3
标签
这就对了。一个小小的窗户,上面只有我的标签。
如果你不想要两行代码,你可以这样写,这样就可以了:
Label(w, text='My Label').pack()
我将坚持使用第一种方法,因为一旦我们开始设计标签并向它添加许多属性,它看起来会很整洁。稍后我还可以引用同一个标签来更改它的属性值。它只是在现实世界中更有活力。
但是你注意到什么了吗?每当我运行这个程序时,我的 shell 都会打开我的窗口,但是它会返回到下一个提示符(<<
现在,再次运行,您会注意到我们的 Shell 没有进入下一个提示。很好!
好吧。我们现在能把事情弄得漂亮一点吗?为什么我们不玩我的标签的大小和颜色?
不过,在我们开始之前,让我们先来看看颜色的选择。 Tkinter 认识一吨的颜色名称,你可以在这里找到他们的名单,在他们的官方网站: www.tcl.tk/man/tcl8.5/TkCmd/colors.htm
。
如果你想把颜色形象化,你可以使用这个网站: www.science.smith.edu/dftwiki/index.php/Color_Charts_for_TKinter
.
第二个链接是第三方网站,但仍然有用。
所以,现在我们已经用颜色武装了自己,让我们开始吧!
您可以使用宽度和高度属性来更改标签的大小。它们分别改变标签的宽度和高度。但是,当您使用这些属性时,您会注意到一些不同。假设这两个属性的值都是 10,但是您会注意到标签的高度大于标签的宽度。这是因为这些值不是以像素为单位,而是以字符“0”的大小为单位。它的宽度比它的高度小两倍,不是吗?这就是你看到的。所以,在给出你的价值观的时候考虑一下这个。
此外,您可以使用 bg 属性更改标签的颜色,使用 fg 属性更改标签文本的颜色。让我们把它们结合在一起,设计我们的标签吧!
label1 = Label(w, text='My Label', bg="Salmon4", fg="gold2", width=10, height=5)
label1.pack()
我已经将背景色改为“Salmon4”,文字颜色改为“gold2”,宽度改为 10 个字符单位,高度改为 5 个字符单位。现在让我们运行程序(图 16-4 )!
图 16-4
更改标签的大小和颜色
喔!看看窗口是如何扩展到包含我的新标签的。太完美了。
你还可以使用其他的属性,但是让我们在本章的后面部分来看看。那么,纽扣呢?它遵循相同的程序。使用 Button()方法,属性相同。
button1 = Button(w, text='My Button', bg='steel blue', fg="snow", width=10, height=5)
button1.pack()
运行所有程序,看看会得到什么(图 16-5 )。
图 16-5
纽扣
你会发现你真的可以点击按钮。它是动画,不像标签。
但这还没有结束。当你点击这个按钮时,你可以让你的系统做一些事情。使用 command 属性,我可以在单击按钮时调用函数。
def buttonClick():
print('You just clicked the button! :)')
button1 = Button(w, text='Click Me!', bg='steel blue', fg="snow", width=10, height=5, command=buttonClick)
button1.pack()
如你所知,在 Python 中,函数定义应该总是在函数调用之前,在我们的例子中,是在按钮之前。让我们创建一个打印消息的函数 buttonClick()。就这样。
现在,在我们的按钮中,我们添加了一个新属性“command”,该值是我们函数的名称。就名字,不用加括号。现在,像往常一样,打包按钮并运行程序,您将得到按钮。单击它,回到 Shell,您会看到:
= RESTART: C:\Users\aarthi\AppData\Local\Programs\Python\Python38-32\tkPrograms.py
You just clicked the button! :)
哇哦!有效!
详细包装
到目前为止,事情看起来很糟糕。我们实话实说吧。这不是我们创建应用的方式。但是不要惊慌。pack()方法还有一些技巧。
在我们看这些之前,让我们先来看看所谓的“框架”。这个框架不完全是一扇窗户。我们已经创造了一个。但是有了框架,你可以把你的部件分组,然后按照你想要的方式组织它们。
让我们围绕标签和按钮创建一个框架。我们也可以给出背景颜色(bg)、宽度和高度,但是在这种情况下,宽度和高度是以像素为单位的,所以要使数字更大。
frame1 = Frame(w)
frame1.pack()
label1 = Label(frame1, text='First button')
label1.pack()
button1 = Button(frame1, text="Button1")
button1.pack()
label2 = Label(frame1, text='Second button')
label2.pack()
button2 = Button(frame1, text="Button2")
button2.pack()
正如您在前面的代码中看到的,我创建了一个框架,然后在该框架中创建了两个标签和两个按钮。因此,frame1 的根窗口是“w”,即原始窗口,但其余小部件的根窗口是我们的 frame1。这样,我们可以在同一个窗口中创建任意多的框架。现在,让我们运行它,看看我们会得到什么(图 16-6 )。
图 16-6
包装几何方法
似乎什么都没有改变。☹该收拾()方法来救援了!
pack()方法是一个几何管理器,它将您的小部件打包到它的父窗口(在我们的例子中是 frame1)的行和列中。
首先,让我们看看填充选项。您可以使用此选项让您的小部件填充父小部件。
现在,弹出的窗口看起来好像包含了整个框架(图 16-7 ),但事实并非如此。如果我调整它的大小,它会在框架周围添加填充。
图 16-7
Pack()调整大小问题
但是如果我想让框架充满主窗口,那么我可以使用填充和扩展选项。先说“填”。这里我可以给三个值,X,Y,或者两个都给。
x 在水平方向填充主窗口,Y 在垂直方向填充主窗口,两者都填充了整个窗口小部件。三个都看看吧。
frame1 = Frame(w, bg="black")
frame1.pack(fill=X)
我已经给了框架一个背景色,这样我们就可以单独看到框架了。
接下来,我将填充到 Y:
frame1.pack(fill=Y)
最后我把它改成两个(图 16-8 ):
图 16-8
带填充的框架
frame1.pack(fill=BOTH)
填充在一定程度上起作用,但是当窗口调整大小时,它仍然不会扩展。这是因为 fill 只是让 Python 知道它想要填充给它的整个区域。如果我们两个都给,它将水平和垂直填充整个区域。
但是,如果我们希望它填充整个父对象,也就是说,在父对象扩展时扩展,那么我们需要“expand”选项。让它成真,看看神奇。
frame1 = Frame(w, bg="black")
frame1.pack(fill=BOTH, expand=True)
让我们对 X 和 Y 做同样的尝试:
frame1.pack(fill=X, expand=True)
最后,
frame1.pack(fill=Y, expand=True)
现在,用不同的填充值运行程序,并调整窗口大小,得到这个结果(图 16-9 )。
图 16-9
Tkinter 中的填充选项
很迷人,不是吗?
好了,现在我们知道了如何填充父窗口,但是这对我们的小部件有什么帮助呢?我们有四个,我希望第一个标签和按钮在第一行,第二个标签和按钮在第二行。我该怎么做?这就是“侧面”选项出现的原因。
让我先解释一下侧边选项是如何工作的。让我们创建两个小部件,并尝试用“side”的不同选项打包它们。
label = Label(w, text='My Label')
label.pack(side=TOP)
button = Button(w, text='My Button')
button.pack(side=TOP)
我已经给了边作为顶部开始,这是默认的。您会注意到小部件一个接一个地被打包。
现在,将两者的值都更改为 LEFT。它会把所有东西并排放在一起。当你给 BOTTOM 的时候,它会从底部到顶部打包所有的东西,RIGHT 做的正好和 LEFT 相反。
当我们运行前面代码的四个变体时,我们得到以下四个输出(图 16-10 )。
图 16-10
Tkinter 中的侧面选项
看看在第三个图像中,按钮是如何出现的,然后是标签。这就是 BOTTOM 的作用。它颠倒了顶部。同样,右是左的反义词。
看起来很棒,是的,但这似乎还不完整。这是因为您需要所有三个选项来按照您想要的方式正确对齐您的小部件。
所以现在,让我们结合所有的选项,创造一些看起来一致的东西。我将创建两个框架,每个框架都将被打包在顶部(一个接一个),它们从两侧填充父窗口,expand 为 True。
类似地,我将在第一个框架下创建一个标签和一个按钮,并向左打包(并排),但使它们包含整个父框架(fill 为 both,expand 为 True)。让我们对第二帧重复同样的操作。
现在,让我们看看我们得到了什么:
frame1 = Frame(w, bg="black")
frame1.pack(side=TOP, fill=BOTH, expand=True)
label1 = Label(frame1, text='First button')
label1.pack(side=LEFT, fill=BOTH, expand=True)
button1 = Button(frame1, text="Button1")
button1.pack(side=LEFT, fill=BOTH, expand=True)
frame2 = Frame(w, bg="white")
frame2.pack(side=TOP, fill=BOTH, expand=True)
label2 = Label(frame2, text='Second button')
label2.pack(side=LEFT, fill=BOTH, expand=True)
button2 = Button(frame2, text="Button2")
button2.pack(side=LEFT, fill=BOTH, expand=True)
运行前面的代码,你会得到这个(图 16-11 )。
图 16-11
包装有序的标签和按钮
哇哦!这正是我第一次创建这些小部件时想要的放置方式。搞定了。
现在展开这个窗口,您会注意到小部件也随之展开。由于子部件完全包围了框架,您看不到它们的背景颜色,这意味着我们已经正确地完成了我们的工作!
大量输入
既然您已经知道了如何使用 pack()方法来正确地对齐小部件,那么让我们回到快速查看更多小部件。Tkinter 提供了大量从用户那里获取输入的小工具。
一行文本
通过使用 Entry()方法,可以从用户那里获得单行文本输入。
entry = Entry().pack()
运行前面的代码,你会得到这个(图 16-12 )。
图 16-12
入口小部件
看那个!我现在可以给出单行条目。
此外,除了 fg、bg 和 width 等常见属性之外,您的入口小部件还具有可用于操作入口的方法。
为什么我们不看看怎么做呢?我们可以使用 get()方法来检索我们在条目中键入的内容,并且我们可以按照自己的意愿使用它。
现在,让我们创建一个标签“name”和一个输入框,最后创建一个按钮“Enter”。当用户单击按钮时,它调用 greet()函数,该函数从输入框中“获取”输入并打印出“Hello”消息。够简单吗?我们试试吧!
def greet():
name = entry.get()
print('Hello {}'.format(name))
label = Label(w,text='Your name?')
label.pack(side=LEFT)
entry = tkinter.Entry(w)
entry.pack(side=LEFT)
button = Button(w,text='Enter',command=greet)
button.pack(side=LEFT)
让我们运行一切,我们会得到这个(图 16-13 )。
图 16-13
带有条目的名称框
现在,当我按 Enter 键并查看 Shell 时,我得到了这个:
= RESTART: C:\Users\aarthi\AppData\Local\Programs\Python\Python38-32\tkPrograms.py
Hello Susan
耶!
另外,delete()方法可以删除文本,insert()方法可以在任意位置插入文本。
我们先来看看 insert。语法非常简单。
entry.insert(pos, ‘text’)
所以,只要给出你想要插入文本的位置。第一个位置是 0,从那里开始增加,就像你的弦一样。第二个参数是要插入的直接文本或包含文本的变量。
你想看看这是怎么工作的吗?让我们修改我们的程序。现在,当用户输入他们的名字时,他们必须点击“插入你好”按钮,当点击时,就会在他们的名字前插入你好和一个空格。
def insert():
entry.insert(0,'Hello ')
label = Label(w,text='Your name?')
label.pack(side=LEFT)
entry = tkinter.Entry(w)
entry.pack(side=LEFT)
button = Button(w,text='Insert Hello',command=insert)
button.pack(side=LEFT)
让我们运行程序(图 16-14 )。
图 16-14
插入到输入框中
当我们点击按钮时,我们得到这个(图 16-15 )。
图 16-15
插入的
哇哦!
同样,你可以删除。如果你只给出一个参数,它会删除那个字符。0 删除第一个字符,1 删除第二个字符,依此类推。
但是,如果你给一个范围,它会删除一个字符范围。
通常,不考虑范围内的最后一个数字。例如,范围 0,4 删除索引 0 到 3 中的字符(不包括 4)。
但是如果你想删除一切,那么就把 END 作为你的最后一个论点,就大功告成了。我们试试好吗?
def insert():
entry.delete(0,END)
label = Label(w,text='Your name?')
label.pack(side=LEFT)
entry = tkinter.Entry(w)
entry.pack(side=LEFT)
button = Button(w,text='Clear',command=insert)
button.pack(side=LEFT)
当我运行程序时,我得到这个(图 16-16 )。
图 16-16
从输入框中删除
我已经输入了 Susan,当我按下清除按钮时,我得到了这个(图 16-17 )。
图 16-17
输入框已清除
一笔勾销!
一行又一行
现在,让我们看看如何输入和操作多行文本!您可以使用 Text()方法来做到这一点。
text_box = Text()
text_box.pack()
运行这个,你会得到一个大的文本框,当你在其中输入几行代码时,它会是这样的(图 16-18 )。
图 16-18
文本框
您可以使用 get()方法从该文本框中检索文本,但是您需要指定行号和范围中的字符数。
“1.0”将只检索第一行的第一个字符。
“1.0”,“1.9”将检索第一行中的第一个字符到第九个字符。
“2.0”、“2.5”将从第二行的第一个字符到第五个字符进行检索,以此类推。
“1.0”,“2.10”会一直检索到第二行的第十个字符,所以可以这样跨多行。
要获得整个文本,只需给出“1.0”,结束。
你现在明白这是怎么回事了吗?让我们尝试检索整段文本。
text_box = Text(w)
text_box.pack()
def get_text():
t = text_box.get(1.0,END)
print(t)
button = Button(w,text="Get data",command=get_text)
button.pack()
让我们再次运行程序,键入相同的文本,看看我们会得到什么(图 16-19 )。
图 16-19
从文本框中获取数据
现在我们有一个大的文本框,在末尾有一个小按钮,因为我们就是这样包装的。让我们点击按钮,我们得到这个:
>>>
= RESTART: C:\Users\aarthi\AppData\Local\Programs\Python\Python38-32\tkPrograms.py
Hello there! This is the first line of text.
This is the second line of text.
This is the third line of text.
Bye bye!
有效!
同样,您可以在文本框中插入文本。
text_box.insert(1.0,"Welcome! ")
前面的代码将插入“欢迎!”在第一行的开头加一个空格。文本、复选框、条目、单选按钮、菜单按钮、复选按钮、列表框。
要在文本末尾插入内容,请使用 end 作为第一个参数,但是如果您希望文本在新的一行中,请在第二个参数的开头添加\n(换行符),如下所示:
text_box.insert(END,"\nYou're great!")
要删除整段文本,请执行以下操作:
text_box.delete("1.0",END)
您可以使用与 get()相同的格式来删除文本片段。
这是对文本框的快速浏览。现在,进入下一个部件!
Tkinter变量
**但在此之前,我想先谈谈变量。还记得我们如何直接输入文本,而不使用变量吗?那不是很有活力。如果我想根据应用/游戏中发生的事情更改标签文本或按钮文本,该怎么办?我需要一个变量。这就是 Tkinter 可变类的用武之地。它们的工作原理和我们的正常变量非常相似。
您可以创建五种变量:整型、字符串型、布尔型和双精度型(浮点型)。
num = IntVar()
string = StringVar()
b = BooleanVar()
dbl = DoubleVar()
将它们赋给一个实际变量,使该变量成为一个t inter变量。另外,确保语法的大写和小写保持不变。
既然有了变量,就可以使用“set”方法给它们赋值了。如果你给一个变量赋了一个错误的值,你会得到一个错误。所以,只有一个整型变量,以此类推。
要取回变量值,请使用 get()方法。所以,让我们把两者结合起来,看看我们会得到什么。
num.set(100)
string.set("Hello there!")
b.set(True)
dbl.set(150.14)
print(num.get())
print(string.get())
print(b.get())
print(dbl.get())
运行前面的代码,您将得到这样的结果:
= RESTART: C:\Users\aarthi\AppData\Local\Programs\Python\Python38-32\tkPrograms.py
100
Hello there!
True
150.14
但这仍然不是动态的,对吗?我们可以将动态变量设置为我们的t inter变量吗?答案是肯定的!
只需获取一个输入,将其放入一个变量中,并将其设置为您的字符串(或任何类型)。就这样。
i = input('Enter a string: ')
string.set(i)
print(string.get())
运行前面的代码,您将得到这样的结果:
= RESTART: C:\Users\aarthi\AppData\Local\Programs\Python\Python38-32\tkPrograms.py
Enter a string: Hello there!
Hello there!
所以现在,我们可以动态设置标签文本。
i = input('Label text: ')
string = StringVar()
string.set(i)
label = Label(w, text=string.get())
label.pack()
运行前面的代码,您将得到这样的结果:
= RESTART: C:\Users\aarthi\AppData\Local\Programs\Python\Python38-32\tkPrograms.py
Label text: Hello there!
输入完毕后按回车键,你将得到(图 16-20 ):
图 16-20
字符串变量
耶!我们的第一个动态标签!
很多选择!
如果你想给你的用户选择,那么复选框和单选按钮是正确的选择,你不这样认为吗?Tkinter 也有这些小工具!
您可以使用 Checkbutton(小“b”)小部件创建一个复选框。
它的工作方式类似于其他小部件,除了您可以给出 onvalue 和 offvalue 来指定当 check 按钮被单击或不被单击时的“值”。
但是获得复选按钮状态的一个更简单的方法是将一个 Tkinter 整数变量赋给它的“variable”属性,每当复选框被选中时,变量的值就变为 1,当它未被选中时,变量的值就变为 0。
我们将创建两个复选框,所以让我们创建两个整数变量来存储它们的“状态”(无论它们是否被选中)。
c1 = IntVar()
c2 = IntVar()
让我们创建一个标签“杂货清单”并打包。
Label(w,text='Grocery list').pack()
现在是我们的复选框。唯一的区别是我们的“变量”属性被赋予了整型变量。
Label(w,text='Grocery list').pack()
check1 = Checkbutton(w,text="Milk",variable = c1)
check1.pack(side=LEFT)
check2 = Checkbutton(w,text="Flour",variable = c2)
check2.pack(side=LEFT)
现在,我们如何检索这些值呢?我们需要一个按钮,单击它将调用 check()函数来检查哪些框被单击了。
def check():
if(c1.get() == 1):
print('We bought Milk.')
if(c2.get() == 1):
print('We bought flour.')
button = Button(w,text='Check',command=check)
button.pack()
简单!让我们运行程序,我们得到这个(图 16-21 )。
图 16-21
检验盒
按下“检查”按钮,你会得到这个:
= RESTART: C:\Users\aarthi\AppData\Local\Programs\Python\Python38-32\tkPrograms.py
We bought Milk.
We bought flour.
对于单选按钮,我们只需要一个变量,因为我们只是选择其中一个选项。它有一个“value”属性,当设置了一个整数值时,它将把相同的值赋给你赋给“variable”属性的变量。
您也可以在单选按钮中添加一个“命令”。
让我们创建一个程序,询问用户是否喜欢狗,并根据他们的选择打印一条消息!
对于本例,我将直接从单选按钮发出命令,所以让我们先创建“check”函数。
我们将创建一个字符串,用来保存人们点击复选框后需要显示的消息。现在,我们将在我们的单选按钮中设置两个值,1 表示这个人喜欢狗,2 表示这个人不喜欢狗。
一旦我们设置了字符串,创建标签。
def check():
string = StringVar()
if var.get() == 1:
string.set('You love dogs! :)')
else:
string.set("You don't love dogs :(")
label = Label(w,text=string.get())
label.pack()
现在,让我们创建一个整数变量来保存单选按钮的值。接下来是一个询问他们是否爱狗的标签。
var = IntVar()
Label(w,text='Do you love dogs?').pack()
最后,单选按钮包含相关的文本、分配给它们的变量“var”、每个值和一个命令,如果按钮被选中,该命令将调用“check”函数。
radio1 = Radiobutton(w,text="Yes!",variable = var, value=1, command=check)
radio1.pack()
radio2 = Radiobutton(w,text="Nope",variable = var, value=2, command=check)
radio2.pack()
就这样!
运行程序,你会得到这个(图 16-22 )。
图 16-22
单选按钮
选择一个选项(图 16-23 ):
图 16-23
选择了单选按钮
完美!
菜单
使用 Tkinter ,您可以创建像您在应用中看到的菜单!您可以使用 Menu()方法来创建它们。
您可以创建一个主菜单,并将其配置到窗口的顶部,并且可以添加任意数量的子菜单。
让我创建一个主菜单“main”。
from tkinter import *
w = Tk()
main = Menu(w)
让我们在主菜单中添加一个子菜单。我会叫它文件菜单。
fileMenu = Menu(main)
现在,我将使用 add_cascade()方法向我的第一个子菜单添加一个标签,并将其放在 main 中。
main.add_cascade(label='File',menu = fileMenu)
现在,让我们添加项目到我们的主菜单。
fileMenu.add_command(label='New File', command=lambda: print('New File clicked'))
fileMenu.add_command(label='Open', command=lambda: print('Open clicked'))
如您所见,我们可以像处理按钮一样给这些项目附加一个命令。
如果你现在运行这个程序,你将什么也看不见。这是因为一旦您创建了所有的菜单、子菜单和项目,您就需要将主菜单配置到窗口(就像您打包小部件一样)中,以便显示它。
使用 config()方法可以做到这一点。
w.config(menu=main)
现在,运行您的程序,您将能够看到您的菜单(图 16-24 )。
图 16-24
菜单
单击“新建文件”菜单项,您将看到以下内容:
>>> New File clicked
我得到了我期望的消息。完美!
完美的布局——网格
我认为包装几何管理器在功能上有一点点限制。你不也这样认为吗?
这就是为什么 Tkinter 有网格几何管理器,这是联赛除了包管理器。您可以根据行和列完美地对齐您的小部件。
行和列的排列如下图所示(图 16-25 )。小部件将被放置在单元格内,每个单元格都有一个从 0 开始的行号和列号。您可以将单元格扩展到任意数量。
图 16-25
网格中的行和列
你可以提到部件的具体行和列,以及你希望它粘在哪里。
sticky 有多个值:E 代表东,W 代表西,N 代表北,S 代表南,NE 代表东北,NW 代表西北,SE 代表东南,SW 代表西南。
如果你给一个小部件一个“E ”,它将(通常是文本)粘在栏的最右边,以此类推。
如图所示,行和列从 0 开始。您可以使用 padx 和 pady 来填充小部件,这样它们就不会粘在一起。
所以,让我们把所有的放在一起排列一堆标签,好吗?
from tkinter import *
w = Tk()
w.title('My first Tkinter app')
#first row, first column, east sticky
label1 = Label(w,text='Label1')
label1.grid(row=0,column=0,sticky='E',padx=5,pady=5)
#first row, 2nd column, east sticky
label2 = Label(w,text='Label2')
label2.grid(row=0,column=1,sticky='E',padx=5,pady=5)
#first row, 4th column, west sticky
button1 = Button(w,text='Button1')
button1.grid(row=0,column=2,sticky='W',padx=5,pady=5)
#second row, first column, east sticky
label3 = Label(w,text='Label3')
label3.grid(row=1,column=0,sticky='E',padx=5,pady=5)
#first row, 2nd column, east sticky
label4 = Label(w,text='Label4')
label4.grid(row=1,column=1,sticky='E',padx=5,pady=5)
#second row, 4th column, west sticky
button2 = Button(w,text='Button2')
button2.grid(row=1,column=2,sticky='W',padx=5,pady=5)
w.mainloop()
运行程序,你会得到这个(图 16-26 )。
图 16-26
以网格形式排列的小部件
太美了!
迷你项目-小费计算器应用
让我们把到目前为止学到的所有东西放在一起,用 Tkinter 创建一个小费计算器,好吗?
这是我们需要的:
-
两个输入框用于输入账单金额(浮点)和小费金额。
-
接下来,我们需要一个按钮来获取这些值并调用 tip_calculator()函数。
-
这个函数将计算我们的小费,并在屏幕底部的标签中显示结果。
够简单吗?我们开始吧!
-
让我们先设置一下 Tkinter 。
from tkinter import * w = Tk() w.title('My first Tkinter app')
-
然后,让我们看看账单(一个标签和一个条目,在屏幕上正确排列)。
#Get the bill amount bill_label = Label(w,text='What was your bill? ') bill_label.grid(row=0,column=0,sticky="W",padx=5,pady=5) bill = Entry(w) bill.grid(row=0,column=1,sticky="E",padx=5,pady=5) Next, let’s create the label and entry widgets to get the tip. #Get the tip tip_label = Label(w,text='What did you tip? ') tip_label.grid(row=1,column=0,sticky="W",padx=5,pady=5) tip = Entry(w) tip.grid(row=1,column=1,sticky="E",padx=5,pady=5)
-
在创建按钮之前,我们需要定义 tip_calculator 函数。它将从 tip 和 bill 中获取条目值,并将这些值转换为整数(条目通常是字符串)。接下来,我们要计算小费的百分比。
#Tip calculator function def tip_calculator(): t = tip.get() t = int(t) b = bill.get() b = int(b) percent = (t * 100) / b percent = int(percent)
-
让我们根据“percent”的值格式化一个适当的字符串。
if((percent >= 10) and (percent <= 15)): string = '{}%. You tipped Okay!'.format(percent) elif((percent >= 15) and (percent <= 20)): string = '{}%. That was a good tip!'.format(percent) elif(percent >= 20): string = '{}%. Wow, great tip! :)'.format(percent) else: string = "{}%. You didn't tip enough :(".format(percent)
-
最后,让我们创建一个 Tkinter 字符串变量,并在其中设置格式化字符串,然后用该文本创建一个标签,并将其放在屏幕上。
str_var = StringVar() str_var.set(string) label = Label(w, text=str_var.get()) label.grid(row=3,column=0,padx=5,pady=5)
-
最后,让我们创建一个按钮,让它在被点击时调用函数。
#Enter button button = Button(w,text='Enter',command=tip_calculator) button.grid(row=2,column=0,sticky="E",padx=5,pady=5) w.mainloop()
让我们运行程序,我们会得到这个(图 16-27 )。
图 16-27
小费计算器应用
我们的应用运行完美!你可以通过添加颜色和字体来进一步美化它。
摘要
在本章中,我们看了如何使用 Tkinter 包在 Python 中创建桌面应用。我们学习了如何创建不同的小部件,包括按钮、标签、复选框、单选按钮和菜单。我们还学习了框架。然后,我们学习了如何设计小部件的样式,以及当小部件被点击时如何执行命令。最后,我们学习了如何使用 pack()和 grid()几何方法在屏幕上组织小部件。
在下一章,让我们学习当点击、鼠标点击和键盘按下等事件在我们的小部件上发生时执行功能。**
十七、项目:Tkinter 井字棋
在前一章,我们学习了 Tkinter 的基础知识。我们学习了如何用 Tkinter 创建按钮、标签、框架、菜单、复选框、单选按钮等等。我们还学习了如何设计我们的窗口小部件,并让我们的窗口小部件根据事件(点击、鼠标移动、键盘按压等)来做事情。).最后,我们学习了如何用画布画画。
在这一章中,让我们应用我们在上一章中学到的知识,创建我们的第一个大项目:井字棋!我们还将了解事件,并将它们绑定到我们的小部件。
绑定事件–让您的应用充满活力!
在上一章中,我们学习了很多关于 Tkinter 的知识。我敢肯定,你已经厌倦了学习所有的概念,你宁愿现在就创建一个项目。耐心听我说几分钟,好吗?让我们快速了解如何将事件绑定到我们的小部件,并开始玩井字棋。
那么,什么是绑定呢?好吧,让我们说你点击你的按钮(用你的鼠标左键),你想在这种情况下执行一个功能。你会怎么做?你可以使用“命令”,是的,但是如果你想区分鼠标左键和右键点击呢?根据点击了哪个鼠标键或者按了哪个键盘键打开不同的功能?
事件可以帮助您完成所有这些任务,甚至更多。
让我们先来看看按钮点击事件。让我们创建一些绑定,当鼠标左键和右键点击一个按钮部件时,这些绑定执行不同的功能。
from tkinter import *
w = Tk()
def left_clicked(event):
print('Left mouse button clicked')
return
def right_clicked(event):
print('Right mouse button clicked')
return
button = Button(w,text='Click here!')
button.pack()
button.bind('<Button-1>',left_clicked)
button.bind('<Button-3>',right_clicked)
w.mainloop()
请看前面的代码片段。我们创建了按钮,打包它,然后使用 bind()方法创建两个绑定。第一个参数表示我们希望绑定到按钮的事件,第二个参数是事件发生时需要调用的函数。
事件需要在引号中指定,表示鼠标左键单击,是鼠标右键单击,因为是鼠标中键单击。
现在,在函数定义中,我们接受了一个参数 event,尽管我们没有从函数调用中发送任何参数。这怎么可能?好吧,每当一个事件被绑定到一个小部件时,你的程序自动发送一个事件对象到这个函数。这个“事件”会有很多关于刚刚发生的事件的信息。
例如,我们可以使用 event.x 和 event.y 找到鼠标左键单击的 x 和 y 坐标位置。
from tkinter import *
w = Tk()
def click(event):
print("X:{},Y:{}".format(event.x,event.y))
frame = Frame(w,width=200,height=200)
frame.pack()
frame.bind('<Button-1>',click)
w.mainloop()
现在,让我点击框架上的任意位置(图 17-1 )。
图 17-1
鼠标左键单击事件
我点击了中间的某个地方,结果是这样的:
= RESTART: C:\Users\aarthi\AppData\Local\Programs\Python\Python38-32\tkPrograms.py
X:93,Y:91
那是 93 的 x 和 91 的 y。太好了。
同样,你也可以寻找键盘按键。为此,您需要使用绑定,并且可以使用 event.char 属性打印出被按下的确切键。这只适用于可打印的键,不适用于空格、F1 等键。对此有单独的事件绑定。
当您将鼠标光标移动到小部件上时,您可以使用事件来运行函数。当用户按下回车键时,触发事件,依此类推。
好了,现在你已经知道了事件是如何发生的,让我们开始玩井字棋吧!
井字棋–解释
到目前为止,我们只是在创建迷你项目。但是在现实世界中,你需要做的不仅仅是画几个形状或者运行一堆循环。在现实世界中,你将创建人们日常生活中使用的游戏和应用。
所以在这一章,我们将创建我们的第一个这样的游戏。让我们创建经典的井字棋。我们的应用看起来会像这样(图 17-2 )。
图 17-2
Tkinter 的井字棋
我们的游戏板有九个盒子,你可以在上面画画。有两个玩家:X 和 O,他们轮流在黑板上画画。如果一个玩家连续抽三张牌(垂直、水平或对角),那么这个玩家赢。如果没有人做到这一点,并且所有九个棋盘都被填满,那么游戏就是平局。
这是一个简单的游戏。我将介绍“messagebox ”,它将帮助您创建您在笔记本电脑程序中看到的消息弹出窗口。
设置Tkinter
像往常一样,让我们从导入 Tkinter 的所有内容开始。但是我们还需要导入 messagebox,因为当您使用*时,您只是导入外部的类和函数,而不是“一切”。
from tkinter import *
from tkinter import messagebox
让我们接下来设置我们的窗口。我将把我的窗口的标题改为“井字棋”。
w = Tk()
w.title('Tic Tac Toe')
创建全局变量
我们在函数一章中看到了全局变量,还记得吗?全局变量可用于跟踪多个函数中发生的变化。在这种情况下,我们需要多个全局变量。
例如,我们需要跟踪“turn”变量发生的总体变化,该变量计算玩家使用的回合数(井字棋总共提供九个回合)。
turn = 0
接下来,我们需要一个列表来记录谁在哪个盒子上玩过。这个列表将有九个预定义的条目,它们当前包含空字符串。我们将根据谁在哪个盒子上玩,用“X”或“O”来代替它们。
state = ['','','','','','','','','']
接下来,我们需要一个二维列表(一个更大的列表中的列表)来保存所有的获胜状态(图 17-3 )。我们将在每个玩家玩游戏后比较这些获胜状态,以检查是否有人赢得了游戏。
图 17-3
井字棋盒(编号)
请看前面的图像。在井字棋中,如果玩家在三个连续的盒子上画出他们的符号,无论是垂直的、水平的还是对角的,他就赢了。1,4,7 是第一个垂直获胜状态。1,2,3 是第一个水平获胜状态。1,5,9 是第一个对角线赢的状态,以此类推。
有三种垂直获胜状态、三种水平获胜状态和两种对角获胜状态。一共赢了八个州。
让我们将它们存储在我们的列表中。但是因为我们在这里使用列表,并且它们的索引从 0 开始,所以让我们将 1,2,3 转换为 0,1,2。对其余的获胜州进行同样的操作,您将得到如下结果:
winner = [[0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [2,4,6]]
最后,让我们创建一个变量“game”来存储游戏的状态。当我们开始游戏时,这将是真的,如果有人赢了,或者如果游戏以平局结束(所有九个盒子都用完了,但没有人赢),我们将把“游戏”的值改为假,这样就没有人可以在盒子上画画了。
game = True
创建按钮
我们需要九个盒子,玩家可以在上面“画画”,对吗?为什么不把事情简单化,创建按钮呢?我们可以让他们的文本从一个单倍行距的字符串开始,每次玩家玩的时候,我们可以把文本改成“X”或者“O”。那会有用的!
在我们创建按钮之前,让我们定义一个变量“font ”,它将存储按钮文本所需的字体(我们的玩家在按钮上“画”的内容)。“Helvetica”,20 为文本大小,“粗体”字体。
font = ('Helvetica',20,'bold')
接下来,让我们创建九个按钮,每个框一个。我们将文本设为单个空格,高度为 2,宽度为 4。让我们将创建的“font”变量分配给字体。
最后,我们将看到我们在函数一章中学到的“lambda”函数的一些实际应用。到目前为止,每当我们在按钮上使用 command 属性时,我们都不必向被调用的函数发送参数。
但是现在,我们需要发送两个参数:一个是被点击的实际按钮,另一个是被点击的按钮的编号(从 1 开始)。
如果你想在这样的事件中发送参数,你需要用 lambda 封装函数调用,如下所示。lambda 本身不需要任何参数,因为它现在是一个匿名函数。您的一行代码将是对 buttonClick()函数的函数调用,其中发送了参数 b1 和 1。
让我们对其余的按钮重复这个过程。让我们也将按钮平行地放置在网格中。这是正常的网格排列。
#9 buttons
b1 = Button(w, text=' ', width=4, height=2, font = font, command = lambda: buttonClick(b1,1))
b1.grid(row=0,column=0)
b2 = Button(w, text=' ', width=4, height=2, font = font, command = lambda: buttonClick(b2,2))
b2.grid(row=0,column=1)
b3 = Button(w, text=' ', width=4, height=2, font = font, command = lambda: buttonClick(b3,3))
b3.grid(row=0,column=2)
b4 = Button(w, text=' ', width=4, height=2, font = font, command = lambda: buttonClick(b4,4))
b4.grid(row=1,column=0)
b5 = Button(w, text=' ', width=4, height=2, font = font, command = lambda: buttonClick(b5,5))
b5.grid(row=1,column=1)
b6 = Button(w, text=' ', width=4, height=2, font = font, command = lambda: buttonClick(b6,6))
b6.grid(row=1,column=2)
b7 = Button(w, text=' ', width=4, height=2, font = font, command = lambda: buttonClick(b7,7))
b7.grid(row=2,column=0)
b8 = Button(w, text=' ', width=4, height=2, font = font, command = lambda: buttonClick(b8,8))
b8.grid(row=2,column=1)
b9 = Button(w, text=' ', width=4, height=2, font = font, command = lambda: buttonClick(b9,9))
b9.grid(row=2,column=2)
在按钮的顶部创建一个 buttonClick()函数定义,并在它上面放置一个 pass(这样就不会出现函数为空的错误)。我们将在下一部分用相关代码填充函数定义。
让我们运行程序,我们得到这个(图 17-4 )。
图 17-4
九个盒子–已创建
这是我们目前掌握的情况。很好!
当按钮被点击时,在它上面绘图
现在让我们定义 buttonClick()函数。这应该在我们创建按钮的文本块之上(函数调用规则之前的函数定义)。
我们将在这个函数中访问全局变量 turn、state 和 game,所以让我们先加载它们。
#When a button is clicked
def buttonClick(b,n):
global turn,state,game
接下来,在绘制特定的盒子之前,让我们检查一下这个盒子当前是否是空的。如果它被占用了(一个玩家已经在上面画过了),我们就不应该再在上面画了,相反,你的游戏会弹出一个错误信息。
还有,检查一下游戏是否还是真的(没人赢,九试还没用完)。
if b['text'] == ' ' and game == True:
如果条件成立,那么检查谁正在玩。玩家“X”开始游戏,由于我们从 0 开始“回合”,每当轮到 X 时,“回合”的值将是一个偶数。你知道怎么检查偶数,对吧?去吧。
#hasn't been clicked on yet
if turn % 2 == 0:
所以,如果轮到 X,那么将按钮的文本改为“X”,将 turn 的值增加 1,并将 state[n–1]的值改为“X”。为什么是 n–1?列表的索引从 0 开始,按钮的编号从 1 开始,所以在“状态”中使用它之前,我们需要将值减 1。
#player X's turn
b['text'] = 'X'
turn += 1
state[n-1] = 'X'
当你在一个盒子上画图的时候,调用 winner_check()函数并发送“X”作为参数。我们将很快定义 winner_check()函数。如果你和我一起编码,现在,只需要在函数中输入 pass,这样你就不会因为没有定义它,而是调用它而得到错误。另外,在 buttonClick()函数之上创建 winner_check()函数,因为我们是从 buttonClick 调用的。
#winner check
winner_check('X')
好了,现在做完了,我们来检查一下 turn 是不是偶数,也就是说,是不是轮到 O 了。如果是,按照前面的方法做,但是只对“O”做。
elif turn % 2 == 1:
#player O's turn
b['text'] = 'O'
turn += 1
state[n-1] = 'O'
winner_check('O')
让我们运行一下我们目前所拥有的,看看我们是否能在我们的盒子上“画”出来(图 17-5 )。
图 17-5
盒子上的“画”
是的,我们可以!
最后,检查“else”条件。要么游戏已经结束,要么有人已经在盒子上画了,你不想重复。
在 messagebox 中,有一个 showinfo 方法,可以用来打印消息。让我们利用这一点。
如果“游戏”变量为假(游戏结束),打印“游戏结束!开始新游戏。如果盒子已经被画出,打印“这个盒子被占用了!”。
else:
if game == False:
messagebox.showinfo('showinfo','Game over! Start a new game.')
#because even when the game is over, the buttons will be occupied, so check for that first
elif b['text'] != ' ':
messagebox.showinfo('showinfo','This box is occupied!')
让我们检查错误框现在是否工作(图 17-6 )。
图 17-6
箱子被占了
我试着在一个被占用的盒子上画画,弹出了这条消息。太好了。另一个条件现在不相关,因为我们还没有检查赢家,所以游戏还不会“结束”。
看起来这个项目快结束了,对吗?我们已经提取了。我们甚至创建了 winner_check()函数来进行下一步工作。但是我们真的完成了 buttonClick()吗?没有。
我们还需要检查抽签情况!如果 turn 的值大于 8(玩家玩过九次),而“game”的值仍然为真怎么办?如果“game”仍然为真,这意味着还没有人赢,因为当我们调用 winner_check()函数时,如果我们发现有人赢了,我们会立即将“game”更改为 False。
所以,我们没有回合,游戏仍然正确的唯一原因是因为我们打成了平局。让我们打印该消息并结束游戏(将“game”改为 False)。
#game ended on draw
if turn > 8 and game == True:
messagebox.showinfo('showinfo','The match was a Draw')
game = False
buttonClick()到此为止!咻。那很大。
让我们运行程序,并检查“绘制”条件是否有效(图 17-7 )。
图 17-7
这场比赛是平局
是的,它工作了!但是我们需要 winner_check()来使一切正常工作。
接下来我们来看看 winner_check()。
检查每个回合中是否有玩家获胜
每当玩家玩游戏时,我们需要检查该玩家是否在该回合赢得了游戏。这个函数接受玩家(“X”或“O”)作为它的参数。
#Every time a button is clicked, check for win state
def winner_check(p):
我们还要导入全局变量 state、winner 和 game,因为我们需要它们。
global state,winner,game
现在,我们需要循环访问获胜者。因此,对于循环的每次迭代,“I”都有一个“赢”状态列表。
对于每次迭代,让我们检查 state[i[0]]、state[i[0]]、state[i[0]]是否持有相同的 player 值(“X”或“O”)。
例如,第一个内部列表是[0,1,2],所以我们检查 state[0],state[1]和 state[2],如果它们都包含字符串“X”,则 plaly er“X”获胜。如果他们都持有“O”,“O”获胜。就这样!
for i in winner:
if((state[i[0]] == p) and (state[i[1]] == p) and (state[i[2]] == p)):
如果条件成立,那么创建一个字符串,基本上就是“X 赢了!”或者“O won!”并用它创建一条消息。最后,将“game”的值改为 False。
string = '{} won!'.format(p)
messagebox.showinfo('showinfo',string)
game = False
让我们现在运行我们的程序,我们得到这个(图 17-8 )。
图 17-8
x 赢了!
哇哦!有效!
“游戏结束”条件有效吗?让我关闭当前消息框,并尝试通过单击在其中一个空框上绘图(图 17-9 )。
图 17-9
游戏结束!
看那个!我们的“游戏结束!”刚刚弹出的消息。我们的游戏运行完美!
新游戏按钮
我们为什么不在游戏中加入一个“新游戏”按钮呢?现在,我们的游戏结束后就暂停了。我们必须再次运行程序来开始新的游戏。如果我们有一个可以重置一切的按钮,那就太好了,不是吗?
就这么办吧。让我们先创建一个按钮。
new = Button(w,text='New Game',command=new_game)
new.grid(row=3,column=1)
点击此按钮将执行 new_game()函数。
现在,让我们在“新建”按钮上方创建 new_game()函数。
在我们定义函数之前,让我们创建一个所有按钮的列表。我们将需要它来遍历按钮并清除它们(这样我们就可以再次在它们上面绘图)。
#create a list of the buttons so we can change their text
boxes = [b1,b2,b3,b4,b5,b6,b7,b8,b9]
我们的 new_game()函数需要全局变量 state、game、turn 和 box。我们需要导入状态、游戏和回合,这样我们就可以将它们重置回初始值。
#New game
def new_game():
global state,game,turn,boxes
让我们重新设置回合、状态和游戏。
turn = 0
state = ['','','','','','','','','']
game = True
最后,让我们遍历“框”,将每个框的文本值改为一个空格。
for b in boxes:
b['text'] = ' '
我们的节目到此结束!我相信你已经这样做了,但是如果你忘记了,在程序的末尾添加一个 mainloop()。
w.mainloop()
让我们现在运行程序,我们得到这个(图 17-10 )。
图 17-10
新游戏按钮
我们现在有了“新游戏”按钮。试着测试一下。效果非常好!
你觉得创建这个游戏有趣吗?我知道我很乐意创造它,也很乐意教你如何创造它。摆弄游戏。更改字体、颜色等。祝你一切顺利!
整个程序
现在你已经学会了如何在 Tkinter 中创建一个井字棋,下面是整个程序的编写顺序。供你参考。
from tkinter import *
from tkinter import messagebox
w = Tk()
w.title('Tic Tac Toe')
turn = 0
state = ['','','','','','','','','']
winner = [[0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [2,4,6]];
game = True
#Every time a button is clicked, check for win state
def winner_check(p):
global state,winner,game
for i in winner:
if((state[i[0]] == p) and (state[i[1]] == p) and (state[i[2]] == p)):
string = '{} won!'.format(p)
messagebox.showinfo('showinfo',string)
game = False
#When a button is clicked
def buttonClick(b,n):
global turn,state,game
if b['text'] == ' ' and game == True:
#hasn't been clicked on yet
if turn % 2 == 0:
#player X's turn
b['text'] = 'X'
turn += 1
state[n-1] = 'X'
#winner check
winner_check('X')
elif turn % 2 == 1:
#player O's turn
b['text'] = 'O'
turn += 1
state[n-1] = 'O'
player = 'X'
winner_check('O')
else:
if game == False:
messagebox.showinfo('showinfo','Game over! Start a new game.')
#because even when the game is over, the buttons will be occupied, so check for that first
elif b['text'] != ' ':
messagebox.showinfo('showinfo','This box is occupied!')
#game ended on draw
if turn > 8 and game == True:
messagebox.showinfo('showinfo','The match was a Draw')
game = False
font = ('Helvetica',20,'bold')
#9 buttons
b1 = Button(w, text=' ', width=4, height=2, font = font, command = lambda: buttonClick(b1,1))
b1.grid(row=0,column=0)
b2 = Button(w, text=' ', width=4, height=2, font = font, command = lambda: buttonClick(b2,2))
b2.grid(row=0,column=1)
b3 = Button(w, text=' ', width=4, height=2, font = font, command = lambda: buttonClick(b3,3))
b3.grid(row=0,column=2)
b4 = Button(w, text=' ', width=4, height=2, font = font, command = lambda: buttonClick(b4,4))
b4.grid(row=1,column=0)
b5 = Button(w, text=' ', width=4, height=2, font = font, command = lambda: buttonClick(b5,5))
b5.grid(row=1,column=1)
b6 = Button(w, text=' ', width=4, height=2, font = font, command = lambda: buttonClick(b6,6))
b6.grid(row=1,column=2)
b7 = Button(w, text=' ', width=4, height=2, font = font, command = lambda: buttonClick(b7,7))
b7.grid(row=2,column=0)
b8 = Button(w, text=' ', width=4, height=2, font = font, command = lambda: buttonClick(b8,8))
b8.grid(row=2,column=1)
b9 = Button(w, text=' ', width=4, height=2, font = font, command = lambda: buttonClick(b9,9))
b9.grid(row=2,column=2)
#create a list of the buttons so we can change their text
boxes = [b1,b2,b3,b4,b5,b6,b7,b8,b9]
#New game
def new_game():
global state,game,turn,boxes
turn = 0
state = ['','','','','','','','','']
game = True
for b in boxes:
b['text'] = ' '
new = Button(w,text='New Game',command=new_game)
new.grid(row=3,column=1)
w.mainloop()
摘要
在本章中,我们从 Python 中的注释以及如何创建单行和多行注释开始。然后我们继续讨论变量,如何创建它们,它们的命名约定,以及可以在其中存储什么。然后我们看了 Python 编程语言中大量可用的数据类型以及如何使用它们。然后我们看了 Python 中的类型检查,最后我们看了如何在 Python 中获取输入并在输出中显示它们。
在下一章,让我们深入研究字符串,如何创建和使用它们,以及 Python 为您提供的各种预定义的字符串方法。
十八、项目:使用 Tkinter 绘制应用
在前一章中,我们学习了如何用 Tkinter 创建井字棋应用。我们还学习了所有关于事件的知识,以及如何使用它们让我们的应用响应外部事件(鼠标点击、键盘按键等)。).
在这一章中,我们将学习所有关于使用“画布”在你的 Tkinter 屏幕上“绘画”的知识,并使用它来制作一个绘画应用。你可以用笔画画,画圆形/椭圆形、直线和正方形/长方形。您还可以更改笔的大小以及形状的轮廓颜色和填充颜色。这是一个简单,但完整的应用!
绘画应用–解释
我们的绘画应用将会非常棒!你将能够徒手画直线、正方形、长方形、椭圆形和圆形。你也可以从数百种不同的颜色中选择。酷吧?
一旦我们完成了,它看起来就像图 18-1 一样。
图 18-1
最终应用
我不是艺术家,所以请原谅我的基本图纸,但你可以看到这个应用是多么强大,对不对?最棒的是,这只是一个起点。您可以扩展此应用,添加更多功能,并将其变成您想要的任何东西。
分享给你的朋友,有绘画比赛,或只是玩得开心!
开始
先从导入 Tkinter 开始。像往常一样,让我们导入所有内容,但是这样做只会导入“外部”类。它不会导入内部的,比如颜色选择器。我们需要颜色选择器来为我们的应用创建调色板。那么,让我们也导入它。
from tkinter import *
from tkinter import colorchooser
现在,让我们创建并初始化我们的变量。要在屏幕上绘图,您需要坐标,即鼠标指针在屏幕上点击位置的 x 和 y 点。让我们创建 x 和 y 变量,并在开始时将它们分别赋值为 0。
我现在用了一种新的分配方式。让事情变得简单,不是吗?
x, y = 0,0
接下来,让我们创建一个变量“color ”,并从一开始就把它设置为 None(没有值)。你也可以让它成为一个空字符串。这个变量将在将来保存我们的形状的填充颜色。我们还需要一种颜色作为我们的“笔”或形状的轮廓,所以让我们创建一个可变的“轮廓”,默认为黑色。我们还需要一支笔的大小。默认情况下会是 1。
color = None
outline = 'black'
sizeVal = 1
设置屏幕
现在,让我们设置我们的屏幕。我们将默认设置屏幕的状态为“缩放”,因此它会扩展到全屏。此外,我们将配置我们的行和列,使第一个单元格(行 0 和列 0)扩展到屏幕的整个宽度和高度。我们可以将画布放在这个单元格中,这样它也可以扩展到全屏。
w = Tk()
w.title('Paint Application')
w.state('zoomed')
w.rowconfigure(0,weight=1)
w.columnconfigure(0,weight=1)
我们将权重设为 1,让程序知道这个特定的行和列应该扩展到它的最大容量。
让我们运行我们的程序,我们得到这个(图 18-2 )。
图 18-2
我们的 Tkinter 屏幕
太好了。
创建画布
现在,让我们创建画布。我们需要使用 Canvas 方法来做到这一点,并将其放置在窗口“w”中。让我们也使我们的画布的背景默认为“白色”。
#create a canvas
canvas = Canvas(w, background="white")
接下来,我将把画布放在第一行和第一列(0),并使它在所有方向(北、南、东、西)都具有粘性,这样它就可以向所有方向扩展并占据整个空间(这是我们目前的整个屏幕)。
canvas.grid(row=0,column=0,sticky="NSEW")
让我们现在运行程序,我们得到这个(图 18-3 )。
图 18-3
帆布
完美!我们现在有了我们的白色画布。
创建您的第一个菜单(形状)
如果你看了完整的应用,你会注意到我们有多个菜单可供选择。第一个是形状菜单。你可以选择用钢笔还是画线、正方形或圆形。让我们现在创建菜单。
你已经知道如何创建菜单。让我们创建一个包含所有菜单的主菜单。我们的“绘图选项”菜单将成为我们“主”菜单的第一个子菜单。让我们添加一个级联,并标记它。
main = Menu(w)
menu1 = Menu(main)
main.add_cascade(label='Draw Options',menu = menu1)
最后,让我们添加四个命令,“钢笔”,“直线”,“正方形”和“圆形”。但是我们需要将选择值发送给“select”函数,该函数将依次调用相关的函数来进行相应的绘制。让我们用λ来实现。我们要给我们的选项编号,钢笔是 1,线条是 2,正方形是 3,圆形是 4。
menu1.add_command(label='Pen', command=lambda: select(1))
menu1.add_command(label='Line', command=lambda: select(2))
menu1.add_command(label='Square', command=lambda: select(3))
menu1.add_command(label='Circle', command=lambda: select(4))
最后,让我们配置窗口的“主”菜单。将来,这一行应该出现在我们创建了所有四个菜单之后。
w.config(menu=main)
如果你现在运行你的程序,并尝试点击菜单项,你会得到一个错误,因为你的“选择”功能还没有定义,但你仍然会看到你的菜单,就像这样(图 18-4 )。
图 18-4
第一个菜单(绘图选项)
哇哦!第一步成功了!
让我们的抽奖选项发挥作用吧!
现在我们有了绘图选项菜单,让我们让它工作。让我们首先创建“选择”函数,它将画布与相关的鼠标点击绑定在一起。在菜单上创建这个函数(函数调用)。我们需要两种绑定。
对于手绘,我们需要一个 bind,每当鼠标左键在屏幕上点击和拖动时,它就会画一条线。所以,我们基本上会在每 2 分钟的点之间得到一些细线,所以基本上几百条连接在一起的细线就组成了我们的手绘。
然后,我们需要一个 bind,当鼠标左键在屏幕上点击和拖动后释放时,它可以绘制直线、正方形或圆形。因此,结果将是从点击点到释放点的直线、正方形或圆形。
让我们现在做那件事。让我们以“选项”的形式接收我们的号码。如果 options 为 1,则取消绑定,因此,如果我们之前选择了其他选项,现在它将被取消选择,并且在我们释放笔之后,我们将不会获得形状或线条。然后,我们绑定,调用 draw_line 函数。
def select(options):
if options == 1:
#selected Pen, create bind
canvas.unbind("<ButtonRelease-1>")
canvas.bind('<B1-Motion>',draw_line)
类似地,对于 2,解除绑定使笔不再活动,绑定并调用 draw_line 函数。
if options == 2:
#selected line, create bind
canvas.unbind("<B1-Motion>") #so pen is no longer active
canvas.bind('<ButtonRelease-1>',draw_line)
对于 3,调用 draw_square 函数。
elif options == 3:
#selected square, create bind
canvas.unbind("<B1-Motion>")
canvas.bind('<ButtonRelease-1>',draw_square)
对于 4,调用 draw_circle 函数。
elif options == 4:
#selected circle, create bind
canvas.unbind("<B1-Motion>")
canvas.bind('<ButtonRelease-1>',draw_circle)
获取鼠标位置
在创建 draw_line 函数之前,我们需要获取鼠标位置。如您所知,我们可以使用我们的“事件”来实现这一点。因此,让我们在我们的函数之外创建另一个绑定(在菜单的正上方和函数定义的正下方),将任何鼠标左键点击绑定到画布。
所以,每次你的用户点击画布时,我们都要记录下背景中画布的 x 和 y 位置。
我们不会绘制任何东西,直到用户选择了一个绘制选项,但是我们仍然要做好准备,好吗?
canvas.bind('<Button-1>',position)
现在,定义绑定上面的函数。接收函数定义中的“事件”。让我们也加载全局 x 和 y 值,并将 event.x 和 event.y 值(鼠标单击的 x 和 y 坐标位置)赋给 x 和 y 全局变量。
在画布上单击鼠标左键,获取鼠标的当前位置。
def position(event):
global x,y
x,y = event.x,event.y
就这样!你可以打印出 x 和 y,看看这个函数的运行。让这成为我们的小活动,好吗?
让我们画出我们的线
现在,让我们创建一个函数,它将为我们的手绘绘制迷你线和直线。我们需要什么?
canvas 中有一个 create_line 函数,可以用来,是的,你猜对了,画直线!你只需要给出起点和终点的坐标点。您还可以指定“填充”,这实际上是线条的颜色。
我们将使用“轮廓”颜色,因为我们希望线条颜色和形状轮廓颜色一致。您也可以指定线条的宽度。让我们给 sizeVal 作为这个属性的值。
但是你需要小心你提到的坐标值。先提起点的 x,y 坐标,再提终点的 x,y 坐标。更重要的是,要在一个元组中提到所有四个值,否则会出现错误。
def draw_line(event):
让我们加载我们的 x 和 y 值,这是鼠标第一次点击的点,我们使用 position()函数不断计算。让我们也加载 sizeVal,它目前为 1。一旦我们写了让用户手动改变线条宽度的代码行,它就会自动更新。
global x,y,sizeVal
现在,开始的 x 和 y 位置是包含鼠标单击点的 x 和 y 位置(position()函数)。结束 x 和 y 位置是事件的 x 和 y 位置。
在手绘的情况下,每次拖动鼠标(同时仍然按下鼠标左键),我们都会为每一分钟的变化获得一个新的事件,以及新的 x 和 y 位置。
对于绘制直线,终点是释放鼠标按钮的时间。
canvas.create_line((x,y,event.x,event.y),fill=outline, width = sizeVal)
最后,让我们用事件的 x 和 y 值来更新 x 和 y 值。手绘的时候特别需要这个,这样就可以重新开始了。
x,y = event.x,event.y
让我们现在运行我们的程序。
当我们试图像这样在屏幕上画画时,什么也没有发生。为什么呢?我们还没有启动任何选项。但是,如果我选择钢笔或线条(从菜单中),我可以在画布上绘图(图 18-5 )。
图 18-5
徒手和直线
正方形和长方形!
现在让我们画正方形和长方形。过程类似。在 canvas 中有一个 create_rectangle 方法。再次给出元组内的开始和结束坐标。在这种情况下,你可以提到两种颜色,轮廓和填充颜色,最后是形状的宽度。
然后,让我们将当前事件的 x 和 y 值(鼠标释放)赋给第一个 x 和 y 值(鼠标左键单击)。
def draw_square(event):
global x,y,sizeVal
canvas.create_rectangle((x,y,event.x,event.y), outline=outline, fill=color, width = sizeVal)
x,y = event.x,event.y
就这样!让我们现在运行我们的程序。选择“正方形”,按住鼠标按钮,将其拖到您想要的点,然后释放按钮。你会得到一个正方形或长方形。试试看!
我就是这么做的(图 18-6 )。😛
图 18-6
正方形和长方形
漂亮的正方形和长方形!
圆形和椭圆形!
最后,让我们画圆和椭圆。还有一种方法叫做 create_oval。完美的椭圆形是圆形,对吗?你也需要给出这个方法的起点和终点。
您的起点是您按下鼠标按钮的时候,终点是您最后释放鼠标按钮的点的 x 和 y 值(鼠标释放事件)。
def draw_circle(event):
global x,y,sizeVal
canvas.create_oval((x,y,event.x,event.y), outline=outline, fill=color, width= sizeVal)
x,y = event.x,event.y
让我们运行程序,我们得到这个(图 18-7 )。
图 18-7
环
很好!我们已经完成了所有的绘图功能。我们快到了!
选择尺寸!
现在,让我们进入我们节目的第二个菜单。到目前为止,我们的线条和轮廓的宽度都太窄了。如果我们希望它们更厚呢?为此我们也需要选择。让我们创造它们吧!我将创建从 1、5、10 到 30 的尺码。1 是我们设置的默认值。
让我们为尺寸创建一个新的子菜单 menu2。将它放在 menu1 代码之后,但在菜单配置代码行之前。每个选项都将是一个大小,我将为每个选项的点击调用 changeSize 函数。我们将把大小作为参数发送给这个函数。
menu2 = Menu(main)
main.add_cascade(label='Select Size', menu = menu2)
menu2.add_command(label='1', command=lambda: changeSize(1))
menu2.add_command(label='5', command=lambda: changeSize(5))
menu2.add_command(label='10', command=lambda: changeSize(10))
menu2.add_command(label='15', command=lambda: changeSize(15))
menu2.add_command(label='20', command=lambda: changeSize(20))
menu2.add_command(label='25', command=lambda: changeSize(25))
menu2.add_command(label='30', command=lambda: changeSize(30))
现在,定义函数来改变大小。您可以将该函数放在 select()函数之后,或者您想要的任何地方,只要它在 menu2 的代码行(函数调用)之上。
这是一个非常简单的过程。让我们接收我们的大小,加载全局 sizeVal,并将我们的大小赋给 sizeVal。就这样!因为 sizeVal 是全局的,并且被加载到我们所有的绘图函数中,所以一旦我们改变了大小,下一次我们绘图时,新的大小将反映在该绘图中。
def changeSize(size):
global sizeVal
sizeVal = size
让我们检查一下这个是否有效!我准备把大小改成 15 后画一堆线(图 18-8 )。
图 18-8
更改轮廓的宽度
这些线条很粗。😄
很多很多颜色!
现在,让我们创建第三个菜单,让我们改变轮廓和填充颜色的图纸。
让我们创建一个 menu3,它只包含两个选项,一个用于更改线条颜色,另一个用于更改填充颜色,每个选项调用各自的函数。
menu3 = Menu(main)
main.add_cascade(label = 'Choose Color', menu = menu3)
menu3.add_command(label='Line Color', command = set_line_color)
menu3.add_command(label='Fill Color', command = set_fill_color)
现在,让我们定义这些函数。我们将使用我们的颜色选择器来创建我们的调色板。colorchooser 中有一个 askcolor 方法,可以在我们需要时打开调色板(在这种情况下,当单击“线条颜色”选项时)。这将在新窗口中打开。让我们为这个窗口设置一个标题:选择颜色。
def set_line_color():
global outline
getColor = colorchooser.askcolor(title="Choose color")
现在,你不能就这样使用 getColor。当我们选择一种颜色时,比如说红色,这是它在 getColor 中注册的格式:
((255.99609375, 0.0, 0.0), '#ff0000')
元组中的第一个值包含另一个保存 rgb 颜色值的元组(我们颜色的红、绿、蓝阴影)。元组中的第二个值包含我们刚刚选择的颜色的十六进制值。它们是一样的,你可以把它写成“红色”。这些只是你提及颜色的不同形式。你真的不需要了解它们或者记住它们。只要知道每个阴影有十六进制和 rgb 值,你可以使用,你的计算机可以识别。
现在,我们不能使用整个元组。我们只需要它的一个值。让我们检索第二个值并使用它,好吗?
outline = getColor[1]
现在,每次我们改变“线条颜色”,“轮廓”的值也会改变,这将反映在我们的下一个绘图中。
现在,让我们做同样的填充颜色。
def set_fill_color():
global color
getColor = colorchooser.askcolor(title="Choose color")
color = getColor[1]
这就是我们的颜色!让我们检查它是否工作,好吗?让我们点击“线条颜色”,看看调色板是否打开(图 18-9 )。
图 18-9
颜色!
有效!
现在,让我们选择我们的颜色(图 18-10 )。
图 18-10
最终应用,完成!
我们所有的颜色都非常完美!
我已经画完了!
好了,我们有了一个小小的绘画应用。我们已经心满意足了!但是如果我们想重新开始呢?我们需要一个选项来清空画布。让我们来创造吧!
首先,菜单。
menu4 = Menu(main)
main.add_cascade(label = 'Clear', menu = menu4)
menu4.add_command(label = 'Clear', command = clear_screen)
现在,clear_screen()函数。我们只需要一行代码:canvas.delete('all ')。这将删除画布上的所有内容。
def clear_screen():
canvas.delete('all')
这就是选项出现的方式(图 18-11 )。
图 18-11
清楚的
画一些东西,选择清除选项,看到一切都消失了!不过在你做之前先截图吧!
我们已经完成了我们的绘画应用!😮 最后,如果您还没有编写它,请编写主循环代码行,我们就完成了。
w.mainloop()
整个程序
现在,整个程序按照它应该被创建的顺序:
from tkinter import *
from tkinter import colorchooser
x, y = 0,0
color = None
outline = 'black'
sizeVal = 1
w = Tk()
w.title('Paint App')
w.state('zoomed')
w.rowconfigure(0,weight=1)
w.columnconfigure(0,weight=1)
#create a canvas
canvas = Canvas(w, background="white")
canvas.grid(row=0,column=0,sticky="NSEW")
def draw_line(event):
global x,y,sizeVal
canvas.create_line((x,y,event.x,event.y),fill=outline, width = sizeVal)
x,y = event.x,event.y
def draw_square(event):
global x,y,sizeVal
canvas.create_rectangle((x,y,event.x,event.y), outline=outline, fill=color, width = sizeVal)
x,y = event.x,event.y
def draw_circle(event):
global x,y,sizeVal
canvas.create_oval((x,y,event.x,event.y), outline=outline, fill=color, width= sizeVal)
x,y = event.x,event.y
def select(options):
if options == 1:
#selected Pen, create bind
canvas.unbind("<ButtonRelease-1>")
canvas.bind('<B1-Motion>',draw_line)
if options == 2:
#selected line, create bind
canvas.unbind("<B1-Motion>") #so pen is no longer active
canvas.bind('<ButtonRelease-1>',draw_line)
elif options == 3:
#selected square, create bind
canvas.unbind("<B1-Motion>")
canvas.bind('<ButtonRelease-1>',draw_square)
elif options == 4:
#selected circle, create bind
canvas.unbind("<B1-Motion>")
canvas.bind('<ButtonRelease-1>',draw_circle)
def position(event):
global x,y
x,y = event.x,event.y
def changeSize(size):
global sizeVal
sizeVal = size
def set_line_color():
global outline
getColor = colorchooser.askcolor(title="Choose color")
outline = getColor[1]
def set_fill_color():
global color
getColor = colorchooser.askcolor(title="Choose color")
color = getColor[1]
def clear_screen():
canvas.delete('all')
canvas.bind('<Button-1>',position)
#options
main = Menu(w)
menu1 = Menu(main)
main.add_cascade(label='Draw Options',menu = menu1)
menu1.add_command(label='Pen', command=lambda: select(1))
menu1.add_command(label='Line', command=lambda: select(2))
menu1.add_command(label='Square', command=lambda: select(3))
menu1.add_command(label='Circle', command=lambda: select(4))
menu2 = Menu(main)
main.add_cascade(label='Select Size', menu = menu2)
menu2.add_command(label='1', command=lambda: changeSize(1))
menu2.add_command(label='5', command=lambda: changeSize(5))
menu2.add_command(label='10', command=lambda: changeSize(10))
menu2.add_command(label='15', command=lambda: changeSize(15))
menu2.add_command(label='20', command=lambda: changeSize(20))
menu2.add_command(label='25', command=lambda: changeSize(25))
menu2.add_command(label='30', command=lambda: changeSize(30))
menu3 = Menu(main)
main.add_cascade(label = 'Choose Color', menu = menu3)
menu3.add_command(label='Line Color', command = set_line_color)
menu3.add_command(label='Fill Color', command = set_fill_color)
menu4 = Menu(main)
main.add_cascade(label = 'Clear', menu = menu4)
menu4.add_command(label = 'Clear', command = clear_screen)
w.config(menu=main)
w.mainloop()
摘要
在这一章中,我们学习了如何使用“画布”在屏幕上“绘画”,并用它来制作一个绘画应用。我们用钢笔画画,画了圆形/椭圆形、直线和正方形/长方形。我们还改变了你的笔的大小和形状的轮廓颜色和填充颜色。
在下一章,让我们回到我们最初的包,turtle
包。让我们用turtle
、记分牌等等创建一个成熟的蛇应用。这将是一次有趣的旅程。系好安全带。