RWPy4learner 11.3.30 documentation

Version: 11.3.30

Tkinter

一、程序库安装

小白听大伙儿说,Python 安装包虽小,可是却包含了很好的文档 (Battery included),一个内置编辑器 (IDLE)和一个 Tkinter 桌面程序库。小白将信将疑。不信!Really? 试一试。 小白使用的是 Windows 7 操作系统,Python 2.5 软件。他点击 IDLE (Python GUI),打开 Python 内置的编辑器,输入了下面的程序,以测试是否能导入 Tkinter 程序库。:

>>import Tkinter
>>

恩!没有报错。说明用 Python + Tkinter 的环境已经配置好了。

下面就可以用 Tkinter 编写通讯录了。但是,从哪里开始呢?小白想,我最需要的是一个可以执行的简单的 Tkinter 程序,了解 Tkinter 程序的基本结构。 然后在这个简单的程序上搭建我的通讯录程序。桌面程序,比我已经写好的 CLI 程序,无非就是多了交互界面。实际的功能部分,例如增加、删除和查找联系人应该都是差不多的。只需要点击界面后,调用我前面写好的功能函数就可以了。 小白对自己这个思路非常陶醉。对!分三步走,简单的入门程序,画交互界面,最后实现界面功能。

二、入门程序,Hello, Tkinter

小白决定,百度一把,使用 “Python Tkinter” 查了一下。又必应 (bing.com) 搜索了 “Python Tkinter Example”。 果真有好几个简单的 Tkinter 程序。必应第二个搜索结果标题是 Simple GUI Examples (TKinter),来自 ku.edu。 哇 !居然是学校的 Tkinter 课件,还有好多简单的 Tkinter 程序。小白YY中,“学完这个,我应该就可以给MM做好通讯录了”。

小白选了一个有按钮(button)和标签(label)的简单示例。替换了一下里面的文字。“谁不会替换引号里的文字呀?”。换成 “Hello, Tkinter” 吧,应该行!于是小白通过 copy/paste 和简单的修改,得到了下面的代码(参见源代码 tk/HelloTkinter.py)。

# -*- coding: utf-8 -*-
"""
Created on Wed Apr 27 23:31:14 2011

@author: He Jibo
hejibo@ueseo.org
University of Illinois

function:
    a simple program, "Hello, Tkinter"
    for use of our Real Python bookchpater
ref:
    http://rurutia.is-programmer.com/posts/103.html
    
"""


import Tkinter
root = Tkinter.Tk()
root.title("Hello, Tkinter")
#显示Hello, Tkinter
Tkinter.Label(text = "Hello, Tkinter").pack(pady = 15)
#增加一个按钮
Tkinter.Button( text = "Button") .pack(side = Tkinter.BOTTOM)
root.mainloop()

点F5看看什么样吧。

../_images/gui_tk_helloTkinter.png

图1. Hello Tkinter 界面

哇哦!这几行代码,居然是一个完整的程序,并且有文本标签和按钮。看来,Tkinter 应该不难。那么,我们下面开始施工吧。

三、GUI 图纸施工

Tkinter 的基本元素(widget)

先画一个 GUI 的构想图吧。根据以往使用软件的经验,小白认为,通讯录 GUI主要需要三种基本元素: 按钮 (用于搜索,查找、删除等按钮),输入对话框 (用于输入查询关键词,输入联系人基本信息等。),和文本标签。 根据上一节简单的示例,小白知道按钮应该是用 Button ,文本标签是 Label。输入对话框应该怎么做呢?

于是小白搜索了”Python Tkinter widget”,发现 Tkinter 具有这些基本元素(widget):Toplevel, Frame(帧), Button(按钮), Checkbutton(多选按钮), Entry(输入框), Label(文本标签), Listbox(列表框), OptionMenu(选项菜单), Photoimage(图片), Radiobutton(单选按钮), Scale Menu(菜单), Menubutton(菜单按钮), Scrollbar(滚动条).

啊哈!输入对话框应该是 Entry 啦。我的通讯录应该只需要 Label, Entry, Button 这三个 widgets。其它的暂时不管了,以后需要的时候,再查询怎么用。

