代码分析
本周的任务难点主要有两个:第一是对w1代码的改写。需要提炼核心功能。第二是GUI中控件的响应事件。从命令行互动,发展到图形界面交互。
w1代码改写为可复用的函数
对w1中CLI程序的改写,使其可复用。这需要将程序的功能剥离开来,分别定义函数,每个函数只能实现一个独立的功能。这样就能够将其导入到w2中重复使用。这样做的好处时,在后续编写代码的时候,不至于又重新从头写代码,而是可以直接使用已经写好的功能模块。
对于没有编程基础(尤其是没有面向对象编程知识)的人来说,提炼出核心功能是非常困难的。通过w1到w2的练习,可以发现:写出独立核心功能函数的关键有三点:
- 传入的形式参数
- 实现本函数功能的程序
- 函数的返回值
这些都是C语言编程中学习过的基础知识。但是,当真正动手编程的时候,主要将思路厘清,就会函数功能是比较好实现的,但是,需要写出优美而精练的代码,就需要关注“传入形式参数”和“函数的返回值”这两项,这两项看似简单,但其实相对比较难。可能需要注意的知识点如下:
- 实际参数(形式参数)的数据类型(str、list、dict、tuple等)
- 在主函数中定义全局变量
- 函数返回值传递回主函数,并应用于主函数(不要直接在函数定义中print等操作)。这样可以让功能更独立。
- 函数返回值作为参数,可以放到哪些位置(比如print,某个函数中的参数等。)
通过GUI控件实现图形化的人机交互
GUI(Graphical User Interface)这个词往往会吓到我们。这个词给我们一种很高大上的感觉(比起w1中的CLI程序)。这让人误以为这样的程序不同于我们在CLI中的程序。因此,在最初的摸索中,我一直以为GUI程序使用的是完全不同于CLI端程序的架构和结构。实际上,GUI运行程序只是将CLI端程序进行包装,也就是完成界面交互设计。w2作业结果表明:GUI的代码框架结构与CLI的大体相同,只不过应用的模块不一样。
GUI界面的构建使用Tkinter模块,可以构建出文本框、按钮等基本的控件。这些都是比较简单的,找到官方文档中的案例,或者自己搜索一些tutorial,跟着做几个例子,就自然明白了。但问题是,官方文档(tkinter这一块的例子比较少)、各种tutorial中找到的例子和我们要完成的任务还是有差别的。那么,修改这些例子的过程,就是学习和熟悉这个模块及文档的过程。我没有找到与w2任务完全相同的例子,那么,这些差异就需要发挥自学能力,进行摸索和尝试的地方。这给我的经验是,以后在写其他程序的时候,最好是首先提炼核心功能,然后寻找与核心功能类似的例子。通过对例子的研读和修改,完成自己的程序功能。
控件的响应事件,这个是最难的部分。具体说,有:
- 输入entry的值
- 点击“查询”按钮
- 将entry的值传入处理过程
- 将处理结束的值显示到text
- text的状态设置:text的disable、normal的状态处理,保证text文本框不能直接获取从键盘的输入值。
实际上,在具体的程序中,【查询】【帮助】【日志】需要完成的工作是相同的,以【查询】为例,可分解如下:
- 定义一个全局变量e,保存entry的值
- “查询”按钮的command参数,抽离出来,写成一个独立函数模块(callback)
- callback中,将全部变量e的值,调用ch1(QueryWeather.py)字典查询程序,得到天气信息后,将其通过text.insert插入到text的显示文本中。
- text的状态通常设置为disable,在需要insert的时候,就改为normal,结束后再变成disable。
优化设计
程序名称: 按钮排布: 字体颜色: bind:输入文字后直接回车查询
附:源文件WeatherQueryGUI.py
from tkinter import *
import QueryWeather as qw
def callback():
#点击查询按钮,将查询结果输出到文本显示区域
text.config(state=NORMAL)
if e.get():
print(e.get())
str = qw.query(e.get(), qw.txt_convert_dict("weather_info.txt"))
text.insert(END, str)
print(str)
e.delete(0, END)
text.config(state=DISABLED)
def print_help():
#点击帮助按钮,将帮助文档显示到文本显示区域
text.config(state=NORMAL)
str = qw.help()
text.insert(END, str)
print(qw.help())
text.config(state=DISABLED)
def print_log():
#打印查询日志
text.config(state=NORMAL)
with open("log.txt", "r", encoding = "utf-8") as f:
text.insert(END, f.read())
print(f.read())
text.config(state=DISABLED)
if __name__ =='__main__':
master = Tk()
master.title("天气查询小程序")
text = Text(master)
text.pack()
text.config(state=DISABLED)
with open("log.txt", "w", encoding = "utf-8") as f:
f.write("您的查询历史如下:\n")
Label(master, text = "请输入城市名称:").pack(side = "left")
e = Entry(master)
e.pack(side = "left")
e.focus_set()
Button(master, text="查询", width=10, command = callback).pack(side = "left")
Button(master, text="历史", width=10, command = print_log).pack(side = "left")
Button(master, text="帮助", width=10, command = print_help).pack(side = "left")
Button(master, text="退出", width=10, command = master.destroy).pack(side = "left")
mainloop()
附:导入模块文件WeatherQuery.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from time import strftime, localtime
def quit():
'''quit'''
return "本次查询结束!欢迎再次使用!"
def txt_convert_dict(filename):
'''convert a file(.txt) to a dictionary and return the dictionary'''
with open(filename,"r", encoding = "utf-8") as f:
dict = {} #create a dict
for line in f:
s = line.split(',') #split: turning a string with a certain separator into a list.
dict.setdefault(s[0], s[1])
return dict
def help():
'''Help documentaion'''
with open("README.md","r", encoding = "utf-8") as f:
return f.read()
def query(item, dict):
if item in dict:
str = item + "的天气:" + dict[item]
else:
str = "不存在,请重新输入规范信息\n"
str = strftime("%Y-%m-%d, %H:%M:%S", localtime()) + str
log(str)
return str
def log(str):
with open("log.txt", "a", encoding = "utf-8") as f:
f.write(str)
return f
if __name__ == '__main__':
city_weather = txt_convert_dict("weather_info.txt")
with open("log.txt", "w", encoding = "utf-8") as f:
f.write("您的查询记录如下:\n")
while True:
user_input = input("请输入城市:")
if user_input == "history":
with open("log.txt", "r", encoding = "utf-8") as f:
print(f.read())
print(query(user_input, city_weather))
mainloop()