使用PyGtk和Glade构建应用程序


使用PyGtk和Glade构建应用程序
  
原文

在使用PyGTK和Glade完成了我的第一个例子后, 我决定写一个更加复杂的例子, 但是我写不出一个好的题目来. (注: 我很乐意听到你对任何例子或帖子有任何建议), 于是我决定写一个简单的程序或许会从这里找出一些有用的主题来.
  我的想法是创建一个可以让我记录我喝过酒的种类以及我对它们的喜好程度. 我写想这个程序很久了, 因此我想把学习PyGTK和写它结合起来会是一个好主意.
  我把这个项目命名为PyWine. 本例程的全部代码和Glade文件可以从这里下载.
   

文件:
PyWine.tar.tar
大小:
30KB
下载:
下载
   我们要做的第一件事情是在项目目录下创建一个名为PyWine的新Glade项目. 然后创建一个名为”mainWindow”的窗口, 并把它的标题设置成”PyWine”. 然后像在例程1里那样为它添加一个销毁的信息处理器.
   然后我添加了一个有4行的垂直盒子(Vertical Box), (从上到下): 一行放置菜单栏,一行放工具栏,一行放一个树状或列表视图, 最后一行旋转一个状态栏. 把树状视图命名为”wineView”.

   我们刚添加的树形/列表视图会用来显示酒的信息. 因此我们要做的第一件事就是我们应当允许用户往对话框里添加一种酒. 我们需要使用一个菜单命令和工具栏按钮来完成这个任务.
   首先添加一个工具栏按钮. 这只需要简单的在控件面板里选择”工具栏按钮”控件然后再点击我们往窗口里添加的工具栏. 这应该会往工具栏里添加一个样子很奇怪的按钮. 现在我们需要在”物件标签”(widget tab)来编辑这个工具栏按钮的属性. 我们把它的名字设置成”tbAddWine”, 图标设置成一个带加号的图标. (译注: 下面的图片可能与glade版本相关, 我这的图标名称为gtk-add, 请根据需要自己作少许变更:D).
  

   现在我们要为tbAddWine按钮添加一个点击事件的处理器, 这次我们不使用默认的名字,把它命名为”on_AddWine”.
添加菜单栏
   现在我们要对菜单作一些修改了, 因为我们希望人们既能够通过工具栏按钮或是通过菜单栏添加酒的条目. 那么就点击窗口上的菜单栏然后转到属性窗口中的物件标签页, 然后点”编辑菜单…”按钮来编辑菜单栏. (译注: 这里没有讲怎么往窗口添加菜单栏, 和添加工具栏一样, 从glade的面板里找:D).
    现在点击”添加”按钮来添加一个新的根菜单项, 把它的标签设置成”_Add”. 然后在菜单列表中选择这个”Add”项然后点击”添加子项”(Add Child)按钮, 这就会为Add菜单创建一个子菜单. 把这个新的菜单项的标签设置成”_Wine”并把它的事件处理器设成”on_AddWine”.
   你可能会注意到工具栏按钮Add的点击事件处理器和菜单Add|Wine的点击事件处理器是一样的. 这是因为这两个物件都是要进行同样的任务: 往酒列表中添加酒的条目.

   因此当用户点Add Wine按钮或是从菜单中选择Add|Wine时要发生就是弹出一个可以让用户添加一些有关酒详细信息的对话框. 如果他们最后选择Ok按钮那么就把他们输入的酒的信息添加进列表视图中.
创建对话框
   所以接下来我们要做的是创建一个可以让用户添加酒的对话框. 在这个例子中我们尽量让事情保持简单: 仅仅让用户输入酒的名字, 生产厂家, 装瓶的年代以及葡萄的类别.
   因此我们仅要通过点击Glade面板上的对话框按钮来创建一个新的对话框. 这就会弹出一个新建对话框窗口, 在里面选择包含Cancel和Ok按钮的”标准按钮布局”选项. 把对话框的名字设置成”wineDlg”, 窗口标题设置成”Add Wine”.