Tkinter 的布局管理(Layout manager)

构建界面的元素已经明白了。怎么摆放这些元素呢?“知之为知之,不知必应知!” 搜一下,“Tkinter 布局” 和”tkinter layout manager”。

经过搜索,小白发现,原来 Tkinter 有三种布局管理器,pack、grid 和 place。作为学习笔记,小白将资料整理如下:

  • pack()
    pack 采用块地方式组织各种元素。pack 适用于快速生成界面,代码量最少。pack 将元素以自上往下地方式添加到 父组件中去。 使用 pack()布局的通用公式为: WidgetObject.pack(option, …)
  • grid()
    grid 采用类似表格的方式组织元素。grid 比较适合设计带对话框和滚动条的窗体。grid 通过行列参数确定元素摆放位置。 使用 grid()布局的通用公式为: WidgetObject.grid(option, …)
  • place()
    place 直接指定元素放置的具体位置。使用 place() 布局的通用公式为: WidgetObject.place(x,y,anchor)

那我们就选个最简单的吧。用 pack 来摆放元素。小白前面已经找到了三个元素的方法,Entry, Label 和 Button.于是在 HelloTkinter.py 的基础上修改。得了一个新建联系人的对话框。代码如下 (参见源代码tk/TkLayoutManager.py)。

# -*- coding: utf-8 -*-
"""
Created on Thu Apr 28 11:53:22 2011

@author: He Jibo
hejibo@ueseo.org
University of Illinois

function:
    demo tkiner layout manager
    for use of our Real Python bookchpater

    
"""

import Tkinter

root = Tkinter.Tk()
#设置软件尺寸
root.geometry("400x230+100+100")
d = Tkinter.Frame(root)


#名
txtfirst = Tkinter.Entry(d, width = 40)
fname = Tkinter.Label(d, width = 15,text = u"名")
fname.pack(side = "left",pady = 8)
txtfirst.pack(side = "left")
d.pack(side = "top",fill = "x")

#姓
e = Tkinter.Frame(root)
txtlast = Tkinter.Entry(e, width = 40)
lname = Tkinter.Label(e, width = 15,text = u"姓")
lname.pack(side = "left",pady = 8)
txtlast.pack(side = "left")
e.pack(side = "top",fill = "x")

#街道
f = Tkinter.Frame(root)
txtadd = Tkinter.Entry(f, width = 40)
address = Tkinter.Label(f, width = 15,text = u"街道")
address.pack(side = "left",pady = 8)
txtadd.pack(side = "left")
f.pack(side = "top",fill = "x")

#市
g = Tkinter.Frame(root)
txtcity = Tkinter.Entry(g, width = 16)
city = Tkinter.Label(g, width = 15,text = u"市")
city.pack(side = "left",pady = 8)
txtcity.pack(side = "left")

#省
txtst = Tkinter.Entry(g, width = 4)
street = Tkinter.Label(g, width = 8,text = u"省")
street.pack(side = "left",pady = 8)
txtst.pack(side = "left")

#邮政编码
txtzip = Tkinter.Entry(g, width = 8)
zipcde = Tkinter.Label(g, width = 8,text = u"邮政编码")
zipcde.pack(side = "left",pady = 8)
txtzip.pack(side = "left")
g.pack(side = "top",fill = "x")

#电话
h = Tkinter.Frame(root)
txtph = Tkinter.Entry(h, width=16)
phone=Tkinter.Label(h, width = 15,text = u"电话")
phone.pack(side = "left",pady = 5)
txtph.pack(side = "left")

#电子邮箱
txtemail = Tkinter.Entry(h, width = 16)
email = Tkinter.Label(h, width = 10,text = "E-mail")
email.pack(side = "left",pady = 8)
txtemail.pack(side = "left")
h.pack(side = "top",fill = "x")
j = Tkinter.Frame(root)
j.pack(side = "left",fill = "y")

#保存按钮
save = Tkinter.Button(j, text = u"保存")
save.pack(pady = 20, padx = 10)    
i = Tkinter.Frame(root)
i.pack(side = "top",fill = "x")

