Python GUI Programming
大妈曾聊到:我们的整个互联网,以及建立在互联网上的庞大产业链,都建立在CLI(command-line interface)的基础之上。程序员通过CLI和电脑沟通,用户通过GUI(Graphical User Interfce用户图形界面)和程序沟通。
孩子们在废寝忘食地玩游戏时,程序员脑中看到的是一行又一行代码。学完今天的课程之后,你也会像程序员一样:转变自己看待程序的视角。有了这种视角后,你会看到智慧之光从无数目之所及的程序中流淌出来,就像画家看到梵高画作一般,感慨生活在这个时代的幸运。
没错,今天我会教你制作GUI,Python有三种GUI工具包:wxPython、PyQt、Tkinter。我们今天会用Tkinter,为什么?因为wxPython、PyQt者需要你额外安装,Tkinter则内置在Python中。虽然它很丑,但代码简单,这会帮助你捋清思路。
我会给你一份教程,但我只要求你阅读前面七篇文章,并运行其代码。做完这些以后,你便学会了怎样设计简单的GUI。它只有四个步骤:创建主窗体、创建元件、显示元件、进入窗体的主循环。
你的任务是:把上周的小程序,放在GUI里呈现。你可以自行摸索一段时间,做出作业,然后跟下面的代码做一下对比。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from tkinter import *
#Frame是一个方形区域,他相当于一个篮球场,里面可以放各种Widget(组件)
#生成一个App类,这种表达方式意味着Application是Frame的一种类型
#但App又和Frame有点不一样,它有很多可以自定义的地方。
#如果不给App做init,App会引用Frame的init(当然App可以尽管调用Frame里的代码)
class Application(Frame):
#生成整体框架,进行Class的初始化
def __init__(self,master = None): #Master是一个父组件,默认是None
#在你实例化(instantiate)App类之前,要先对Frame进行初始化
Frame.__init__(self,master)
#初始化了Frame之后,再来给App类加入新特性。
#先定义三个可变容器模型
self.history_List = [] #创建列表,存放历史记录
self.v = StringVar() #创建可变字符串StringVar,注意它不是Python内建的对象,是Tkinter下的对象
self.weather_dict = {} #创建字典
#导入数据,制作词典
file = open('weather_info.txt')
for i in file.readlines():
i = i.strip('\n').split(',')
self.weather_dict[i[0]] = i[1]
file.close()
#创建主窗体
self.frame = Frame(root, width = 100, height = 250)
#Grid管理布局比pack更容易。不要混用Grid和Pack
#Grid使用方法: 1.创建控件 2.用Grid方法告诉布局管理器在合适的行和列显示它们。
#Grid好处:不用事先指定每个网格的大小
self.frame.grid()
#设置按钮上内容位置的(anchor)在正上方,多行文本对齐方式(justify)为靠左对齐
self.label = Label(root, textvariable = self.v, anchor = N, justify = LEFT )
#创建好label后,把它放在第一排(row),指定标签的对齐方式(sticky|参数)为靠左对齐。
self.label.grid(row = 0, sticky = N)
#创建输入框
self.entry = Entry(root)
self.entry.grid(row = 8, column = 0)
#为了让用户单击回车就可以得到结果,这里通过bind将回车键和enter指令绑定在一起
self.entry.bind('<Return>',self.enter)
self.button1 = Button(root, text = 'Help', command = self.help)
self.button1.grid(row = 8, column = 1 )
self.button2 = Button(root, text = 'History', command = self.history)
self.button2.grid(row = 8, column = 2)
self.button3 = Button(root, text = "Quit", command = self.frame.quit)
self.button3.grid(row = 8, column = 3)
def enter(self, event): #回调的函数必须要有event作为输入参数
'''按回车时,返回天气状况,并且让光标回到初始位置'''
a = self.entry.get() #StringVar是可变字符串,get()和set是得到和设置其内容
if a in self.weather_dict:
self.v.set("%s的天气为:%s" % (a, self.weather_dict[a]))
self.history_List.append(a)
else:
self.v.set("很抱歉,我们暂未收录您要查找的城市")
self.entry.delete(0, END) #清除输入框中的内容
def help(self):
self.v.set("""
欢迎使用笨方法查天气
输入城市名和回车键即可查天气
点击help获取帮助文档
点击history查看搜索记录
点击quit退出程序
""")
def history(self):
file2 = open('log.txt', 'w+')
file2.truncate()
file2.write("你曾查询过下列城市的天气:\n ")
for i in self.history_List:
file2.write("%s %s \n" %(i, self.weather_dict[i]))
file2.seek(0)
self.v.set(file2.read())
file2.close()
def enter(self, event):
#按回车时,返回天气状况,并且让光标回到初始位置
#回调的函数必须要有event作为输入参数
a = self.entry.get()
if a in self.weather_dict:
self.v.set("%r 的天气为 %r" %(a, self.weather_dict[a]))
self.history_List.append(a)
else:
self.v.set("我好像还未收录到这个城市")
#光标恢复原位
self.entry.delete(0, END)
if __name__ == '__main__':
root = Tk()
root.title('笨方法查天气')
app = Application()
root.mainloop() #进入窗体主循环
你在读代码时应该已经理解了六成代码,但你仍未理解的那四成代码才是你挑战下周作业的基础。所以请不断地问自己一些问题:
你想做一个怎样的GUI?
这是最关键的一步。就像懂得PS软件的所有按键不会让你学会P图,懂得Tkinter的所有特性也不会让你做出一个超牛掰的GUI。你要像魔法师一样,在使用道具之前,就想好自己要怎样震撼观众。
所以在了解Tkinter大概的套路之后,就立刻画图吧。画出功能键的位置,诠释它们。之后再根据具体的功能去文档中寻找解决方案。这时,你将会遇到几个非常有趣的部件:
- 按钮:Button可以包含文本或图像,并且能关联一个函数。当你按下这个按钮的时候,Tkinter会自动调用关联的函数,比如:
self.button1 = Button(root, text = 'Help', command = self.help)
def help(self):
self.v.set("""打倒醉点""")
- 布局管理器:你有了一个按钮,它要放在哪儿比较好呢?Grid可以帮到你。除了Grid之外,Pack、Place也有相同作用。只是Grid更加方便,你不用事先指定组件大小,只要告诉Grid,第一列放这个,第二列放这个,Grid就会自动调整组件大小,使其适应你要求的排列顺序。比如:
self.button1.grid(row = 8, column = 1 )
self.button2.grid(row = 8, column = 2)
self.button3.grid(row = 8, column = 3)
Tips:如果想让一个按钮占两个按钮的大小(也就是指定组件跨越多个网格)怎么办?使用rowspan和columnspan吧。
不要混用Grid和Pack。
- 颜色:画画时,上色必不可少,Tkinter也可以帮你做到。
from tkinter import *
root = Tk()
Label(root, text = 'red', bg = 'red').pack()
Label(root, text = 'blue', bg ='blue').pack()
Label(root, text = 'yellow', bg = 'yellow').pack()
Label(root, bg = 'red', width = 10, height = 3).pack()
Label(root, bg = 'red', width = 12, height = 5).pack()
Label(root, bg = 'red', width = 15, height = 8).pack()
root.mainloop()
虽然还有很多可以教你的,但具体的用法和注意点我都写进刚才的代码了,你多看几遍应该就能懂。
PS:这篇代码只是微调并且注释了Shippo同学的代码,因为实在太简洁太美了。