接下来我们要用一个表格来布置winedlg中的东西. 向对话框中添加表格(和通常添加物件的方法相同), 并把行数设成4, 列数设置成2. 然后我们就要用标签和文字输入物件来填充表格的空隙直到它看起来像这样:

    我在表格的行间增加了3个像素的间隔, 这个设置可以在表格属性的物件标签页里找到. 如果你正在为怎么选择这个表格而犯愁, 你可以通过选择glade主窗口中的菜单栏”视图 | 显示物件树”(View|Show widget Tree). 或者按下shift键, 然后左键点击表格上的物件, 这会让你在这个对话框中的所有物件间循环.
    现在我们要编辑这个对话框中的所有东西, 把它们命名为: enWine, enWinery, enGrape和enYear.
Python代码
   现在我们要写一些让它工作的代码了. 我们把这个python文件命名为pywine.py, 把它放在/projects/Pywine目录中(和我们创建glade文件的目录相同). 下面是我们要用到的基本代码(大部分都是从上一个例程中拿来的).
#!/usr/bin/env python
import sys
try:
        import pygtk
          pygtk.require("2.0")
except:
          pass
try:
        import gtk
          import gtk.glade
except:
        sys.exit(1)
class pyWine:
        """This is the PyWine application"""
        def __init__(self):
               
                #Set the Glade file
                self.gladefile = "pywine.glade"  
                self.wTree = gtk.glade.XML(self.gladefile, "mainWindow")
                #Create our dictionay and connect it
                dic = {"on_mainWindow_destroy" : gtk.main_quit
                                , "on_AddWine" : self.OnAddWine}
                self.wTree.signal_autoconnect(dic)
               
        def OnAddWine(self, widget):
                """Called when the use wants to add a wine"""
               
                print "OnAddWine"
if __name__ == "__main__":
        wine = pyWine()
        gtk.main()
  在这里多了一点额外的东西. 一个是on_AddWine信号的处理器. 如果你运行这个程序你会注意到当你点击Add Wine按钮或是从菜单栏中选择Add | Wine时会打印出”OnAddWine”. 代码中的新加的另外一个是我们把主窗口的名字传给gtk.glade.XML, 这会让我们只加载这个窗口和它的子窗口.
   下一步是创建一个用来存储酒信息的类:
class Wine:
        """This class represents all the wine information"""
       
        def __init__(self, wine="", winery="", grape="", year=""):
               
                self.wine = wine
                self.winery = winery
                self.grape = grape
                self.year = year
   很简单, 下一步我们需要创建一个给wineDlg使用的类, 我们称它为wineDialog:
class wineDialog:
        """This class is used to show wineDlg"""
       
        def __init__(self, wine="", winery="", grape="", year=""):
       
                #setup the glade file
                self.gladefile = "pywine.glade"
                #setup the wine that we will return
                self.wine = Wine(wine,winery,grape,year)
   下一步我们需要为wineDialog添加一个函数, 它来从glade文件中装载wineDialg物件并显示. 我们还想要返回这个对话框的执行结果, 这会是一个gtk_RESPONSE, 你可以从
PyGTK的网站上
得到更多的信息.
   这个就是run 函数:
def run(self):
        """This function will show the wineDlg"""       
       
        #load the dialog from the glade file          
        self.wTree = gtk.glade.XML(self.gladefile, "wineDlg")
        #Get the actual dialog widget
        self.dlg = self.wTree.get_widget("wineDlg")
        #Get all of the Entry Widgets and set their text
        self.enWine = self.wTree.get_widget("enWine")
        self.enWine.set_text(self.wine.wine)
        self.enWinery = self.wTree.get_widget("enWinery")
        self.enWinery.set_text(self.wine.winery)
        self.enGrape = self.wTree.get_widget("enGrape")
        self.enGrape.set_text(self.wine.grape)
        self.enYear = self.wTree.get_widget("enYear")
        self.enYear.set_text(self.wine.year)       
        #run the dialog and store the response               
        self.result = self.dlg.run()
        #get the value of the entry fields
        self.wine.wine = self.enWine.get_text()
        self.wine.winery = self.enWinery.get_text()
        self.wine.grape = self.enGrape.get_text()
        self.wine.year = self.enYear.get_text()
       
        #we are done with the dialog, destroy it
        self.dlg.destroy()
       
        #return the result and the wine
        return self.result,self.wine
   你会注意到我们装载对话框的方法用的是和装载主窗口一样的方法. 我们调用gtk_glade_XML()并把我们想要装载物件的名称传给它. 这将会自动的显示这个对话框(和装载主窗口的行为相同), 但这样不够好, 我们想让run函数等待用户退出了这个对话框才返回. 要这么做我们需要从物件树里得到这个这个对话框物件(self.dlg = self.wTree.get_widget(“wineDlg”)),然后调用
