Fc46 Python编程
出自Full Circle 中文项目主页
HOW-TO
Greg Walters撰
Python编程——第20部分
欢迎回来。本期我们将要再次介绍图形界面编程,不过这次我们转而使用pyGTK库。我们不会涉及图形界面的设计,而是单纯地使用这个图形库。
使用新立得来安装python-gtk2、python-gtk2-tutorial以及python-gtk2-doc。
让我们直接着手使用pyGTK来完成我们的第一个程序,如上图所示。
接下来,我们将着手编写这个简单的代码。在第3行有一个新的指令。“pygtk.require('2.0')”这行表明只有当pygtk的版本号至少为2.0时我们的应用程序才可运行。在__init__例程中,我们将一个窗口赋给self.window这个变量(第8行),然后显示这个窗口。别忘了每当我们实例化一个类的时候,__init__例程就立刻执行了(第13行)。将这段代码保存为“simple1.py”。
在终端中运行这个程序,你会看到一个简单的窗口在你桌面的某个位置显示出来了。在我的电脑上,它出现在我的桌面的左上角。如果要关闭这个程序,你必须在终端中按下Ctrl-C。为什么呢?这是因为我们还没有加入任何用于销毁并终止应用程序的代码。这也是我们下一步将要进行的工作。将以下这行代码添加到self.window.show()这行前面。
self.window.connect("delete_event", self.delete_event)
然后在gtk.main()这个调用后面,加入以下例程。
def delete_event(self, widget, event, data=None):
gtk.main_quit()
return False
将你的程序保存为“simple2.py”,并再次在终端中运行它。现在当你按下标题栏的“X”的时候,程序便会退出了。这是由于什么原因呢?我们所添加的第一行(self.window.connect)将delete事件连接到一个回调函数上,在我们这次的代码中,回调函数为self.delete_event。在向系统返回False的同时,它也从系统内存中销毁了我们的窗口。
现在,虽然我并不了解你的喜好,但我倾向于让我的程序在屏幕的中央打开,而不是在某个随机的小角落,导致它有可能被用户所忽视。让我们修改一下代码使得它具备这个功能吧。我们所需要做的是在__init__函数的self.window.connect这行之前加入以下代码:
self.window.set_position(gtk.WIN_POS_CENTER)
如你所能预料的,这行代码将窗口位置设置为屏幕的中央。将它保存为“simple3.py”并运行。
现在看起来好多了,但是我们的窗口上却空空如也。所以,让我们加上一个控件(widget)吧。如果你还记得很久以前我们制作“Boa Constructor”的那期,你就应该知道控件被定义为我们能添加到窗口上来执行操作的控制部件。最简单的一个控件便是按钮了。我们将在之前代码的__init__例程的中的self.window.connect这行之后加入以下的代码。
self.button = gtk.Button("Close Me")
self.button.connect("clicked",self.btn1Clicked,None)
self.window.add(self.button)
self.button.show()
第一行定义了一个按钮以及显示在上面的文本。下一行定义了连接到点击事件的函数。第三行将按钮添加到了窗口上,第四行将按钮显示在窗口上。看一下self.button.connect这行,你会发现这里有三个参数。第一个参数是我们希望连接到的事件,第二个是事件被激发时将要被调用的例程(在我们这个例子中为self.btn1Clicked),第三个参数为我们希望传去第二个参数所指定的例程的参数(如果有的话)。
接下来,我们需要创建self.btn1Clicked例程。在self.delete_event例程后加入以下代码。
def btn1Clicked(self,widget,data=None):
print "Button 1 clicked"
gtk.main_quit()
如你所见,这个例程并没有做太多的事情。它仅仅是在终端中输出“Button 1 clicked”,并调用gtk.main_quit()例程。这个例程会关闭窗口并终止程序,就好比你在标题栏上点击了“X”。再一次,将代码保存为“simple4.py”,并在终端中运行。你会发现我们居中的窗口上有一个写着“Close me”的按钮。点击它,然后如我们所设计的那样,程序关闭了。然而,注意,这次我们的窗口比上次simple3.py运行时显示的窗口小很多。你可以重新调整程序窗口的大小,但是按钮的大小也随之被改变了。这是为什么呢?我们仅仅是将一个按钮塞进了窗口,整个窗口便自动调整大小以适应控件的大小。
在一定程度上,我们违反了图形界面程序的设计原则。我们直接将按钮放置在了窗体上而没有使用一个容器。回忆我们第一期图形界面编程时所做的那个“BoaConstructor”,我们使用“sizer boxes”(容器)来存放我们的控件。就算我们只有一个控件,我们也应当这样做。在我们的下个例子中,我们将要添加一个HBox(水平容器)来存放我们的按钮,并添加一个新的按钮。如果我们需要一个垂直的容器,我们就选用VBox。
首先,我们使用“simple4.py”作为我们的基础代码。删掉所有位于self.window.connect和self.window.show这两行之间的内容。我们将要在这里添加新的代码。用于创建HBox和我们第一个按钮的代码如下:
self.box1 = gtk.HBox(False,0)
self.window.add(self.box1)
self.button = gtk.Button("Button 1")
self.button.connect("clicked",self.btn1Clicked,None)
self.box1.pack_start(self.button,True,True,0)
self.button.show()
让我们分析一下这段代码,首先我们创建了一个HBox,并将其命名为self.box1。我们传入HBox的参数是homogeneous(True或False),以及一个间隔距离。
HBox = gtk.HBox(homogeneous=False, spacing=0)
homogeneous参数决定容器中的控件是否具有相同的尺寸(如果是HBox,那么就是同宽,如果是VBox,那么就是同高),在这个例子中,我们传入了False,并设置间距为0。接下来,我们将容器添加到窗口上。现在我们如之前一样创建按钮,并将按键事件连接到我们的例程上。
现在,我们要接触一个新的命令。self.box1.pack_start这个命令被用来将按钮添加到容器中(HBox)。我们使用这条命令而不是self.window.add用来在容器中添加控件。
box.pack_start(widget,expand=True, fill=True, padding=0)
pack_start命令有如下参数:第一个是widget(控件),接着是expand(扩展)(True或False),然后是fill(填充)(True或False),以及padding value(间隙值)。容器的spacing(间距)即容器中不同控件之间的空白,而padding(间隙)则是控件左边及右边的空白。Expand参数可以让你指定是否让容器中的控件占据容器中的所有多余空间(True),还是让容器自动变小以适应控件的大小。Fill(填充)参数仅在expand参数为True时才有效。最后我们需要显示按钮,接下来的代码则是为了显示第二个按钮。
self.button2 = gtk.Button("Button 2")
self.button2.connect("clicked",self.btn2Clicked,None)
self.box1.pack_start(self.button2,True,True,0)
self.button2.show()
self.box1.show()
注意这段代码与第一个按钮控件的代码几乎一样。最后一行代码将box显示出来。现在,我们将要添加self.btn2Clicked这个例程。在self.btn1Clicked例程后面,添加以下代码:
def btn2Clicked(self,widget,data=None):
print "Button 2 clicked"
并在btn1Clicked例程里,注释掉下面这行:
gtk.main_quit()
我们希望两个按钮能在不关闭窗口的情况下在被按下时打印相对应的“Button X clicked”。
将这段代码保存为"simple4a.py"。在终端中运行,你会看到一个居中的窗口,窗口上有分别写着"Button 1"和"Button 2"的两个按钮(在窗口的边沿上)。按下它们,你会发现他们如我们所说的那样,正确的对事件作出了响应。现在,在关闭窗口前,改变窗口的大小(拖拽窗口的右下角),并观察按钮是如何随着窗口大小的改变而自动地增大或缩小。
为了了解expand参数,在代码中将self.box1.pack_start的参数从True改为False。重新运行你的程序并看看发生了什么。这次,窗口乍看上去与之前一样,但是当你改变窗口大小的时候,按钮的宽度确没有改变,当你伸展窗口的时候,窗口的右边有一片空白。接下来,将expand参数重新改回True,并将fill参数设为False。重新运行程序,你会发现按钮的宽度不变,但在改变窗口大小的时候,按钮的左右都有多出了空白区域。记住,在expand参数为False的时候,fill参数什么也不做。
另一种排列控件的方法是使用table。许多时候,如果你的所有控件都适合用一个网格状的结构来表示的话,那么table(表格)就是你最好(和最容易)的选择。你可以将table想象成一个电子表格,其中每行每列的网格中都存有控件。每个控件可以占据一个或更多的格子,取决于你程序的需要。或许下图可以帮助你理解这些。这里是一个2x2的网格。
0 1 2 0+-----------+-----------+ | | | 1+-----------+-----------+ | | | 2+-----------+-----------+
在第一行中,我们将放置两个按钮,一个在第一列,一个在第二列。在第二行,我们会放置一个横跨两行的按钮。像这样:
0 1 2 0+-----------+-----------+ | Button 1 | Button 2 | 1+-----------+-----------+ | Button 3 | 2+-----------+-----------+
为了设置一个表格,我们创建一个table对象并将其加入到窗口中。创建table的调用是:
Table = gtk.Table(rows=1,columns=1,homogeneous=True)
如果homogeneous标志设为True,那么整个table的大小将自动适应table中最大的控件的大小。如果设置为False,那么table的大小将会由一行中最高的以及最宽的控件大小所决定。然后我们创建一个控件(像上面的按钮),并将按钮放置到table中合适的行和列。放置控件的调用如下:
table.attach(widget,left point,right point,top point,bottom point,xoptions=EXPAND|FILL,yoptions=EXPAND|FILL, xpadding=0,ypadding=0)
强制需要的参数为前五个。为了将按钮放入table的第0行第0列,我们需要使用如下命令:
table.attach(buttonx,0,1,0,1)
如果需要将其放入第0行第1列(记住它是以0为基数的),像上面的button 2那样,那么就调用:
table.attach(buttonx,1,2,0,1)
希望你已经明白了这些。我们开始编写一些代码吧,你会明白的更透彻。第一部分与之前一样:
# table1.py
import pygtk
pygtk.require('2.0')
import gtk
class Table:
def __init__(self):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.set_position(gtk.WIN_POS_CENTER)
self.window.set_title("Table Test 1")
self.window.set_border_width(20)
self.window.set_size_request(250, 100)
self.window.connect("delete_event", self.delete_event)
在我们继续编写之前,这里有一些需要讨论的新玩意。第九行将窗口标题设为“Table Test 1”。在放置任何控件前,我们使用“set_border_width”这个调用给整个窗口加上20像素的边框。最后,我们使用“set_size_request”函数来强制将窗口大小设为250x100像素。懂了吗?现在,我们创建一个table并把它加到窗口中。
table = gtk.Table(2, 2, True) # Create a 2x2 grid self.window.add(table)
接下来,我们创建第一个按钮,设置事件回调,并加入到table中,然后显示。
button1 = gtk.Button("Button 1")
button1.connect("clicked",self.callback,"button 1")
table.attach(button1,0,1,0,1)
button1.show()
现在是按钮2……
button2 = gtk.Button("Button 2")
button2.connect("clicked",self.callback,"button 2")
table.attach(button2,1,2,0,1)
button2.show()
几乎与按钮1相同,但是注意table.attach这个调用的不同。同时注意我们用于事件处理的例程是“self.callback”,这个例程对于两个按钮是一样的。差不多就是这样了,待会你会了解我们刚才的代码是做了哪些事情。
现在是按钮3。这是“退出”按钮。
button3 = gtk.Button("Quit")
button3.connect("clicked",self.ExitApp,"button 3")
table.attach(button3,0,2,1,2)
button3.show()
最后,显示table以及窗口,同时这里有main例程以及我们之前使用的销毁例程。
table.show()
self.window.show()
def main(self):
gtk.main()
def delete_event(self, widget, event, data=None):
gtk.main_quit()
return False
现在这部分就有趣了。对于按钮1和按钮2,我们设置事件处理例程为“self.callback”。这里是实现的代码:
def callback(self,widget,data=None):
print "%s was pressed" % data
将会发生的是:当用户按下按钮的时候,按键事件将被激发,然后我们设置的事件数据也随之而来。对于按钮1,发送的数据是“button 1”,而对于按钮2则是“button 2”。我们这里所做的是在终端中打印“button x was pressed”。我相信你会发现当与一个设计良好的if | elif | else例程结合的时候,这会是一个非常有用的工具。 现在进行收尾工作,我们必须定义一个“ExitApp”例程,用于处理“Quit”按钮被按下的情况。
def ExitApp(self, widget, event, data=None):
print "Quit button was pressed"
gtk.main_quit()
现在是最后的main代码:
if __name__ == "__main__":
table = Table()
table.main()
将上面所有代码整合到一个名为“table1.py”的程序。在终端中运行它。
概括一下,当我们想要使用pyGTK来创建一个GUI程序的时候,步骤如下:
- 创建窗口
- 创建HBox,VBox或table来放置控件
- Pack或attach控件(根据具体的容器是box还是table)
- 显示控件
- 显示box或table
- 显示窗口
现在我们已经具备了足够多的工具和知识了。所有的代码可以在Pastebin网站上找到:http://fullcirclemagazine.pastebin.com/wnzRsXn9 。下期再见。
Text454: # simple.py
import pygtk
pygtk.require('2.0')
import gtk
class Simple:
def __init__(self):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.show()
def main(self):
gtk.main()
if __name__ == "__main__":
simple = Simple()
simple.main()