#取消按钮
close = Tkinter.Button(i, text = u"取消",command = root.destroy)
close.pack(side = "right",pady = 10, padx = 75)

root.mainloop()

点F5看看什么样吧。

../_images/gui_tk_TkLayoutManager.png

图2. 新建联系人界面,展示Tkinter的布局管理器

Tkinter的事件处理(Event and Bindings)

上面的TkLayoutManager.py已经是一个完整地添加新的联系人的界面了。可是现在它还不能保存输入的联系人信息。点击“保存”按钮,没有任何反应。我们需要程序对用户输入,点击等动作做出反应。这被称为事件处理。小白还没有接触过事件处理,但是猜测这应该不难。因为他已经做好了界面,以前写的CLI通讯录的保存联系人信息的函数应该改改就可以了。现在唯一需要做的就是,在点击“保存”按钮和保存联系人信息的函数(savefile)之间建议联系。
Tkinter事件处理的方法为:
widget.bind(event, handler)

widget为事件触发者,比如,一个Button。event为事件,handler为处理事件的函数。 小白想,“何不做一个简单的程序,测试一下事件处理吧。”于是,小白修改了一下前面的HelloTkinter.py,使新的程序,可以点击一个按钮后呈现”Hello Tkinter”.代码如下(参见源代码tk/HelloTkinterEventBinding.py)

# -*- coding: utf-8 -*-
"""
Created on Thu Apr 28 14:14:01 2011

@author: He Jibo
hejibo@ueseo.org
University of Illinois

function:
    demo event and bindings in tkinter
    for use of our Real Python bookchpater


"""


import Tkinter

def DisplayHello(event):
    win = Tkinter.Toplevel()
    Tkinter.Label(win,text = "Hello, Tkinter").pack(pady = 15)

    
root = Tkinter.Tk()

button = Tkinter.Button( text = "Click me")
button.bind("<Button-1>", DisplayHello)
button.pack(side = Tkinter.BOTTOM)

root.mainloop()


    

点F5看看什么样吧。

../_images/gui_tk_HelloTkinterEventBinding.png

图3. Hello Tkinter界面,展示Tkinter的事件处理

完善通讯录

对于小白正在做的新建联系人的界面,widget为保存按钮save,event为点击事件,handler就是保存联系人信息的函数savefile。“原来这么简单呀!”小白想,“这应该通过一两行代码就可以完成事件处理了”。小白试了试,应该在TkLayoutManager.py中增加下面这一行代码,save.bind(“<Button-1>”, savefile)这样应该就可以了。

下面,应该修改一下做CLI的通讯录时写的savefile函数,应该就可以了。不过,在保存联系人信息之前,小白还得想办法获取输入框Entry内的信息才行。 这个不难!搜一下“tkinter entry value”,很容易就可以看到如何获取Entry widget的输入值 。 经过搜索,小白发现widgetName.get()就可以得到一个widget的值。不过我们的软件需要支持中文,小白回顾了一下以前第二章时写下的中文处理的笔记,终于解决了中文的问题。widgetName.get().encode(‘utf-8’)就可以得到Entry widget输入的中文内容。

小白在TkLayoutManager.py的基础上修改,为save 按钮增加了事件处理(save.bind(“<Button-1>”, savefile)),然后修改了以前在写CLI通讯录时的savefile函数,很轻松地完成了创建新联系人的软件。 代码如下(源代码请见tk/TkCreateContact.py)。

# -*- coding: utf-8 -*-
"""
Created on Thu Apr 28 11:53:22 2011

@author: He Jibo
hejibo@ueseo.org
University of Illinois

function:
    a workable program for creating new contacts
    for use of our  Real Python bookchpater

    
"""

import Tkinter

FILENAME = "addressfile.txt"


