Python有著豐富的界面開發庫,除了缺省安裝的Tkinter以外,wxPython、pyQt4等都是非常優秀的界面開發庫。但是它們有一個共同的問題:需要開發者掌握眾多的API函數,許多細節,例如配置控件的屬性、位置以及事件響應都需要開發者一一處理。
在開發科學計算程序時,我們希望快速實現一個夠用的界面,讓用戶能夠交互式的處理數據,而又不希望在界面制作上花費過多的精力。以traits為基礎、以Model-View-Controller為設計思想的TraitUI庫就是實現這一理想的最佳伴侶。
TraitsUI是一套建立在Traits庫基礎上的用戶界面庫。它和Traits緊密相連,如果你已經設計好了一個繼承于HasTraits的類的話,那么直接調用其configure_traits方法,系統將會使用TraitsUI自動生成一個界面,以供用戶交互式地修改對象的trait屬性。讓我們先來看下面這個例子:
from enthought.traits.api import HasTraits, Str, Int
class SimpleEmployee(HasTraits):
first_name = Str
last_name = Str
department = Str
employee_number = Str
salary = Int
sam = SimpleEmployee()
sam.configure_traits()
此程序創建一個SimpleEmployee類的對象sam,然后調用sam.configure_traits顯示出如下的缺省界面:
自動生成的SimpleEmployee類的對話框
可以看到此界面是自動根據trait屬性生成。所有的屬性都以文本框的形式編輯,并且每個文本框前面都有一個文字標簽,其文字根據trait屬性名自動生成:第一個字母變為大寫,所有的下劃線變為空格。最下面為我們提供了OK和Cancel按鈕以確定或者取消對trait屬性值的修改。
salary屬性雖然和其它屬性一樣都采用文本框進行編輯,但是由于salary屬性定義為Int類型,所以它將檢查非法輸入,并以紅色背景警示,鼠標左擊Salary標簽,將彈出salary屬性相關的詳細說明,由于我們沒有設置此說明,系統缺省給出salary所能接受的值的類型。
界面中的每個屬性編輯器都有詳細說明,并且能檢查非法輸入
我們連一行界面相關的代碼都沒有寫,卻能得到這樣一個已經夠實用的界面,應該還是很令人滿意的吧。為了人工控制界面的設計和布局,就需要我們添加自己的代碼了。
下面的程序在前面的基礎上自定義了一個視圖對象view1,然后將此對象傳遞給configure_traits方法,于是界面就按照視圖中描述的那樣生成了:
# -*- coding: utf-8 -*-
from enthought.traits.api import HasTraits, Str, Int
from enthought.traits.ui.api import View, Item
class SimpleEmployee(HasTraits):
first_name = Str
last_name = Str
department = Str
employee_number = Str
salary = Int
view1 = View(
Item(name = 'department', label=u"部門", tooltip=u"在哪個部門干活"),
Item(name = 'last_name', label=u"姓"),
Item(name = 'first_name', label=u"名"))
sam = SimpleEmployee()
sam.configure_traits(view=view1)
選擇后臺界面庫
用traits.ui庫創建的界面可以選擇后臺界面庫,目前支持的有qt4和wx兩種。在啟動程序時添加 -toolikt qt4 或者 -toolikt wx 選擇使用何種界面庫生成界面。本文中全部使用wx作為后臺界面庫。
通過label和tooltip手工指定屬性編輯器的標簽和說明
有關界面視圖的對象都在traits.ui庫中,所以首先從其中載入View和Item。View用來生成視圖,而Item則用來描述視圖中的項目(控件)。程序中,用Item依次創建三個視圖項目,都作為參數傳遞給View,于是所生成的界面中按照參數的順序顯示控件,而不是按照trait屬性名排序了。
Item對象是視圖的基本組成單位,每個Item描述界面中的中的一個控件,通常都是用來顯示HasTraits對象中的某一個trait屬性。每個Item由一系列的關鍵字參數來進行配置,這些參數對Item的內容、表現以及行為進行描述。其中最重要的一個參數就是name。我們看到name參數的值都配置為SimpleEmployee類的trait屬性名,于是Item就知道到哪里去尋找真正要顯示的值了。可以看出視圖與數據是通過屬性名聯系起來的。剩下的兩個參數label和tooltip設置Item在界面中的一些顯示相關的屬性。Item對象還有很多屬性其它屬性,請參考TraitsUI的用戶手冊,或者在iPython中輸入Item??直接查看其源代碼。如果你查看了Item的源代碼的話,你就會發現,原來Item的這些屬性也都是用trait定義的:
class Item ( ViewSubElement ):
""" An element in a Traits-based user interface.
"""
# Trait definitions:
# A unique identifier for the item. If not set, it defaults to the value
# of **name**.
id = Str
# User interface label for the item in the GUI. If this attribute is not
# set, the label is the value of **name** with slight modifications:
# underscores are replaced by spaces, and the first letter is capitalized.
# If an item's **name** is not specified, its label is displayed as
# static text, without any editor widget.
label = Str
# Name of the trait the item is editing:
name = Str
除了Item之外,TraitsUI庫還定義了下面幾個Item的子類:
這些類用來協助View的布局,因此不需要和某個trait屬性關聯。
前面的例子中,我們通過把三個Item對象傳遞給View,創建了一個控件垂直排列的布局。然而在真正的界面開發中,需要更加高級的布局方式,例如,將一組相關的元素組織在一起,放到一個組中,我們可以為此組添加標簽,定義組的幫助文本,通過設置組的屬性使組類的元素同時有效或無效。在TraitUI中,這樣的組的功能通過Group對象實現,讓我們來修改一下前面的例子:
# -*- coding: utf-8 -*-
from enthought.traits.api import HasTraits, Str, Int
from enthought.traits.ui.api import View, Item, Group
class SimpleEmployee(HasTraits):
first_name = Str
last_name = Str
department = Str
employee_number = Str
salary = Int
bonus = Int
view1 = View(
Group(
Item(name = 'employee_number', label=u'編號'),
Item(name = 'department', label=u"部門", tooltip=u"在哪個部門干活"),
Item(name = 'last_name', label=u"姓"),
Item(name = 'first_name', label=u"名"),
label = u'個人信息',
show_border = True
),
Group(
Item(name = 'salary', label=u"工資"),
Item(name = 'bonus', label=u"獎金"),
label = u'收入',
show_border = True
)
)
sam = SimpleEmployee()
sam.configure_traits(view=view1)
此程序的運行效果如下:
分標簽頁顯示兩個Group的內容
我們分別創建兩個Group傳遞給View,每個Group中仍然通過Item創建控件,通過Group的關鍵字參數指定其label和show_border屬性。由于View中的所有內容都是Group,它自動地將兩個Group放到Tab中,對兩個Group進行分標簽顯示。
如果我們希望能同時看到兩個Group的話,可以另外再創建一個Group將這兩個Group包括起來:
view2 = View( Group( view1.content ) )
這里我們創建視圖view2,它包括一個Group,此Group的內容則直接使用view1的內容(也就是那兩個Group)。當然也可以把view1中的內容復制進去:
view2 = View(Group(
Group(
Item(name = 'employee_number', label=u'編號'),
Item(name = 'department', label=u"部門", tooltip=u"在哪個部門干活"),
Item(name = 'last_name', label=u"姓"),
Item(name = 'first_name', label=u"名"),
label = u'個人信息',
show_border = True
),
Group(
Item(name = 'salary', label=u"工資"),
Item(name = 'bonus', label=u"獎金"),
label = u'收入',
show_border = True
)
))
然后我們將view2傳遞給configure_traits,用view2顯示界面:
sam.configure_traits(view=view2)
豎排顯示兩個Group的內容
在創建Group時,我們可以通過設置其orientation和layout等屬性,改變Group的內容呈現方式。由于某些設置會經常用到,因此還提供了專門的Group子類重載這些屬性的缺省值。例如下面是從Group類繼承的HSplit類的代碼:
class HSplit ( Group ):
# ... ...
layout = 'split'
orientation = 'horizontal'
HSplit對象將其所包括的內容按照水平排列,并且在每兩個子內容之間添加一個可調整的分隔條,HSplit和如下的代碼是等價的:
Group( ... , layout = 'split', orientation = 'horizontal')
為了正確顯示分隔條,其子內容中需要有一個具有scrollable屬性,如下面的代碼(省略Item定義等部分)所示:
view1 = View(
HSplit(
VGroup(
... ...,
scrollable = True
),
VGroup(
... ...
),
),
resizable = True,
width = 400,
height = 150
)
帶分隔條的橫排顯示兩個Group的內容
下面是Group的各種子類和其對應的Group屬性配置:
HGroup : 內容水平排列:
Group(orientation= 'horizontal')
HFlow : 內容水平排列,當超過水平寬度時,將自動換行:
Group(orientation= 'horizontal, layout='flow', show_labels=False)
HSplit : 內容水平分隔,中間插入分隔條:
Group(orientation= 'horizontal', layout='split')
Tabbed : 內容分標簽頁顯示:
Group(orientation= 'horizontal', layout='tabbed)
VGroup : 內容垂直排列:
Group(orientation= 'vertical')
VFlow : 內容垂直排列,當超過垂直高度時,將自動換列:
Group(orientation= 'vertical', layout='flow', show_labels=False)
VFold : 內容垂直排列,可折疊 :
Group(orientation= 'vertical', layout='fold', show_labels=False)
VGrid : 按照兩列的網格進行垂直排列 :
Group(orientation= 'vertical', columns=2)
VSplit : 內容垂直排列,中間插入分隔條:
Group(orientation= 'vertical', layout='split')
前面介紹了如何使用Item和Group等類組織窗口界面中的內容,這一節我們來看看如何配置窗口本身的屬性。
通過kind屬性可以修改View對象的顯示類型:
其中 'modal', 'live', 'livemodal', 'nonmodal' 四種類型的View都將采用窗口顯示其內容。所謂模式窗口,表示此窗口關閉之前,程序中的其它窗口都不能被激活。而即時更新則是指當窗口中的控件內容改變時,修改會立即反應到窗口所對應的模型數據上,非即時更新的窗口則會復制模型數據,所有的改變在模型副本上進行,只有當用戶確定修改(通常通過OK或者Apply按鈕)時,才會修改原始數據。
'wizard'由一系列特定的向導窗口組成,屬于模式窗口,并且即時更新數據。
'panel'和'subpanel' 則是嵌入到窗口中的面板,panel可以擁有自己的命令按鈕,而subpanel則沒有命令按鈕。
在對話框中經常可以看到 OK, Cacel, Apply 之類的按鈕,我們稱之為命令按鈕,它們完成所有對話框窗口都共同的操作。在TraitsUI中,這些按鈕可以通過View對象的buttons屬性進行設置,其值為要顯示的按鈕列表。
TraitsUI定義了UndoButton, ApplyButton, RevertButton, OKButton, CancelButton等六個標準的命令按鈕,每個按鈕對應一個名字,在指定buttons屬性時,可以使用按鈕的類名或者其對應的名字。與按鈕類對應的名字就是類名除去Button,例如UndoButton對應為"Undo"。
在 enthought.tratis.ui.menu 中還預定義了一些命令按鈕列表,方便直接使用:
OKCancelButtons = ``[OKButton, CancelButton ]``
ModalButtons = ``[ ApplyButton, RevertButton, OKButton, CancelButton, HelpButton ]``
LiveButtons = ``[ UndoButton, RevertButton, OKButton, CancelButton, HelpButton ]``