GTKDialogs 的run函数
. 下面是PyGTK文档对这个函数的描述:
Run()方法会在一个无穷的主循环里阻塞到它收到一个”response”信号或是销毁. 如果这个对话框是被销毁了, run ()方法会返回一个gtk_RESPONSE_NONE; 否则它返回一个在response信号中的响应id. 在进入这个无穷的主循环之前run ()方法会为你调用对话框的gtk_Widget_show()函数. 注意你仍然需要自己负责显示对话框的子窗口.
   在run()方法运行期间, delete事件的默认行为是被禁止的, 如果对话框收到一个delete的事件, 它并不会像窗口通常那样进行销毁, 这时run()方法会返回一个gtk_RESPONSE_DELETE_EVENT. 而且在run ()方法运行的时候对话框是模态的. 你可以通过调用responese()函数产生一个response信号来强制run ()方法在任何时候退出. 在run()方法运行的时候销毁对话框是很糟糕的主意, 因为你后面的代码无法知道对话框是不是已经销毁了.
   在run()方法返回后, 你需要负责对话框的隐藏和销毁.
    Ok按钮会返回gtk_RESPONSE_OK, Cancel按钮会返回gtk_RESPONSE_CANCEL, 大部分情况下我们仅关心对话框返回的wine信息是不是由用户点Ok按钮产生的.
   你还可以看到我们从对话框里得到GTKEntry物件来取/设置它们的文字. 总得来说这个函数还是相当简单的.
树状视图和列表存储(List Stores)
   现在我们有了用户想要往列表中添加的酒, 我们需要把它添加进
gtk_TreeView
中.
   GTKTreeViews的主要特性就是它们按照模型指定的任何方式来显示它们的数据. 数据模型可以用
gtk_ListStore
,
gtk_TreeStore
,
gtk_TreeModelSort
,或是
gtk_GenericTreeModel
. 在本例子中我们将使用 gkt_ListStore.
树状视图与模型的关系有些复杂,但是一旦你可以使用它你就会理解为什么它们是这样的. 很简单的讲模型表现数据,而树状视图则是一个简单的显示数据的方法. 所以对于同一份数据(模型)你可以有多个完全不同的视图. 下面是
GTK+参考手册
中的内容:
  在GTK+中要创建一个树或列表可以使用结合使用GtkTreeModel接口和GtkTreeView物件. 这个物件使用模型/视图/控制器模式设计, 它由以下四个主要部分组成:
树状视图物件(GtkTreeView)
列视图 (GtkTreeViewcolumn)
单元渲染器(GtkCellRenderer等等)
模型接口(GtkTreeModel)
  视图是由前面的三组对象组成, 最后一个是模型. MVC设计模式的一个主要收益是可以使用单个模型创建多个视图. 例如:由文件系统映射的模型(可能由一个文件管理器创建), 可以创建多种视图来显示文件系统的各个部分, 但仅需要在内存中保存一份拷贝
   我们要做的第一件是在自动把词典和物件树连接上后往pyWine类的__init__函数中添加一些代码:
#Here are some variables that can be reused later
self.cWine = 0
self.cWinery = 1
self.cGrape = 2
self.cYear = 3
               
self.sWine = "Wine"
self.sWinery = "Winery"
self.sGrape = "Grape"
self.sYear = "Year"               
                               