def save_file(self):
    '''
    保存数据到文件
    '''
    text_file = open(FILENAME,"a")
    first = self.txtfirst.get().encode("utf-8")#http://flyantme.blog.163.com/blog/static/7586977520096711026111/
    last = self.txtlast.get().encode("utf-8")
    address = self.txtadd.get().encode("utf-8")
    city = self.txtcity.get().encode("utf-8")
    state = self.txtst.get().encode("utf-8")
    zipcode = self.txtzip.get().encode("utf-8")
    phone = self.txtph.get().encode("utf-8")
    email = self.txtemail.get().encode("utf-8")
    print >>text_file,last,"\t",first,"\t",address,"\t",city,"\t",state,"\t",zipcode,"\t",phone,"\t",email
    text_file.close()
    self.win.destroy()


root = Tkinter.Tk()
#设置软件尺寸
root.geometry("400x230+100+100")
d = Tkinter.Frame(root)


#名
txtfirst = Tkinter.Entry(d, width = 40)
fname = Tkinter.Label(d, width = 15,text = u"名")
fname.pack(side = "left",pady = 8)
txtfirst.pack(side = "left")
d.pack(side = "top",fill = "x")

#姓
e = Tkinter.Frame(root)
txtlast = Tkinter.Entry(e, width = 40)
lname = Tkinter.Label(e, width = 15,text = u"姓")
lname.pack(side = "left",pady = 8)
txtlast.pack(side = "left")
e.pack(side = "top",fill = "x")

#街道
f = Tkinter.Frame(root)
txtadd = Tkinter.Entry(f, width = 40)
address = Tkinter.Label(f, width = 15,text = u"街道")
address.pack(side = "left",pady = 8)
txtadd.pack(side = "left")
f.pack(side = "top",fill = "x")

#市
g = Tkinter.Frame(root)
txtcity = Tkinter.Entry(g, width = 16)
city = Tkinter.Label(g, width = 15,text = u"市")
city.pack(side = "left",pady = 8)
txtcity.pack(side = "left")

#省
txtst = Tkinter.Entry(g, width = 4)
street = Tkinter.Label(g, width = 8,text = u"省")
street.pack(side = "left",pady = 8)
txtst.pack(side = "left")

#邮政编码
txtzip = Tkinter.Entry(g, width = 8)
zipcde = Tkinter.Label(g, width = 8,text = u"邮政编码")
zipcde.pack(side = "left",pady = 8)
txtzip.pack(side = "left")
g.pack(side = "top",fill = "x")

#电话
h = Tkinter.Frame(root)
txtph = Tkinter.Entry(h, width=16)
phone=Tkinter.Label(h, width = 15,text = u"电话")
phone.pack(side = "left",pady = 5)
txtph.pack(side = "left")

#电子邮箱
txtemail = Tkinter.Entry(h, width = 16)
email = Tkinter.Label(h, width = 10,text = "E-mail")
email.pack(side = "left",pady = 8)
txtemail.pack(side = "left")
h.pack(side = "top",fill = "x")
j = Tkinter.Frame(root)
j.pack(side = "left",fill = "y")

#保存按钮
save = Tkinter.Button(j, text = u"保存")
save.pack(pady = 20, padx = 10)    
i = Tkinter.Frame(root)
i.pack(side = "top",fill = "x")

#取消按钮
close = Tkinter.Button(i, text = u"取消",command = root.destroy)
close.pack(side = "right",pady = 10, padx = 75)

root.mainloop()

点F5看看什么样吧。

../_images/gui_tk_TkCreateContact.png

图4. 新建联系人界面,展示Tkinter的事件处理

上面的代码是一个完整地可以创建新联系人的程序。不过小白并不满意。因为这个通讯录还缺少搜索和删除联系人功能。而且代码也没有实现模块化 (下一节,在讲wxPython时,我们会以制作通讯录的主界面为示例。读者也可以在下一节学习如何实现主界面和模块化)。 不过,这些对小白都不算太难了。因为他已经能够完整地写一个tkinter界面程序了。搜索和删除功能,也就是重复上面的,设计界面和实现事件绑定了。事件绑定部分,他可以利用CLI通讯录中的search和deletefile函数。 经过数小时的奋战,小白成功地完成了基于tkinter的通讯录。 他把代码放在了tk/AddressBookGUIzh.py。您也去看看他写的代码吧?

../_images/gui_tk_Addressbookzh.png

图5. 通讯录界面