用Python脚本语言建立一个基于应用程序的GUI快速启动
开发者在线 Builder.com.cn 更新时间:2007-02-27 作者:builder.com.cn
当你想学习一门新的语言时,特别是像Python这样的脚本语言,在你准备充分开始写应用程序的图形化界面之前,有时候你可能被迫使用应用程序的控制台。
从第一版商业图形界面发行以来(感兴趣的话,可以查看Xerox之星),至今经过了25年的时间,在应用程序上继续使用控制台看上去似乎有点古老。
非常感谢Python脚本语言强调简洁的语法,这意味着你不需要成为Python编程高手就能在程序中使用图形用户界面。为了证明这个说法,我将使用Python标准的GUI(图形用户界面)工具:Tk来建立一个简单的记录会话程序。我不会详细介绍Python的简单语法,如果你有不明白的地方,请你阅读我先前关于这个主题的文章(点击这里和这里就可以查看);
让我们从基本的开始讲起,首先你需要输入Tk界面到你的程序命名空间中。因为我们将会不断地引用到Tk窗口小部件,我们不希望一直用一个包来限定它们,所以最好的方法就是这样做:
from Tkinter import *
这个导入语句与传统的导入语句的区别在于,它在模块中将所有的东西导入程序默认的命名空间,而不是在你需要引用一个像Tkinter.Textbox文本框的时候,你就只能写文本框。
现在我们来建立根窗口并设置它的标题来解释一些东西:
root = Tk()
root.title("Note Taker")
创建根窗口就像创建一个Tk类的实例一样简单,它会装载图形工具包并提供给我们一个可以装载窗口小部件的空白窗口。这是启动一个Tk程序基本过程的第一部分。
root.mainloop()
第二部分(上面所显示的)是调用Tk主循环(mainloop),这个主循环是用来处理事件的,比如键盘事件或者鼠标输 入,允许用户与对话框交换信息。事实上,这时候你才真正地用到了GUI程序。用那四种方式运行一个python脚本,将弹出一个窗口,但是这个窗口仅仅是 放置在那里,它不会做任何操作。
在你的窗口中放置窗口小部件
虽然这是非常令人烦恼的,它的界面包括以下窗口小部件:按钮,窗体等等,这些用户能做的事。现在让我们来创建一些按钮、一个文本框和一个列表框:
button1 = Button(root, text="button1")
button2 = Button(root, text="button2")
button3 = Button(root, text="button3")
text = Entry(root)
listbox = Listbox(root)
创建基本对象非常简单。每个类初始化时候的第一个参数是属于窗口小部件表面的,既然这样我们只能得到根窗口。按钮的文本 属性可以用来设置按钮的标签。有很多类型的窗口小部件,同样也有很多方法可以定制这些窗口小部件,但是这个程序里面我们需要相当多这样的窗口小部件——一 个完整的列表,如果想查看Tkinter官方文档的话就点击这里。现在我们需要做的是,用像下面这样的包方法将这些窗口小部件删除:
text.pack()
button1.pack()
button2.pack()
button3.pack()
listbox.pack()
在窗口中窗口小部件打包的顺序就是他们在屏幕上显示的顺序。运行这个程序后你看到的窗口类似下面这个图片所显示的,它取决于你的操作系统或窗口管理器的特点。
窗口小部件之间的交互
现在,这看起来更有趣,但是它一直没有真正地做任何事情,你可以点击按钮或者在文本框中输入一些信息,但是没有很多要 点。现在让我们来改变这个。使用Tk和它里面的GUI工具包,是通过回叫信号——当某个事件出现的时候调用。让我们写一个简单回叫信号,以便在按下按钮1 的时候提供反馈信号:
def Button1():
listbox.insert(END, "button1 pressed")
这个函数增加“按下按钮1”这个字符串以便在列表框中结束这些项。当按钮被按下时,我们需要以如下方式来调用这个函数改变按钮1的创建:
button1 = Button(root, text="button1", command = Button1)
以同样的方法为按钮2创建一个新的回叫信号函数,这个函数用来在按下第二个按钮时插入不同的字符串到列表框中,然后调试程序。能够非常确定的是,按下按钮将改变列表框的内容。如果我们对这些字符串感到很厌烦,那么我们想在对话框中添加些什么新东西呢?
def Button3():
text_contents = text.get()
listbox.insert(END, text_contents)
text.delete(0,END)
然后设置按钮3的命令达到这样的功能。当点击这个按钮时,按钮3回叫信号首先获得文本框的内容,并将它插到列表框的底部,再清除文本框。装载这个程序并调试。
如果执行这个,一会儿后这个按钮可能看上去像停止工作了,其实是列表框满了。事实上,它工作得很好,但是我们只能在列表 中看到顶部的那组选项,但是我们能使用鼠标滚轮滚动来看到余下的列表。这样有一点笨重,在这里我们需要一个滚动条,这样我们就能在列表中选择我们想看到的 东西。如果我们用下面语句来代替创建列表框,增加一个滚动条只比增加其他窗口小部件中的任何一个部件复杂一点点,这是因为我们必须把它与列表框联系起来:
scrollbar = Scrollbar(root, orient=VERTICAL)
listbox = Listbox(root, yscrollcommand=scrollbar.set)
scrollbar.configure(command=listbox.yview)
然后当我们在它周围移动滚动条时,将改变列表框Y轴的位置(垂直位置)。另外,现在如果我们使用鼠标滚轮滚动列表框,滚动条的位置将会更新。
如果你一直跟着做,现在代码应该看上去像下面这样:
#!/usr/bin/python
from Tkinter import *
root = Tk()
root.title("Note Taker")
def Button1():
listbox.insert(END, "button1 pressed")
def Button2():
listbox.insert(END, "button2 pressed")
def Button3():
text_contents = text.get()
listbox.insert(END, text_contents)
text.delete(0,END)
button1 = Button(root, text="button1", command = Button1)
button2 = Button(root, text="button2", command = Button2)
button3 = Button(root, text="button3", command = Button3)
text = Entry(root)
scrollbar = Scrollbar(root, orient=VERTICAL)
listbox = Listbox(root, yscrollcommand=scrollbar.set)
scrollbar.configure(command=listbox.yview)
text.pack()
button1.pack()
button2.pack()
button3.pack()
listbox.pack()
scrollbar.pack()
root.mainloop()
安置窗口小部件
我们开始来部署这些地方,可以将我们的文本信息输入到文本框中,然后把它们增加到一个列表中。虽然这个程序有点不太好看,每个东西都是垂直排放的,文本框和列表框太小而使用起来不太方便。我们需要对窗口小部件放置的地方更多点控制。
有一些方法可以用来做到这点,但是我们实际会将窗口分成两部分,一部分是文本框和按钮,另一部分控制列表框。我们将使用框架窗口小部件来实现这个功能,框架窗口小部件看上去像个容器,可以将其它的窗口小部件放在它的里面就像根窗口一样。现在让我们来创建一对框架
textframe = Frame(root)
listframe = Frame(root)
到目前为止我们已经将每个窗口小部件的所有者都设置为根窗口,接下来,我们改变它的所有者并应用到我们的新设计中:
button1 = Button(textframe, text="button1", command = Button1)
button2 = Button(textframe, text="button2", command = Button2)
button3 = Button(textframe, text="button3", command = Button3)
text = Entry(textframe)
scrollbar = Scrollbar(listframe, orient=VERTICAL)
listbox = Listbox(listframe, yscrollcommand=scrollbar.set)
scrollbar.configure(command=listbox.yview)
其实,这样不会起太多作用,除非我们告诉Tk窗口小部件在框架中放置的位置,为实现这个我们可以使用包方法和三个关键 字:side、fill和expand。在文字处理器中你能把边认为与对准线相似,它告诉窗口小部件框架它们一般需要设置的哪一边,是顶部,底部,左边还 是右边——默认的形式是群围绕着中心这种方式。在这种情况下,除了用作左边对准器的滚动条外,我们需要将其它的每件东西用滚动条包围列表框的右边。
第二个选项,fill,如果它增长了就告诉Tk哪个方向溢出了小窗口部件,在X轴方向(水平方向),Y轴方向(垂直方 向)或者两个方向都有。一般来说,我们能很好处理这些按钮,但是它希望文本框和列表框能使用尽量多的空间——所以设置文本框在X轴方向溢出和列表框在两个 方向溢出——滚动条应该重新设置列表框的大小,所以设置它在Y轴方向溢出。
最后,expand选项告诉窗口小部件如果可能是否要扩张到免费的空间——增加expand=1这条语句到文本框和列表框包选项中就能实现这个效果。现在,你的代码应该看上去像下面这样:
text.pack(side=LEFT, fill=X, expand=1)
button1.pack(side=LEFT)
button2.pack(side=LEFT)
button3.pack(side=LEFT)
listbox.pack(side=LEFT,fill=BOTH, expand=1)
scrollbar.pack(side=RIGHT, fill=Y)
目前,所有的左边按钮像打包到其他的窗口小部件一样,打包到两个框架中并放置到根窗口里面。记住,这种方式框架应该要扩张:
textframe.pack(fill=X)
listframe.pack(fill=BOTH, expand=1)
最后,我们应该设置窗口的默认大小,以便使我们的窗口小部件的窗口能有更多点的空间。
root.geometry("600x400")
经过这些步骤后,我们的窗口现在应该看上去像下面这样:
键盘事件和鼠标事件
我们有一些工作,或多或少的——但是它的界面一直有点不实用。如果我们能使用鼠标和键盘使事情更直接的话,这些工作看上 去将会更容易处理。当你们运行Tk主循环,在键盘上敲击一个键或者四处移动鼠标产生事件,你可以以同样的方法绑定回叫信号函数到按钮上,这样按钮被按下去 的时候也会产生相应事件。现在我们已经有个函数处理这样的情况了,那就是按钮3的回叫信号,但是不幸地是,我们现在还不能使用,因为按钮的事件回叫信号与 鼠标和键盘不一样。我们必须把它限制在其它的函数中:
def ReturnInsert(event):
Button3()
然后我们注册这些事件的回叫信号,并寻找使用绑定函数:
text.bind("<Return>", ReturnInsert)
这里我们使用<Return>事件代码而不使用<Enter>事件代码,这点是非常重要的,因 为当鼠标进入列表框时就会触发第二个触发器。现在我们想让用户使用右击来从列表框中移动项目,对于相同的处理这种处理是相当棒的。首先我们写一个回叫信号 作为输入来接收事件:
def DeleteCurrent(event):
listbox.delete(ANCHOR)
然后我们绑定事件到这个回叫信号上:
listbox.bind("<Double-Button-3>", DeleteCurrent)
鼠标右键在Tk中被称为按钮-3(不要与我们第三个形式按钮回叫信号的名字混淆了),因为第二个鼠标按钮涉及到鼠标的中 间键。最后,以防万一他们想修改它,我们可以允许用户拷贝一个易事贴返回到文本框。我们没有一个函数能实现这个功能,所以我们将不得不在回叫信号中写一些 新的代码:
def CopyToText(event):
text.delete(0,END)
current_note = listbox.get(ANCHOR)
text.insert(0, current_note)
然后像先前那样将事件绑定到回叫信号中:
listbox.bind("<Double-Button-1>", CopyToText)
你不仅仅可以限制这些事件,如果你想知道更多的关于哪些事件你能绑定的话,请查看Tk内部库介绍。
现在看上去是整理我们程序的时候了。按钮1对于一个函数来说是一个不太好的名字——曾经有相似的名字使我们陷入困境,在 这里我们就不要重蹈覆辙了。我们也有机会改变一些按钮作用,比如,我们可以设置输入按钮来关闭文本框,按钮1没有特别的作用,所以我们可以除去它了。修改 这些并没有真正地功能性改变,但是程序现在看上去像这样:
#!/usr/bin/python
from Tkinter import *
root = Tk()
root.geometry("600x400")
root.title("Note Taker")
def Enter():
text_contents = text.get()
listbox.insert(END, text_contents)
text.delete(0,END)
def Remove():
listbox.delete(ANCHOR)
def Save():
pass
def ReturnInsert(event):
Enter()
def DeleteCurrent(event):
Remove()
def CopyToText(event):
text.delete(0, END)
current_note = listbox.get(ANCHOR)
text.insert(0, current_note)
textframe = Frame(root)
listframe = Frame(root)
enter_button = Button(textframe, text="Enter", command = Enter)
remove_button = Button(textframe, text="Remove", command = Remove)
save_button = Button(textframe, text="Save", command = Save)
text = Entry(textframe)
scrollbar = Scrollbar(listframe, orient=VERTICAL)
listbox = Listbox(listframe, yscrollcommand=scrollbar.set, selectmode=EXTENDED)
scrollbar.configure(command=listbox.yview)
text.bind("<Return>", ReturnInsert)
listbox.bind("<Double-Button-3>", DeleteCurrent)
listbox.bind("<Double-Button-1>", CopyToText)
text.pack(side=LEFT, fill=X, expand=1)
enter_button.pack(side=LEFT)
remove_button.pack(side=LEFT)
save_button.pack(side=LEFT)
listbox.pack(side=LEFT,fill=BOTH, expand=1)
scrollbar.pack(side=RIGHT, fill=Y)
textframe.pack(fill=X)
listframe.pack(fill=BOTH, expand=1)
root.mainloop()
保存会话间的数据
做了这些以后,我们的程序就可以用了,但是当我们关闭程序后,所以的易事贴都会消失——我们可以通过保存列表的内容到一 个文件中,同时在程序运行的时候下载它来安装这个程序。为实现这个我们将使用pickle模块——Python的系列版本,或者使用集结待发的数据类型到 文件中。首先我们需要输入模块:
import pickle
然后我们得到一个变量,易事贴,这个变量包含了易事贴列表。为了将它保存到一个文件中,我们仅仅需要打开文件写入,并使用dump函数。你可能会注意到第三个按钮的回叫信号是空的,让我们来改变它以便将当前的易事贴列表保存到文件中:
def Save():
f = file("notes.db", "wb")
notes = listbox.get(0, END)
pickle.dump(notes, f)
现在,当按下“保存”按钮,我们可以在桌面上得到我们的易事贴的副本。这个不会给我们太多的帮助,除非我们有一些下载这些易事贴的方法,所以让我们在启动主循环前正确地把列表框填满:
try:
f = file("notes.db", "rb")
notes = pickle.load(f)
for item in notes:
listbox.insert(END,item)
f.close()
except:
pass
我们需要用一个try语句将所有的东西捆绑起来,以防文件打开抛出一个异常,也就是说,如果文件“notes.db”不存在的话,应该抛出一个异常,产生警告。然而,如果一个异常被抛出,我们不会真正的注意到它,我们只是仅仅不会下载任何易事贴到列表中而已。
这样,你有了一个用Python和Tk写的,不超过70行代码的,可运行的迷你易事贴程序——用键盘和鼠标输入来完成, 并且可以下载和保存数据。使用这个,你能看到建立一个基于应用程序的快速GUI是多么简单的事情,但是这个程序只有少许的改进,比如,多线程文本区域,或 者复制一个易事贴到剪贴版中,自动保存易事贴,等等,它对于保存你的踪迹到列表中来说,是一个恰当地唾手可得的方法。我将把这个作为一个练习留下来。如果 你想找到更多的关于你能用Python在Tk做些什么的文挡,最好的地方是查看这里的库文件。如果你一直跟着做下来的话,程序应该看上去像下面这样:
下面是这个例子的代码:
#!/usr/bin/python
from Tkinter import *
import pickle
root = Tk()
root.geometry("600x400")
root.title("Note Taker")
def Enter():
text_contents = text.get()
listbox.insert(END, text_contents)
text.delete(0,END)
def Remove():
listbox.delete(ANCHOR)
def Save():
f = file("notes.db", "wb")
notes = listbox.get(0, END)
pickle.dump(notes, f)
def ReturnInsert(event):
Enter()
def DeleteCurrent(event):
Remove()
def CopyToText(event):
text.delete(0, END)
current_note = listbox.get(ANCHOR)
text.insert(0, current_note)
textframe = Frame(root)
listframe = Frame(root)
enter_button = Button(textframe, text="Enter", command = Enter)
remove_button = Button(textframe, text="Remove", command = Remove)
save_button = Button(textframe, text="Save", command = Save)
text = Entry(textframe)
scrollbar = Scrollbar(listframe, orient=VERTICAL)
listbox = Listbox(listframe, yscrollcommand=scrollbar.set, selectmode=EXTENDED)
scrollbar.configure(command=listbox.yview)
text.bind("<Return>", ReturnInsert)
listbox.bind("<Double-Button-3>", DeleteCurrent)
listbox.bind("<Double-Button-1>", CopyToText)
text.pack(side=LEFT, fill=X, expand=1)
enter_button.pack(side=LEFT)
remove_button.pack(side=LEFT)
save_button.pack(side=LEFT)
listbox.pack(side=LEFT,fill=BOTH, expand=1)
scrollbar.pack(side=RIGHT, fill=Y)
textframe.pack(fill=X)
listframe.pack(fill=BOTH, expand=1)
try:
f = file("notes.db", "rb")
notes = pickle.load(f)
for item in notes:
listbox.insert(END,item)
f.close()
except:
pass
root.mainloop()