#Get the treeView from the widget Tree
self.wineView = self.wTree.get_widget("wineView")
#Add all of the List Columns to the wineView
self.AddWineListColumn(self.sWine, self.cWine)
self.AddWineListColumn(self.sWinery, self.cWinery)
self.AddWineListColumn(self.sGrape, self.cGrape)
self.AddWineListColumn(self.sYear, self.cYear)
    这个代码是相当直观的, 首先我们创建一些类似定义的变量(这样我们在后面就可以很轻松的改变它们)然后我们从物件树中得到gtk_TreeView. 在这之后我们通过调用一个新的函数来往列表中添加我们需要的列. AddWineListColumn是一个很快的小函数,但它能防止我们每次重复写创建列的代码.
def AddWineListColumn(self, title, columnId):
“”"This function adds a column to the list view.
First it create the gtk.TreeViewColumn and then set
some needed properties”"”
column = gtk.TreeViewColumn(title, gtk.CellRendererText()
, text=columnId)
column.set_resizable(True)
column.set_sort_column_id(columnId)
self.wineView.append_column(column)
  这个代码有一点点复杂, 首先我们创建一个使用
gtk_CellRendererText
作为它的
gtk_CellRenderer
的新
gtk_TreeViewColumn
. 这里是一些从
GTK+参考手册
里的较全的信息:
   一旦GtkTreeView物件有了一个模型, 它需要知道如何显示这个模型. 它通过列和单元渲染器来完成这项工作.
单元渲染器是用来使用某种方法在树状模型中绘图这些数据. 在GTK+2.x中有很多单元渲染器, 包括: GtkCellRenderText, GtkCellRendererPixbuf 和GtkCellRendererToggle. 写一个自定义的渲染器也相对简单.
   GtkTreeView使用GtkTreeViewColumn对象来组织在树状视图中纵的列. 它需要知道列的名字用来以标签的形式显示给用户, 使用单元渲染器的类型和对于一个给定的行它需要得到的数据块.
   因此简单的讲我们在创建一个包含特定标题的列, 并指明它将会使用gtk_CellRendererText(来显示简单的文字),然后再告诉它它与模型中的哪一项关联在一起.然后我们把它设置成可以改变大小而且用户可以通过点击列的头来对它进行排序. 在这之后我们把这一列加到视图中.
   好了, 我们完成了模型的创建. 我们要再回到pyWine类的__init__函数里继续:
#Create the listStore Model to use with the wineView
self.wineList = gtk.ListStore(str, str, str, str)
#Attatch the model to the treeView
self.wineView.set_model(self.wineList)
   大体上我们仅仅创建了一个gtk_ListStore并告诉它它有四个子项, 并且它们都是字符串. 然后我们把这个模型与视图绑定起来, 这就是所有我们需要为初始化gtk_TreeView做的.
把所有合并到一起  
我们要做的最后一件事是完成pyWine类中的OnAddWine函数(从菜单或工具栏按钮调用). 这个函数很简单:
OnAddWine(self, widget):
"""Called when the use wants to add a wine"""
#Create the dialog, show it, and store the results
wineDlg = wineDialog();
result,newWine = wineDlg.run()
if (result == gtk.RESPONSE_OK):
"""The user clicked Ok, so let's add this
wine to the wine list"""
self.wineList.append(newWine.getList())
   这里我们创建了一个wineDialog的实例, 然后运行它并把用户输入的酒的信息保存.然后我们检查result是不是gtk_RESPONSE_OK(用户点击了Ok按钮), 如果是我们就把酒的信息添加到gtk_ListStore中, 它会自动的在gtk_TreeView中显示出来因此它们是连接在一起的.
   在wine类中我使用简单的getList函数以使阅读代码变得简单一些:
def getList(self):
        """This function returns a list made up of the
        wine information.  It is used to add a wine to the
        wineList easily"""
        return [self.wine, self.winery, self.grape, self.year]

   到这里这个简单程序就算完成了, 尽管它不保存任何信息或是任何和它类似的信息, 但是它确实描述了一些创建一个完整pyGTK 应用程序的基本步骤.
  完整的代码和Glade文件可以在
这里
下载,你也可心浏览下面的完整程序:
#!/usr/bin/env python
import sys
try:
        import pygtk
          pygtk.require("2.0")
except:
          pass
try:
        import gtk
          import gtk.glade
except:
        sys.exit(1)
