1. Tkinter库的简介:
Tkinter是Tk GUI工具包的Python绑定包。它是Tk GUI工具包的标准Python接口,并且是Python的业界标准GUI工具包。Tkinter同时也包含在Python的Linux、Microsoft Windows和Mac OS X标准库中。Tkinter的名字来自Tk interface。
- 若在命令行执行
python -m tkinter
,应会弹出一个简单的 Tk 界面窗口, 表明 tkinter 包已安装完成,还会显示当前安装的 Tcl/Tk 版本,以便阅读对应版本的 Tcl/Tk 文档。Python=3.8.10- Tkinter 支持众多的 Tcl/Tk 版本,带或不带多线程版本均可。官方的 Python 二进制版本捆绑了 Tcl/Tk 8.6多线程版本。关于可支持版本的更多信息,请参阅 _tkinter 模块的源代码。
- Tkinter 并不只是做了简单的封装,而是增加了相当多的代码逻辑,让使用体验更具 Python 风格(pythonic)。本文将集中介绍这些增加和变化部分,关于未改动部分的细节,请参考 Tcl/Tk 官方文档。
tkinter 的介绍 : http://en.wikipedia.org/wiki/tkinter
TkDocs:TkDocs Home
tkinter命令:https://www.tcl.tk/man/tcl8.6/TkCmd/contents.html
1.1 GUI编程
- 如果有过 C 或者 Python等语⾔的编程经验,开发GUI界面手到擒来,在 Python ⾥⾯这种⼯具并不多,这也是和 Python 是⼀个脚本语⾔有关。
- 要说 tkinter,先说 tk,它原本是 Tcl 编程语⾔的界⾯库, 后来开发出了Python 接⼝,Tkinter 在 Python2 和 Python3 中的变化较⼤,在 Python2 中叫做 Tkinter,在 Python3 中叫做 tkinter,⽽且在导⼊类库的时候也有些许的变化,请读者稍加注意。
1.2 Tkinter的定位
- 掌握⼀⻔ Python 的界 ⾯编程也很有意思。有时候我们需要⼀些界⾯程序,但是⼜不想⽤那些庞⼤ ⽽繁杂的类库的时候,tkinter 的优势就显现出来了,那就是简洁简单。💕💕💕
- 我感觉 tkinter 是⼀个还不错的界⾯库,但是我感觉 Python 并不是特别擅⻓复杂的应⽤(🐧🐧🐧🐧麻雀虽小五脏俱全🐧🐧🐧),⽽且我感觉⽐如 wxPython 或者 pyQt 等更胜⼀筹,但是不管怎么样,我们可以通过Python⾃带的tkinter,进⼊GUI编程的世界。🎉🎉🎉
- 鹏鹏只用两周就完成项目啦!相信接下来可以帮助你😘😘😘
2. Hello word! 程序起飞
2.1 第⼀个程序
我们新建⼀个⽆格式的⽂本⽂件,通常是.txt ⽂件,然后修改名称为 Hello_World.py
,然
后⽤⽂本编辑器打开,然后写三⾏代码。
#导入Tkinter模块,并用别名tk引用它。 import tkinter as tk #定义一个函数say_hello,当按钮被点击时,这个函数会被调用。 这个函数会更新标签(label)的文本为"Hello World!"。 def say_hello(): label.config(text="Hello World!") #创建一个顶级窗口(root window),这是整个GUI程序的基础。 root = tk.Tk() #创建一个标签(Label),设置其初始文本为"Click the button to say hello!",并将其添加到根窗口中。 label = tk.Label(root, text="Click the button to say hello!") label.pack() #pack()方法用于将控件放置在父容器中,并自动调整它们的大小和位置。 #创建一个按钮(Button),设置其文本为"SAY Hello",并将其命令属性设置为say_hello函数。这意味着当用户点击此按钮时,say_hello函数将被调用。 button = tk.Button(root, text="Say Hello", command=say_hello) button.pack() #最后,进入主循环。在此过程中,程序会持续监听用户的操作,如点击按钮等,并作出相应的响应。 root.mainloop()
总结起来,这个程序会在屏幕上显示一个窗口,其中包含一个标签和一个按钮。当用户点击Say Hello
按钮时,标签上的文本会变为"Hello World!"。
2.2 字体颜色主题
要设置不同的字体颜色主题,可以使用font属性为标签和按钮设置自定义的字体颜色。下面是一个示例,演示如何将文本颜色设置为红色:
import tkinter as tk def say_hello(): label.config(text="Hello World!") root = tk.Tk() # 创建一个自定义的字体对象,设置其大小 custom_font = ('Arial', 14, 'bold') #设置背景填充颜色 label = tk.Label(root, text="Click the button to say hello!", font=custom_font, bg = 'red') label.pack() button = tk.Button(root, text="Say Hello", command=say_hello, font=custom_font, bg = 'red') button.pack() root.mainloop()
在这个例子中,我们创建了一个名为custom_font的自定义字体对象,设置了字体名称(‘Arial’)、大小(14)、粗体样式(‘bold’)和背景颜色(‘red’)。然后,我们将这个自定义字体应用到标签和按钮上。
字体名称 | 大小 | 粗体样式 | 颜色 |
Arial 、 宋体 |
1~100 |
bold |
red 、green 、blue |
您也可以根据需要更改字体颜色和其他属性。例如,您可以创建多个不同的自定义字体对象,用于表示不同的颜色主题,并在程序运行时动态地切换它们。
3. 组件讲解
3.1 tkinter 的核⼼组件
- 在 tkinter 中,有 21 个核⼼组件,它们提供 了GUI开发的完整功能,因为使⽤频率较⾼。
- 这 21 个核⼼组件是 : Label、Button、Entry、Menu、 Radiobutton 、Checkbutton、Text、Image、Canvas、Frame、LabelFrame、Toplevel、 Listbox、Menubutton、Message、OptionMenu、PaneWindow 、 Scale 、Scrollbar 、Spinbox、Bitmap。
3.2 组件的使⽤
- 各个组件都有相应的类,我们可以通过⾯向对象的⽅式 去使⽤它们。
- 这些组件的使⽤也很相似,在实例化这些组件的时候, 第⼀个参数都是⽗窗⼝或者⽗组件,后⾯跟着的就是该组 件的⼀些属性,⽐如上⾯我们学到的 Label 的 text属性和 background 属性。
- 多个组件的位置控制⽅式也很相似,我们可以⽤ pack ⽅法来进⾏简单的布局,具体的个例我们后⾯再说。
- 组件也会有些⽅法是共⽤的,⽐如 configure ⽅法来设置属性等等。
3.3 标签Label
标签就是输出显示信息可动态也可静态✨✨✨
3.3.1 标签显示内容
- 所谓 标签 ,就是贴在物品前⾯的⼀个简短的说明, 它⽤于说明⼀些⽂字信息。
- 标签可以说是最简单的窗⼝组件了,它不需要执⾏任何功能,只是⽤来显示信息。
- 下⾯是代码示例:
from tkinter import * root = Tk() root.wm_title("这是⼀个Tkinter程序窗⼝标题") wl= Label(root, text = "欢迎来到tkinter") wl.pack() root.mainloop()
代码分析
- 这⾥的 Label 是⼀个类,可以在 init.py ⽂件⾥查看 相应的源代码。
- wl 是⼀个 Label 的实例,它有⼀个 text 属性 ,⽤来指定 它的⽂本内容。
- ⼤家可以看到它的标准属性,⽐如有 background , font , bitmap , padx ,relief 等等, 还有 underline 等等。
- 该类有个 pack ⽅法,没错,这个 pack ⽅法我们后⾯会 讲,⼤家可以理解为它的作⽤就是找个合适的位置进⾏放置即可,即 pack() 之后就选定位置放上去了。
- 这⾥的初始化的时候,需要先指定 root,是说 wl 这个组 件是在 root 这个窗⼝的,不是属于别的窗⼝的,以后我们 有了多窗⼝的应⽤程序,不会混淆。
Widget 类
- 我们上⾯发现,Label 类是继承⾃ Widget 类的,并且⾃ ⼰只有⼀个⽅法,就是⽤来
初始化⾃身的,那么要想彻底 理解 Label,就必须彻底理解 Widget。 - 我们找到了 Widget 的代码,发现它更加短⼩,甚⾄连个 ⽅法都没有,下⾯是
Widget 的截图: - 通过这个注释,我们可以理解如下:它是⼀个可以指定 位置并且可以使⽤
pack , place 和 grid 来布局管理的窗⼝ 组件。 - 然后我们找到 BaseWidget ,发现它的代码也很短⼩,代 码截图如下:
- 该类继承⾃ Misc ,包含了四个函数,函数的功能通过名 字就可以知道了。⾄于Misc类,我们后⾯会讲解的。Misc其实是英⽂ Miscellaneous 的前四个字⺟,杂项、混合体、⼤杂烩的意思。在软件⾥经常可以看到与misc相关的⽂件或函数名,使⽤misc来命名主要是表示该⽂件⽬前还没归类好,不知道将它归到哪个⽅⾯或者放置在哪个地⽅⽐较好,所以暂时⽤misc。
3.3.2 多标签的应⽤程序
- 如果我们想要多个标签,该怎么办呢?
- 很简单,只需要声明多个标签的实例,然后分别 pack 到 窗⼝上即可。
from tkinter import * root = Tk() root.wm_title("这是⼀个Tkinter程序窗⼝标题") w1 = Label(root, text = "欢迎来到tkinter教程!", background = "white") w2 = Label(root, text = "好好学习,天天向上!", background = "green") w3 = Label(root, text = "轻松愉快,就选Python!", background = "red") w1.pack() w2.pack() w3.pack() root.mainloop()
3.3.3 总结
- 我们这⼀节学习了 Label 这个组件,它的创建使⽤ Label 这个类,实例化的时候⾸
先要指定它的⽗窗⼝,然后就是 ⽤字典的⽅式设置⾃⼰的⼀些特征,这些特征上⾯
都列出 了,但是没有实例演示。 - 然后使⽤ pack() ⽅法布局上去,关于布局,我们后⾯会详 细讲,这⾥我们统⼀使⽤
pack() 就可以了。
3.4 按钮Button
- 按钮也是⾮常重要的组件, 按钮的重要性在于它可以执⾏相应 的功能。
- 按钮的英⽂表述是 button ,它随处可⻅,当我们单 击的时候,它可以执⾏相应的功能。
- 按钮在 tkinter 中有⼀个类专⻔负责它,叫做 Button ,该类也⾮常简短。
3.4.1 按钮与功能的绑定
- 上⽂说到,按钮可以执⾏相应的功能,这⾥的功能我们 可以理解为⼀个函数,或者这些功能通过相应的函数去实 现。
- 绑定⽅式通常有如下⼏种:第⼀种,在按钮组件被声明 的时候⽤ command 属性声明, command 属性接受⼀个函数名, 注意函数名不要加双引号。第⼆种,使⽤ bind⽅法,该⽅ 法是 Misc 这个类的⼀个⽅法,下⾯我们仔细讲解。
3.4.2 第⼀种⽅法绑定事件
我们要完成的功能是我们按下这个按钮的时候,就会在 窗⼝上增加⼀个 Label,它显示“我爱 python”。
from tkinter import * def myLabel(): global py, i s = Label(py, text = "轻松愉快,就选Python! 重要的事情说 %d 遍!" % i) s.pack() i+=1 # 每次点击后,i的值会⾃增1 py = Tk() i = 1 # 全局变量i 初始化为 1 b = Button(py, text = "Python", command = myLabel) b.pack() py.mainloop()
运⾏结果:(⼀开始是只有⼀个按钮的,点击⼀下,就会在这个窗⼝上多⼀个标签,下⾯是我点击了三次之后的截图):
代码解读
- 其实很简单,这⾥只需要注意 command 属性后⾯不要加任何的标点符号。
- 这⾥的 myLabel 函数中,使⽤了全局的 py 和 i 变量,需要注意。
3.4.3 第⼆种⽅式绑定事件
下⾯使⽤第⼆种⽅式来绑定事件,这个事件完成同样的功能。
from tkinter import * # method 1 # def myLabel(): # method 2 def myLabel(event): global py, i s = Label(py, text = "轻松愉快,就选Python! 重要的事情说 %d 遍!" % i) s.pack() i+=1 # 每次点击后,i的值会⾃增1 py = Tk() i = 1 # 全局变量i 初始化为 1 # method 1 # b = Button(py, text = "Python", command = myLabel) # method 2 b = Button(py, text = "Python") # command = myLabel ⽊有了 b.bind("<Button-1>", myLabel) # 多了这⼀句 b.pack() py.mainloop()
和第⼀种⽅法的结果⼀样
代码解读:
- bind这个⽅法是在 Misc 类中的,可以接受三个参数,但是本例中我们只传递了两个参数。
- 第⼀个参数可能对刚使⽤它的⼈来说有点复杂,常⻅的⿏标左键单击如下: <Button1> (也就是上⾯的代码⽤到的),或者 等。
- 第⼆个参数可以是⼀个函数名,记住,不要加任何的标 点符号,否则运⾏时会报错的。
- 使⽤ bind 函数的时候,第⼆个参数是⼀个函数名,该函数必须接受⼀个参数,即表示该事件。 这个参数通常⽤ event 来表示,如果我们调⽤的函数不接 受任何参数,则会报错如下 : TypeError: myLabel() takes no arguments
(1 given)
bind的第⼀个参数是⽤字符串包含的事件类型,它采⽤的描述⽅式是: <MODIFIER-MODIFIER-TYPE-DETAIL>
- 这⾥的 MODIFIER 即键盘或者⿏标修饰符,它的全部取值如下: Control, Mod2,M2, Shift, Mod3, M3, Lock, Mod4, M4, Button1, B1, Mod5, M5, Button2, B2, Meta, M, Button3,B3, Alt, Button4, B4, Double,Button5, B5 Triple , Mod1, M1
- TYPE 表示类型,它的全部取值如下: Activate, Enter, Map, ButtonPress,Button, Expose, Motion, ButtonRelease, FocusIn, MouseWheel,Circulate, FocusOut, Property, Colormap, Gravity Reparent, Configure,KeyPress, Key, Unmap, Deactivate, KeyRelease Visibility, Destroy,Leave
- DETAIL 表示细节,其实也就是对第⼆个参数的⼀些辅助说明。
3.4.4 设置属性
下⾯我们可以设置⼀些属性,这些东⻄随着⼤量的代码示例,⼤家会接触不少的。
⽐如我们可以设置背景⾊,这⾥直接设置属性,代码截 图:
from tkinter import * py = Tk() b1 = Button(py, text = "tkinter教程") b1["width"] = 20 b1["height"] = 6 b1.pack() b2 = Button(py, text = "Python学院") b2["width"] = 40 b2["background"] = "white" b2.pack() py.mainloop()
3.4.5 总结
- 关于按钮,我们重点理解的就是它如何和事件进⾏绑定的。
- 当然,使⽤⼀些其他属性来美化按钮也很重要。
- 下⾯要讲⼀讲布局⽅⾯的东⻄了。
3.5 输⼊框Entry
3.5.1 输⼊框的重要性
应⽤程序要取得⽤户的信息,输⼊框是必不可少的,虽然执⾏命令可以使⽤按钮,但是总不能让⽤户⼀直点击按钮吧。
- 输⼊框是 Entry,可以阅读它的源代码。
- 源代码截图:
- ⽐较重要的也就是get 函数,get 函数使⽤的时候不需要任何参数,它的返回值就是该输⼊框的内容。
3.5.2 密码框
代码:
from tkinter import * def reg(): myAccount = a_entry.get() # 获取⽤户输⼊的⽤户名 myPassword = p_entry.get() # 获取⽤户输⼊的密码 a_len = len(myAccount) # 获取输⼊的⽤户名⻓度 p_len = len(myPassword) # 获取输⼊的密码⻓度 if myAccount == "qq_group" and myPassword == "945348278": msg_label["text"] = "登录成功" # ⽤户名和密码全部正确 elif myAccount == "qq_group" and myPassword != "945348278": msg_label["text"] = "密码错误" # ⽤户名正确密码错误 p_entry.delete(0, p_len) else: msg_label["text"] = "⽤户名错误" # ⽤户名错误 a_entry.delete(0, a_len) p_entry.delete(0, p_len) root = Tk() # ⽤户名 a_label = Label(root, text = "⽤户名:") a_label.grid(row = 0, column = 0, sticky = W) a_entry = Entry(root) a_entry.grid(row = 0, column = 1, sticky = E) # 密码 p_label = Label(root, text = "密码:") p_label.grid(row = 1, column = 0, sticky = W) p_entry = Entry(root) p_entry["show"] = "*" # 密码显示为 * p_entry.grid(row = 1, column = 1, sticky = E) # 登录按钮 btn = Button(root, text = "登录", command = reg) btn.grid(row = 2, column = 1, sticky = E) # 提示信息 msg_label = Label(root, text = "") msg_label.grid(row = 3) root.mainloop()
- 其实密码框和输⼊框基本是⼀样的,都是向⾥⾯输⼊信 息⽤的。
- 如果要说不⼀样,也就⼀个地⽅不⼀样:密码框需要输 ⼊的信息的显示字符⽐较单⼀。
- ⽐如 e 是⼀个输⼊框,我们可以设置它的 show 属性让它 变成⼀个密码框,即 e[‘show’] = ‘*’ 就可以了。
- 下⾯是⼀个⼩型登录程序,它的⽤户名是
qq_group
,密码是945348278
,如果输⼊正确,那么点击“登录”按钮之后,就会显 示“登录成功”,如果输⼊不符合,那么就会显示“⽤户 名或者密码错误”,并且清空两个输⼊框。 - 运⾏结果:
- 输⼊正确的⽤户名和密码,显示
登录成功
: - 输⼊错误的⽤户名或密码,显示
⽤户名错误
或密码错误
:
3.6 菜单Menu
- 菜单的信息量是⾮常⼤的,给用户提供导航使用,由于菜单⼜可以有⼦菜单,因此菜单的信息量⾮常⼤。
- 菜单的分类也较多,通常可以分为下拉菜单、弹出菜单 等等。
3.6.1 添加顶层菜单
- 我们可以使⽤ Menu 类来新建⼀个菜单, Menu 和其他的组件⼀样,第⼀个是parent ,这⾥通常可以为窗⼝。
- 然后我们可以⽤ add_commmand ⽅法来为它添加菜单项, 如果该菜单是顶层菜单,则添加的菜单项依次向右添加。 如果该菜单时顶层菜单的⼀个菜单项,则它添加的是
下拉 菜单的菜单项。 - add_command 中的参数常⽤的有 label 属性,⽤来指定的 是菜单项的名称, command属性⽤来指定被点击的时候调⽤ 的⽅法, acceletor 属性指定的是快捷键, underline 属性 是是否拥有下划线。
- 最后可以⽤窗⼝的 menu 属性指定我们使⽤哪⼀个作为它 的顶层菜单。
3.6.2 代码演练
- 这⾥只是做出了顶级菜单,它们四个是并列的⼀⾏,并 没有实现什么功能,效果图如下:
- 下⾯是代码演示:
from tkinter import * root = Tk() menuBar = Menu(root) for item in ["⽂件", "编辑", "视图", "关于"]: menuBar.add_command(label = item) root["menu"] = menuBar root.mainloop()
- 可以看到,⾮常简单的⽅式,我们使⽤ add_command 来 添加菜单项即可。
3.6.3 有⼦菜单的情况
- 如果有⼦菜单,则情况稍微复杂点,这个时候,我们需 要使⽤ add_cascade ,cascade 可以理解为“级联”,即它 的作⽤只是为了引出后⾯的菜单。
- add_cascade 的⼀个很重要的属性就是 menu 属性,它指 明了要把那个菜单级联到该
菜单项上,当然,还必不可少 的就是 label 属性,⽤于指定该菜单项的名称。 - 我们先新建⼀个 Menu 的实例,然后使⽤ add_command 来 添加菜单项,这样等该菜单
建⽴完毕,我们要把它作为另 ⼀个菜单项的⼦菜单,就需要使⽤ add_cascade ⽅法。
3.6.4 代码演练
- 下⾯我们使⽤了下拉菜单,这也是我们通常使⽤的菜单 形式,即⼀⾏菜单项,点击之后会产⽣⼀个下拉菜单。
- 效果
- 代码演示:
from tkinter import * root = Tk() menuBar = Menu(root) fMenu = Menu(menuBar) # ⽂件 for item in ["新建", "打开", "保存", "另存为", "退出"]: fMenu.add_command(label = item) eMenu = Menu(menuBar) # 编辑 for item in ["复制", "粘贴", "剪切", "撤销"]: eMenu.add_command(label = item) vMenu = Menu(menuBar) # 视图 for item in ["默认视图", "全屏模式", "显示/隐藏菜单"]: vMenu.add_command(label = item) aMenu = Menu(menuBar) # 关于 for item in ["版权信息", "帮助⽂档"]: aMenu.add_command(label = item) menuBar.add_cascade(label = "⽂件", menu = fMenu) menuBar.add_cascade(label = "编辑", menu = eMenu) menuBar.add_cascade(label = "视图", menu = vMenu) menuBar.add_cascade(label = "关于", menu = aMenu) root["menu"] = menuBar root.mainloop()
这⾥要注意的是我们可以先把⼦菜单做好,然后再做上层菜单。
3.6.5 弹出菜单
- 弹出菜单⼜叫“上下⽂菜单”,也叫“右键菜单”,它 通常是⿏标单击右键产⽣的菜单,因此会有“右键菜单” 的说法。
- 其实很多界⾯库⾥⾯都是给出了弹出菜单的简单的制作 ⽅法的,但是 tkinter ⾥⾯我们却只能使⽤⽐较原始的事 件绑定的⽅式去做。
- ⼤体思路就是:我们先新建⼀个菜单,然后向菜单项中 添加各种功能,最后我们监听⿏标右键消息,如果是⿏标 右键被单击,此时可以根据需要判断下⿏标位置来确定是 哪个弹出菜单被弹出,然后使⽤ Menu 类的 pop ⽅法来弹出 菜单。
- ⼤体思路就是如此,⾄于具体的细节,让我们到代码实 战中⼀探究竟。
- Menu 类⾥⾯有⼀个 post ⽅法,它接收两个参数,即 x 和 y 坐标,它会在相应的位置弹出菜单。
- 还记得⽤ bind ⽅法来绑定事件吗?⽽且要记得⿏标右键 是⽤的
3.6.6 代码演练
- 界⾯效果(该右键菜单中如果点击 python 选项,则会新 建⼀个标签,标签内容是“我的Python课程”):
- 代码演示
from tkinter import * def myLabel(): global root Label(root, text = "我的Python课程").pack() # 点击Python后添加这个标签 root = Tk() menuBar = Menu(root) for each in ["C/C++", "JavaEE", "Android", "PHP", "UI设计", "iOS", "前端与移动开发", "⽹络营销", "云计算"]: menuBar.add_command(label = each) menuBar.add_command(label = "Python", command = myLabel) def pop(event): menuBar.post(event.x_root, event.y_root) root.bind("<Button-3>", pop) # ⿏标右击绑定 root.mainloop()
此处利⽤了 Menu 的 post ⽅法,还有 bind ⽅法,⼀定要 记住⿏标右键的事件名称,这些⽤多了之后⾃然能记住。
3.7 复选Checkbutton与单选Radiobutton
3.7.1 复选按钮
- 复选按钮就是 Checkbutton 类,它的实例化和 Button 很 相似。
- 既然是按钮,那就可以有 command 属性,该属性可以对 应到⼀个函数上去来执⾏某些功能。
- 复选框通常是⽤来选择信息的时候的⼀种选择,它前⾯ 有个⼩正⽅形的⽅块,如果选中则有⼀个对号,也可以再 次点击以取消该对号来取消选中。
3.7.2 复选框代码实例
- 该实例,使⽤了两个复选框,点击那个复选框,如果处 于选中状态,则在下⾯的标签中显示被选中的字样,如果 没有被选中,则显示未被选中的字样。
- 效果截图:
- 代码:
from tkinter import * timeA = 0 timeB = 0 def funcA(): global lab, btnA, timeA if timeA % 2 == 0: timeA += 1 lab["text"] = "Python学科被选中" else: timeA += 1 lab["text"] = "Python学科被取消" def funcB(): global lab, btnB, timeB if timeB % 2 == 0: timeB += 1 lab["text"] = "C++学科被选中" else: timeB += 1 lab["text"] = "C++学科被取消" root = Tk() btnA = Checkbutton(root, text = "Python学科", command = funcA) btnA.pack() btnB = Checkbutton(root, text = "C++学科", command = funcB) btnB.pack() lab = Label(root, text = " ") lab.pack() root.mainloop()
代码也很简单,⽤于控制次数的 timeA 和 timeB
3.7.2 单选框
- 单选框和复选框⾮常相似,只是把 Checkbutton 换成 Radiobutton。
- 我就不代码示例了,因为实在是太相似且简单了。
- 可以参考单选框类源代码。
3.8 文本Text
- 所谓⽂本域,也就是⽂本,其实它可以看做⼀个⼤型的⽂本框,它的属性也更多⼀些。
在Tkinter中,Text控件用于显示和编辑多行文本。以下是一个基本的教程案例,演示如何使用Text控件:
首先,导入Tkinter模块并创建一个顶级窗口。
import tkinter as tk root = tk.Tk()
创建一个Text控件,并设置其大小和位置。
text_widget = tk.Text(root, width=30, height=10) text_widget.pack()
在这个例子中,我们创建了一个宽30字符、高10行的Text控件,并将其添加到窗口中。
可以通过调用insert方法向Text控件中插入文本。
text_widget.insert('end', 'Hello, World!\n')
在这个例子中,我们在文本框的末尾插入了一行文本 “Hello, World!”。
如果需要在运行时获取或修改Text控件中的内容,可以使用get和delete方法。
# 获取所有文本 all_text = text_widget.get('1.0', 'end') # 删除第一行 text_widget.delete('1.0', '1.end')
最后,启动主循环来显示窗口。
root.mainloop()
将这些代码片段组合起来,您将得到一个完整的Tkinter Text控件的使用示例:
3.8.1 代码演练
import tkinter as tk def copy_text(): source_text = text_source.get('1.0', 'end') text_destination.insert('end', source_text) root = tk.Tk() text_source = tk.Text(root, width=30, height=10) text_source.pack() text_destination = tk.Text(root, width=30, height=10) text_destination.pack() button_copy = tk.Button(root, text="Copy Text", command=copy_text) button_copy.pack() root.mainloop()
3.8.2 文本交互
运行此脚本后,您将看到两个文本框和一个按钮。当您在第一个文本框中输入文本并点击“Copy Text”按钮时,文本会被复制到第二个文本框中。
3.8.3 重定向终端text打印
这段代码的主要功能是重定向Python的sys.stdout和sys.stderr标准输出到一个基于Tkinter的ScrolledText控件中,并在窗口中显示。当点击start按钮时,程序会执行一个循环并打印数字,这些数字将通过自定义的myStdout类写入文本框而不是控制台。
import sys import tkinter import time from tkinter import scrolledtext class myStdout(): # 重定向类 def __init__(self): # 将其备份 self.stdoutbak = sys.stdout self.stderrbak = sys.stderr # 重定向 sys.stdout = self sys.stderr = self def write(self, info): # info信息即标准输出sys.stdout和sys.stderr接收到的输出信息 t.insert('end', info) # 在多行文本控件最后一行插入print信息 t.update() # 更新显示的文本,不加这句插入的信息无法显示 t.see(tkinter.END) # 始终显示最后一行,不加这句,当文本溢出控件最后一行时,不会自动显示最后一行 def restoreStd(self): # 恢复标准输出 sys.stdout = self.stdoutbak sys.stderr = self.stderrbak def btn_func(): for i in range(5): print(i) # t.insert('end', i) time.sleep(1) """按键的触发事件""" mystd = myStdout() # 实例化重定向类 window = tkinter.Tk() # 实例化tk对象 t = scrolledtext.ScrolledText(window) # 创建多行文本控件 t.pack() # 布局在窗体上 b = tkinter.Button(window, text='start', command=btn_func) # 创建按钮控件,并绑定触发事件 b.pack() # 布局在窗体上 window.mainloop() # 显示窗体 mystd.restoreStd() # 恢复标准输出
点击运行效果如下:
3.9 图像Image
在Tkinter中,您可以使用PhotoImage类来加载和显示图像。以下是一个基本的教程案例,演示如何在Tkinter窗口中显示一张图片:
3.9.1 显示图像代码
首先,确保您有一个要使用的图片文件,并下面图片将其放在您的Python脚本所在目录下。
然后,编写以下代码:
import tkinter as tk # 创建一个顶级窗口 root = tk.Tk() # 加载图片 image_path = 'path_to_your_image.png' # 替换为实际的图片路径 image = tk.PhotoImage(file=image_path) # 创建一个标签来显示图片 label = tk.Label(root, image=image) label.pack() # 开始主循环 root.mainloop()
在这个例子中,我们首先导入了Tkinter模块,并创建了一个顶级窗口(root)。然后,我们使用PhotoImage类从指定的路径加载了一张图片,并将该图片设置为一个新创建的Label控件的内容。最后,我们将这个Label添加到窗口中并启动主循环。
3.9.2 显示图像
确保将 image_path 变量替换为您要使用的图片的实际路径。如果一切正常,当运行此脚本时,应该会在窗口中看到您加载的图片。
请注意,为了防止图片资源被垃圾回收,通常需要将PhotoImage对象保存在一个变量中。在上面的例子中,我们在创建Label时传递了image对象,这实际上已经起到了保存引用的作用。如果您不打算将图片与Label关联,则可能需要手动保存对PhotoImage对象的引用。
4. 布局layout
对于任何⼀⻔图形界⾯编程来说,布局都是⾮常重要 的⼀关,它的英⽂翻译叫做“layout”。不管是MFC、Java、还是Qt等图形界⾯编程, 都会有有布局的相关知识。 Python 的 tkinter 也⼀样。
4.1 tkinter的三种布局:
- 其实我们已经接触过 tkinter 的⼀种布局,就是 pack 布局,它⾮常简单,我们不⽤做过多的设置,直接使⽤⼀个 pack 函数就可以了。
- grid 布局: grid 可以理解为⽹格,或者表格,它可以把 界⾯设置为⼏⾏⼏列的⽹格,我们在⽹格⾥插⼊我们想要 的元素。这种布局的好处是不管我们如何拖动窗⼝,相对 位置是不会变化的,⽽且这种布局也超简单。
- place 布局:它直接使⽤死板的位置坐标来布局,这样做 的最⼤的问题在于当我们向窗⼝添加⼀个新部件的时候, ⼜得重新测⼀遍数据,且我们不能随便地变⼤或者缩⼩窗⼝,否则可能会导致混乱。
4.1.1 pack 布局
- 我们使⽤ pack 函数的时候,默认先使⽤的放到上⾯,然 后 依次向下排,它会给我们的组件⼀个⾃认为合适的位置 和⼤⼩,这是默认⽅式,也是我们上⾯⼀直采⽤的⽅式。
- pack 函数也可以接受⼏个参数:
- side 参数指定了它停 靠在哪个⽅向,可以为 LEFT,TOP,RIGHT,BOTTOM,分别代表 左,上,右,下
- fill 参数可以是 X,Y,BOTH 和 NONE,即在⽔平⽅向填充,竖直⽅向填充,⽔平和竖直⽅向填充和不填充。
- expand 参数可以是 YES 和 NO,它的 anchor 参数可 以是 N,E,S,W(这⾥的 NESW 分别表示北东南⻄,这⾥分别 表示上右下左)以及他们的组合或者是CENTER(表示中 间)。
- ipadx 表示的是内边距的 x ⽅向,它的 ipady 表示 的是内边距的 y ⽅向,padx表示的是外边距的 x ⽅向, pady 表示的是外边距的 y ⽅向。
4.1.2 pack 的布局实例
- 根据上⾯的介绍,我们可以做出如下布局的样⼦:
- 按理说做的这么复杂本身没什么意思,只是想让⼤家看 ⼀下其实 pack 也可以完成相对复杂的布局,它的源代码如下:
from tkinter import * root = Tk() Button(root, text = "A").pack(side = LEFT, expand =YES, fill = Y) Button(root, text = "B").pack(side = TOP, expand = YES, fill = BOTH) Button(root, text = "C").pack(side = RIGHT, expand = YES, fill = NONE, anchor = NE) Button(root, text = "D").pack(side = LEFT, expand = NO, fill = Y) Button(root, text = "E").pack(side = TOP, expand = NO, fill = Y) Button(root, text = "F").pack(side = BOTTOM, expand = YES) Button(root, text = "G").pack(anchor = SE) root.mainloop()
4.2 grid 布局
- 由于我们的程序⼤多数都是矩形,因此特别适合于⽹格 布局,也就是 grid 布局。
- 使⽤ grid 布局的时候,我们使⽤ grid 函数,在⾥⾯指 定两个参数,⽤ row 表示⾏,⽤ column 表示列,注意的是 row 和 column 的编号都从 0 开始。
- grid 函数还有个 sticky 参数,它可以⽤ N,S,W,E 表示 上,下,左,右 , 它决定了这个组件是从哪个⽅向开始的, 下⾯的例⼦可以很好的解释这⼀点。
- grid 布局直接⽤后⾯的⾏和列的数字来指定了它位于哪个位置,⽽不必使⽤其他参数。
- grid 函数也⽀持诸如 ipadx,ipady,padx,pady ,它们的意思和 pack 函数是⼀样的,默认边距是 0。
- 它还⽀持参数⽐如 rowspan ,表示跨越的⾏数, columnspan 表示跨越的列数。
- 它还有⼀些属性,可以在以后我们的demo中慢慢使⽤来 看出其重要性。
账号登陆例⼦的截图
界⾯如下:
代码如下:
from tkinter import * py = Tk() Label(py, text = "账号:").grid(row = 0, sticky = W) Entry(py).grid(row = 0, column = 1, sticky = E) Label(py, text = "密码:").grid(row = 1, sticky = W) Entry(py).grid(row = 1, column = 1, sticky = E) Button(py, text = "登录").grid(row = 2, column = 1, sticky = E) py.mainloop()
- 代码说明: 代码⾮常简单,参考grid布局介绍理解,其中 Entry 表示“输⼊框”。
4.3 place 布局
- 关于 place 布局,这个的⼏何管理器组织放置在⼀个特定的位置
- 它使⽤ place 函数,它分为 绝对布局 和 相对布局 ,绝对布局使⽤ x 和 y 参数,相对布局使⽤ relx,rely, relheight 和 relwidth 参数。
- 该⽅法⽤的极少,⽽且极度不推荐⼤家⽤,这⾥就不详细说明了。
4.4 总结
- place 不推荐⽤,pack 和 grid 布局更常⽤⼀些。
- 但是 pack 和 grid 不能同时⽤。⽽且通常对于较为复杂点的界⾯, 还是建议⼤家⽤gird;如果布局相对简单,使⽤pack 也很不错。
5. 画布Canvas
窗⼝重绘
- 第⼀次认识到⼿绘图形的重要性还是在学习 MFC 的时候, 因为 MFC ⾃带的绘图功能实在过于丑陋,我们可以重绘标题栏、菜单栏、最⼩化按钮、最⼤化按钮、关闭按钮等窗 ⼝组件来让窗⼝得到美化,除了这些,还可以⼿绘按钮, 后来出了⼀⻔技术, 叫做“Direct UI”,也是这种思想的进⼀步发扬光⼤把。
- 但是 tkinter 没有这些⽅⾯的接⼝,我也深感遗憾。但是 tkinter 有⼀个绘图功能的组件,即 Canvas,翻译成汉 语即“帆布”,可以理解为“画布”,即⽤于绘制图形。
5.1 canvas
- 下⾯是该类的部分截图:
- 其实我们主要的也就是⽤上⾯的这些绘图函数来进⾏⼿ ⼯的绘制⼀些东⻄。
5.2 简单示例
- 我们第⼀个示例随便绘制了,先把背景⽤ rgb 格式刷成 蓝⾊,然后画⼀条线,然后写⼏个字。
- 代码截图:
from tkinter import * root = Tk() root.title("开心就好") canv = Canvas(root, width = 400, height = 300, bg = "pink") canv.create_line((0,0), (200,200), width = 8) canv.create_line((400,0), (200,200), width = 8) canv.create_text(300, 30, text = "Python教程") canv.pack() root.mainloop()
- 效果图如下:
5.3 中国象棋棋盘
- 其实⼿绘可以做很多事,很多东⻄都可以⼿绘,只不过 有些东⻄确实⼿绘挺累的。
- 下⾯是⼿绘了⼀个中国象棋棋盘的截图(关于中国象棋, 我还是挺喜欢玩的):
- 其实没啥技术难度,下⾯是代码截图,不过我还是希望 读者先亲⾃动⼿做⼀个,毕竟不难:
from tkinter import * root = Tk() root.title("中国象棋棋盘⼿绘") canv = Canvas(root, width = 400, height = 450) canv.create_line((0,2), (400,2), width = 2) for i in range(10): canv.create_line((0, i * 50), (400, i * 50), width = 2) canv.create_line((3,0), (3,450), width = 2) for i in range(8): canv.create_line((i * 50,0), (i * 50,200), width = 2) for i in range(8): canv.create_line((i * 50,250), (i * 50,450), width = 2) canv.create_line((397,0), (397,450), width = 2) canv.create_line((150,0), (250,100), width = 2) canv.create_line((150,100), (250,0), width = 2) canv.create_line((150,450), (250,350), width = 2) canv.create_line((150,350), (250,450), width = 2) canv.create_text(110, 220, text = "汉界") canv.create_text(290, 220, text = "楚河") canv.pack() root.mainloop()
6. 打包程序pyinstaller
6.1 配置pyinstaller环境
- 升级我们的pip版本
python -m pip install --upgrade pip
- 通过pip安装 pyinstaller:
pip --default-timeout=100 install pyinstaller -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
- 进⼊项⽬⽂件的⽂件夹,执⾏下⾯命令 pyinstaller -w *.py
常用参数 含义
-i 或 -icon 生成icon
添加icon:
pyinstaller -F -w -i favicon.ico *.py
(注:图片资源需与源文件在同意路径,或指定对应资源图路径)-F 创建一个绑定的可执行文件
-w 使用窗口,无控制台
-C 使用控制台,无窗口
-D 创建一个包含可执行文件的单文件夹包(默认情况下)
-n 文件名
6.2 贪吃蛇游戏
使用Tkinter库创建的简单贪吃蛇游戏。游戏包含一个黑色背景的300x300像素的棋盘,玩家通过键盘控制蛇移动并吃掉苹果以增加分数。
源代码snake.py
#!/usr/bin/env python3 """ ZetCode Tkinter tutorial This is a simple Snake game clone. Author: Jan Bodnar Website: zetcode.com Last edited: April 2019 """ import sys import random from PIL import Image, ImageTk from tkinter import Tk, Frame, Canvas, ALL, NW class Cons: BOARD_WIDTH = 300 BOARD_HEIGHT = 300 DELAY = 100 DOT_SIZE = 10 MAX_RAND_POS = 27 class Board(Canvas): def __init__(self): super().__init__(width=Cons.BOARD_WIDTH, height=Cons.BOARD_HEIGHT, background="black", highlightthickness=0) self.initGame() self.pack() def initGame(self): '''initializes game''' self.inGame = True self.dots = 3 self.score = 0 # variables used to move snake object self.moveX = Cons.DOT_SIZE self.moveY = 0 # starting apple coordinates self.appleX = 100 self.appleY = 190 self.loadImages() self.createObjects() self.locateApple() self.bind_all("<Key>", self.onKeyPressed) self.after(Cons.DELAY, self.onTimer) def loadImages(self): '''loads images from the disk''' try: self.idot = Image.open("dot.png") self.dot = ImageTk.PhotoImage(self.idot) self.ihead = Image.open("head.png") self.head = ImageTk.PhotoImage(self.ihead) self.iapple = Image.open("apple.png") self.apple = ImageTk.PhotoImage(self.iapple) except IOError as e: print(e) sys.exit(1) def createObjects(self): '''creates objects on Canvas''' self.create_text(30, 10, text="Score: {0}".format(self.score), tag="score", fill="white") self.create_image(self.appleX, self.appleY, image=self.apple, anchor=NW, tag="apple") self.create_image(50, 50, image=self.head, anchor=NW, tag="head") self.create_image(30, 50, image=self.dot, anchor=NW, tag="dot") self.create_image(40, 50, image=self.dot, anchor=NW, tag="dot") def checkAppleCollision(self): '''checks if the head of snake collides with apple''' apple = self.find_withtag("apple") head = self.find_withtag("head") x1, y1, x2, y2 = self.bbox(head) overlap = self.find_overlapping(x1, y1, x2, y2) for ovr in overlap: if apple[0] == ovr: self.score += 1 x, y = self.coords(apple) self.create_image(x, y, image=self.dot, anchor=NW, tag="dot") self.locateApple() def moveSnake(self): '''moves the Snake object''' dots = self.find_withtag("dot") head = self.find_withtag("head") items = dots + head z = 0 while z < len(items)-1: c1 = self.coords(items[z]) c2 = self.coords(items[z+1]) self.move(items[z], c2[0]-c1[0], c2[1]-c1[1]) z += 1 self.move(head, self.moveX, self.moveY) def checkCollisions(self): '''checks for collisions''' dots = self.find_withtag("dot") head = self.find_withtag("head") x1, y1, x2, y2 = self.bbox(head) overlap = self.find_overlapping(x1, y1, x2, y2) for dot in dots: for over in overlap: if over == dot: self.inGame = False if x1 < 0: self.inGame = False if x1 > Cons.BOARD_WIDTH - Cons.DOT_SIZE: self.inGame = False if y1 < 0: self.inGame = False if y1 > Cons.BOARD_HEIGHT - Cons.DOT_SIZE: self.inGame = False def locateApple(self): '''places the apple object on Canvas''' apple = self.find_withtag("apple") self.delete(apple[0]) r = random.randint(0, Cons.MAX_RAND_POS) self.appleX = r * Cons.DOT_SIZE r = random.randint(0, Cons.MAX_RAND_POS) self.appleY = r * Cons.DOT_SIZE self.create_image(self.appleX, self.appleY, anchor=NW, image=self.apple, tag="apple") def onKeyPressed(self, e): '''controls direction variables with cursor keys''' key = e.keysym LEFT_CURSOR_KEY = "Left" if key == LEFT_CURSOR_KEY and self.moveX <= 0: self.moveX = -Cons.DOT_SIZE self.moveY = 0 RIGHT_CURSOR_KEY = "Right" if key == RIGHT_CURSOR_KEY and self.moveX >= 0: self.moveX = Cons.DOT_SIZE self.moveY = 0 RIGHT_CURSOR_KEY = "Up" if key == RIGHT_CURSOR_KEY and self.moveY <= 0: self.moveX = 0 self.moveY = -Cons.DOT_SIZE DOWN_CURSOR_KEY = "Down" if key == DOWN_CURSOR_KEY and self.moveY >= 0: self.moveX = 0 self.moveY = Cons.DOT_SIZE def onTimer(self): '''creates a game cycle each timer event''' self.drawScore() self.checkCollisions() if self.inGame: self.checkAppleCollision() self.moveSnake() self.after(Cons.DELAY, self.onTimer) else: self.gameOver() def drawScore(self): '''draws score''' score = self.find_withtag("score") self.itemconfigure(score, text="Score: {0}".format(self.score)) def gameOver(self): '''deletes all objects and draws game over message''' self.delete(ALL) self.create_text(self.winfo_width() /2, self.winfo_height()/2, text="Game Over with score {0}".format(self.score), fill="white") class Snake(Frame): def __init__(self): super().__init__() self.master.title('Snake') self.board = Board() self.pack() def main(): root = Tk() nib = Snake() root.mainloop() if __name__ == '__main__': main()
依赖的三张图片,分别存为apple.png
dot.png
head.png
代码分为几个部分:
- 导入所需模块和自定义常量(Cons)。
- 定义了Board类,继承自Canvas类。这个类包含了游戏的主要逻辑:初始化游戏、加载图片、创建对象、检查碰撞、移动蛇、显示得分等。
- 定义了Snake类,继承自Frame类。这个类主要用于在窗口中显示游戏界面。
- main()函数用于启动游戏主循环。
当运行程序时,它会创建一个包含蛇和苹果的游戏界面。玩家可以使用键盘上的方向键来控制蛇的移动,并尝试吃掉随机出现的苹果。如果玩家让蛇撞到边界或自己的身体,游戏将结束,并显示“Game Over”消息以及最后的得分。
6.3 打包
以Tkinter Realsense D435相机采集图片tkinter_camera_Aligned.py
为例
import tkinter as tk from tkinter import ttk import pyrealsense2 as rs import numpy as np from PIL import Image, ImageTk import os def camera_aligned(): # 创建GUI界面 # global camera_static root = tk.Tk() style = ttk.Style() style.configure('Custom.TButton', font=('Helvetica', 16)) root.title("Realsense D435i Camera") root.geometry('380x400') # 创建图像显示区域 color_label = tk.Label(root) color_label.grid(row=2, column=0,columnspan=3) depth_label = tk.Label(root) depth_label.grid(row=2, column=4,columnspan=3) # 创建保存图像函数 def save_image(color_image, depth_image): # 创建data文件夹(如果不存在) if not os.path.exists("data"): os.makedirs("data") # 获取data文件夹下已保存图像的数量 # num_files = len(os.listdir("data")) # 获取data文件夹下最大的序号 num_files = 0 for file_name in os.listdir("./data"): if file_name.endswith(".png"): index = int(file_name.split(".")[0]) num_files = max(num_files, index) # 保存彩色图为PNG格式 color_image_path = "data/{:04d}.png".format(num_files + 1) color_image.save(color_image_path) print(f"Color image saved as {color_image_path}") # 保存深度图为对齐TIFF格式 depth_image_path = "data/{:04d}.tiff".format(num_files + 1) depth_image.save(depth_image_path, "TIFF") print(f"Depth image saved as {depth_image_path}") # 创建打开相机按钮的回调函数 def open_camera(): # 创建Realsense管道 global pipeline pipeline = rs.pipeline() # 配置相机参数 config = rs.config() config.enable_stream(rs.stream.color, 640, 480, rs.format.rgb8, 30) config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30) # 启动相机 pipeline.start(config) try: while True: # 等待相机数据 frames = pipeline.wait_for_frames() color_frame = frames.get_color_frame() depth_frame = frames.get_depth_frame() # 将相机数据转换为可显示的图像 color_image = np.asanyarray(color_frame.get_data()) pil_color_image = Image.fromarray(color_image) tk_color_image = ImageTk.PhotoImage(pil_color_image.resize((160, 120))) depth_image = np.asanyarray(depth_frame.get_data()) pil_depth_image = Image.fromarray(depth_image) tk_depth_image = ImageTk.PhotoImage(pil_depth_image.resize((160, 120))) # 更新图像显示 color_label.config(image=tk_color_image) depth_label.config(image=tk_depth_image) # 保持图像显示的引用,防止被垃圾回收 color_label.image = pil_color_image depth_label.image = pil_depth_image # 更新GUI界面 root.update() finally: pipeline.stop() root.destroy() # 创建保存图像按钮的回调函数 def save_button_callback(): # 获取当前显示的彩色图和深度图 color_image = color_label.image depth_image = depth_label.image # 检查图像是否存在 if color_image is not None and depth_image is not None: # 保存图像 save_image(color_image, depth_image) else: print("No image to save") def close_camera(): # camera_static = 1 pipeline.stop() # 创建退出按键的回调函数 def image_button_callback(): # 停止相机并关闭窗口 # pipeline.stop() root.destroy() la1=ttk.Label(root,text='Realsense D435相机图形采集', style='Custom.TButton') la1.grid(row=0,column=0, ipady=6, pady=6, columnspan=7) # 0行0列 # 创建打开相机按钮 open_button = tk.Button(root, text="打开相机", command=open_camera, bg='green',fg='yellow') open_button.grid(row=1, column=0) # 创建打开相机按钮 close_button = tk.Button(root, text="关闭相机", command=close_camera, bg='red',fg='yellow') close_button.grid(row=1, column=2) # 创建保存图像按钮 save_button = tk.Button(root, text="保存图像", command=save_button_callback, bg='blue',fg='yellow') save_button.grid(row=1, column=4) # 创建退出图像按钮 image_Exit_button = tk.Button(root, text="退出采集", command=image_button_callback, bg='red',fg='yellow') image_Exit_button.grid(row=1, column=6) la2=ttk.Label(root,text='彩色图像') la2.grid(row=3,column=1) # 0行0列 la2=ttk.Label(root,text='深度图像') la2.grid(row=3,column=5) # 0行0列 # 运行GUI界面 root.mainloop() if __name__ == '__main__': camera_aligned()
- 直接打包:
pyinstaller -F tkinter_camera_Aligned.py
(生成的exe文件运行后,运行会有个控制台,即可见的黑框) - 不带控制台:
pyinstaller -F -w tkinter_camera_Aligned.py
(该命令会去除控制台,运行即运行仅程序窗口) - 添加icon:
pyinstaller -F -w -i favicon.ico tkinter_camera_Aligned.py
(注:图片资源需与源文件在同意路径,或指定对应资源图路径)
生成的exe文件在环境主目录dist文件夹下
7. 总结
- 本文粗略了解Tkinter GUI界面设计,围绕其基本组件设计简易的实验,总之纸上得来终觉浅,为知此事要躬行。
- 在以后的博文中我们将学会用项目系统,从而实现对外部世界进行感知,充分认识这个有机与无机的环境,科学地合理地进行创作和发挥效益,然后为人类社会发展贡献一点微薄之力。