class pyWine:
        """This is an Hello World GTK application"""
        def __init__(self):
                       
                #Set the Glade file
                self.gladefile = "pywine.glade"  
                self.wTree = gtk.glade.XML(self.gladefile, "mainWindow")
                       
                #Create our dictionay and connect it
                dic = {"on_mainWindow_destroy" : gtk.main_quit
                                , "on_AddWine" : self.OnAddWine}
                self.wTree.signal_autoconnect(dic)
               
                #Here are some variables that can be reused later
                self.cWine = 0
                self.cWinery = 1
                self.cGrape = 2
                self.cYear = 3
               
                self.sWine = "Wine"
                self.sWinery = "Winery"
                self.sGrape = "Grape"
                self.sYear = "Year"               
                               
                #Get the treeView from the widget Tree
                self.wineView = self.wTree.get_widget("wineView")
                #Add all of the List Columns to the wineView
                self.AddWineListColumn(self.sWine, self.cWine)
                self.AddWineListColumn(self.sWinery, self.cWinery)
                self.AddWineListColumn(self.sGrape, self.cGrape)
                self.AddWineListColumn(self.sYear, self.cYear)
       
                #Create the listStore Model to use with the wineView
                self.wineList = gtk.ListStore(str, str, str, str)
                #Attache the model to the treeView
                self.wineView.set_model(self.wineList)       
               
        def AddWineListColumn(self, title, columnId):
                """This function adds a column to the list view.
                First it create the gtk.TreeViewColumn and then set
                some needed properties"""
                                               
                column = gtk.TreeViewColumn(title, gtk.CellRendererText()
                        , text=columnId)
                column.set_resizable(True)               
                column.set_sort_column_id(columnId)
                self.wineView.append_column(column)
               
        def OnAddWine(self, widget):
                """Called when the use wants to add a wine"""
                #Cteate the dialog, show it, and store the results
                wineDlg = wineDialog();               
                result,newWine = wineDlg.run()
               
                if (result == gtk.RESPONSE_OK):
                        """The user clicked Ok, so let's add this
                        wine to the wine list"""
                        self.wineList.append(newWine.getList())
                               
class wineDialog:
        """This class is used to show wineDlg"""
       
        def __init__(self, wine="", winery="", grape="", year=""):
       
                #setup the glade file
                self.gladefile = "pywine.glade"
                #setup the wine that we will return
                self.wine = Wine(wine,winery,grape,year)
               
        def run(self):
                """This function will show the wineDlg"""       
               
                #load the dialog from the glade file          
                self.wTree = gtk.glade.XML(self.gladefile, "wineDlg")
                #Get the actual dialog widget
                self.dlg = self.wTree.get_widget("wineDlg")
                #Get all of the Entry Widgets and set their text
                self.enWine = self.wTree.get_widget("enWine")
                self.enWine.set_text(self.wine.wine)
                self.enWinery = self.wTree.get_widget("enWinery")
                self.enWinery.set_text(self.wine.winery)
                self.enGrape = self.wTree.get_widget("enGrape")
                self.enGrape.set_text(self.wine.grape)
                self.enYear = self.wTree.get_widget("enYear")
                self.enYear.set_text(self.wine.year)       
       
                #run the dialog and store the response               
                self.result = self.dlg.run()
                #get the value of the entry fields
                self.wine.wine = self.enWine.get_text()
                self.wine.winery = self.enWinery.get_text()
                self.wine.grape = self.enGrape.get_text()
                self.wine.year = self.enYear.get_text()
               
                #we are done with the dialog, destory it
                self.dlg.destroy()
               
                #return the result and the wine
                return self.result,self.wine
               
class Wine:
        """This class represents all the wine information"""
       
        def __init__(self, wine="", winery="", grape="", year=""):
               
                self.wine = wine
                self.winery = winery
                self.grape = grape
                self.year = year
               
        def getList(self):
                """This function returns a list made up of the
                wine information.  It is used to add a wine to the
                wineList easily"""
                return [self.wine, self.winery, self.grape, self.year]               
               
if __name__ == "__main__":
        wine = pyWine()
        gtk.main()