用View定义界面

HasTraits的派生类用Trait属性保存数据,它相当于MVC模式中的模型(Model)。当没有指定界面显示方式时,Traits库会自动创建一个缺省的界面。我们可以通过视图(View)对象为模型设计更加实用的界面。

外部视图和内部视图

下面是用视图对象定义界面的完整程序,【图:使用视图对象描述界面】显示其界面截图。

07-traitsui/traitsUI_simple_view.py

使用视图对象描述界面

from enthought.traits.api import HasTraits, Str, Int
from enthought.traits.ui.api import View, Item #

class Employee(HasTraits):
    name = Str
    department = Str
    salary = Int
    bonus = Int

    view = View( #
        Item('department', label=u"部门", tooltip=u"在哪个部门干活"), #
        Item('name', label=u"姓名"),
        Item('salary', label=u"工资"),
        Item('bonus', label=u"奖金"),
        title = u"员工资料", width=250, height=150, resizable=True #
    )

if __name__ == "__main__":
    p = Employee()
    p.configure_traits()
/tech/static/books/scipy/_images//traitsUI_simple_view.png

使用视图对象描述界面

此程序在模型类Employee的基础之上,添加了界面显示相关的代码。❶界面相关的内容都在TraitsUI库中,这里从其中载入View和Item。View是描述界面的视图类,Item是描述界面中的控件和模型对象的Trait属性之间关系的类。

❷在Employee类下创建了一个View对象,其中为Employee类的每个Trait属性创建了对应的Item对象。❸创建多个Item对象,并作为参数传递给View()。Item对象是视图的基本组成单位,每个Item对象描述界面中的一个编辑器,这些编辑器用于编辑模型对象中对应的Trait属性的值。界面中的编辑器按照Item对象传递给View()的先后顺序显示,而不再按照Traits属性名排序了。Item对象有许多参数,它们对Item对象的内容、表现以及行为进行描述。第一个参数指定与编辑器对应的Trait属性名,label和tooltip参数设置编辑器的标签和提示文本。Item对象还有很多其它属性,请读者参考TraitsUI的用户手册,或者在IPython中输入Item??直接查看其源代码。下面是Item类的部分源程序,Item类从HasTraits继承,因此它的属性都是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的几个派生类:Label、Heading和Spring。它们只用于辅助界面布局,因此不需要和模型对象的Trait属性关联。

❹View类也从HasTraits继承,可以直接在创建View对象时,通过关键字参数设置其Trait属性。title属性为窗口标题栏中的文字,width和height属性为窗口的大小,resizable属性为True表示窗口的大小可变。

同一个模型对象可以通过多个视图对象用不同的界面显示,下面看一个例子:

07-traitsui/traitsUI_views.py

使用多个视图对象

from enthought.traits.api import HasTraits, Str, Int
from enthought.traits.ui.api import View, Group, Item #

g1 = [Item('department', label=u"部门", tooltip=u"在哪个部门干活"), #
      Item('name', label=u"姓名")]
g2 = [Item('salary', label=u"工资"),
      Item('bonus', label=u"奖金")]

class Employee(HasTraits):
    name = Str
    department = Str
    salary = Int
    bonus = Int

    traits_view = View( #
        Group(*g1, label = u'个人信息', show_border = True),
        Group(*g2, label = u'收入', show_border = True),
        title = u"缺省内部视图")

    another_view = View( #
        Group(*g1, label = u'个人信息', show_border = True),
        Group(*g2, label = u'收入', show_border = True),
        title = u"另一个内部视图")

global_view = View( #
    Group(*g1, label = u'个人信息', show_border = True),
    Group(*g2, label = u'收入', show_border = True),
    title = u"外部视图")

p = Employee()

# 使用内部视图traits_view
p.edit_traits() #

# 使用内部视图another_view
p.edit_traits(view="another_view") #

# 使用外部视图view1
p.configure_traits(view=global_view) #

❶从TraitsUI库载入Group类,用Group对象可以对界面中的编辑器进行分组。为了后续定义视图对象的程序更加简洁,❷程序中定义了两个全局列表g1和g2,它们的元素都是Item对象。

❸❹在Employee类内部用View()定义了两个视图对象:traits_view和another_view。而❺定义了一个全局的视图对象:global_view。在定义视图对象时,用Group对与定义界面上的编辑器的Item对象进行分组。

值得注意的是,Employee类中定义的两个视图对象既不是类的属性,也不是实例的属性。这些内部视图对象会放到Employee类的__view_traits__属性中。__view_traits__属性是一个ViewElements对象,它的content属性是保存所有内部视图的字典:

>>> run traitsUI_views.py
>>> Employee.__view_traits__.content.keys()
['another_view', 'traits_view']

❻当调用edit_traits()显示界面时,缺省使用模型类内部定义的缺省视图对象traits_view生成界面。❼使用view参数可以指定显示界面时所使用的内部视图对象的名称。❽也可以直接将视图对象传递给view参数,这样可以使用在模型类外定义的视图对象生成界面。【图:使用外部视图和内部视图定义界面的显示】显示了用三种视图对象所生成的界面。

/tech/static/books/scipy/_images//traitsUI_views.png

使用外部视图和内部视图定义界面的显示

edit_traits()和configure_traits()一样用于生成界面,它们的区别在于edit_traits()显示界面之后不进入后台界面库的消息循环,因此如果直接运行只调用edit_traits()的程序,界面将在显示之后立即关闭,程序的运行也随之结束。而在configure_traits()中将进入消息循环,直到用户关闭所有窗口。因此通常主界面窗口或者模式对话框[1]使用configure_traits()显示,而无模式窗口或对话框则用edit_traits()显示。

选择后台界面库

用TraitsUI库创建的界面可以选择后台界面库,目前支持的有qt4和wx两种。在启动程序时添加“-toolkit qt4”或者“-toolkit wx”选择使用何种界面库生成界面。本书中全部使用wx作为后台界面库。

在本节的例子中,Employee类用于保存数据,因此它属于MVC模式中的模型(Model),而View对象定义了Employee的界面显示部分,它属于视图(View)。通过将视图对象传递给模型对象的configure_traits()方法,将模型对象和视图对象联系起来。在定义编辑器的Item对象中,不直接引用模型对象的属性,而是通过属性名和模型对象进行联系。这样在模型和视图之间的耦合性很弱,只需要属性名匹配,同一个视图对象可以运用到不同的模型对象之上。

有时候我们希望模型类知道如何显示它自己,这时可以在模型类内部定义视图。在模型类中定义的视图可以被其派生类继承,因此派生类能使用父类的视图。在调用configure_traits()时如果不设置view参数,将使用模型对象内部的缺省视图对象生成界面。如果在模型类中定义了多个视图对象,则缺省使用名为traits_view的视图对象。

Footnotes

多模型视图

在上节的例子中,一个模型可以对应多个视图。同样的,使用一个视图可以将多个模型对象的数据显示在一个界面窗口中。下面是用一个视图对象同时显示多个模型对象的例子。程序的运行界面如【图:用一个视图同时显示多个模型对象】所示。

07-traitsui/traitsUI_multi_models.py

用一个视图同时显示多个模型对象

/tech/static/books/scipy/_images//traitsUI_multi_models.png

用一个视图同时显示多个模型对象

对于前面的模型类Employee,我们可以设计如下的复合视图对象comp_view,它能同时显示两个Employee对象:

comp_view = View(
    Group(
        Group(
            Item('p1.department', label=u"部门"),
            Item('p1.name', label=u"姓名"),
            Item('p1.salary', label=u"工资"),
            Item('p1.bonus', label=u"奖金"),
            show_border=True
        ),
        Group(
            Item('p2.department', label=u"部门"),
            Item('p2.name', label=u"姓名"),
            Item('p2.salary', label=u"工资"),
            Item('p2.bonus', label=u"奖金"),
            show_border=True
        ),
        orientation = 'horizontal'
    ),
    title = u"员工对比"
)

注意Item对象的第一个参数不是简单的模型对象的属性名,它同时设置了Item对象的两个属性:object和name。例如参数”p1.department”将设置Item对象的object属性为”p1”,name属性为”department”。object属性告诉Item对象如何找到模型对象,而name属性则告诉Item对象如何找到模型对象中与其对应的属性。

接下来,下面的程序生成组成模型对象的两个Employee对象:employee1和employee2:

employee1 = Employee(department = u"开发", name = u"张三", salary = 3000, bonus = 300)
employee2 = Employee(department = u"销售", name = u"李四", salary = 4000, bonus = 400)

在显示界面时,使用context参数将包含两个模型对象的字典传递给configure_traits():

HasTraits().configure_traits(view=comp_view, context={"p1":employee1, "p2":employee2})

通过context参数所传递的实际上是视图所对应的模型。这里的模型对象是一个字典,它的键和的Item对象的object属性的值相同。由于已经通过context参数传递了模型对象,因此configure_traits()方法原本所属的对象将不会被用作界面的模型对象。这里直接创建一个临时的HasTraits对象,然后调用其configure_traits()方法。

如果读者认为这种写法有些取巧,我们可以直接调用视图对象的ui()方法显示界面,它的参数就是界面所要显示的模型对象。由于ui()和edit_traits()一样[2] 不会开始界面库的消息循环,因此需要在运行ui()之后添加开始消息循环的代码,下面的消息循环代码支持所有的后台界面库。

comp_view.ui({"p1":employee1, "p2":employee2})

from enthought.pyface.api import GUI
GUI().start_event_loop() # 开始后台界面库的消息循环

如果读者只需要用wx库显示界面,也可以用下面的代码运行wx库的消息循环:

import wx
wx.PySimpleApp().MainLoop() # 开始wx的消息循环

Footnotes

[2]实际上,edit_traits()会调用视图对象的ui()显示界面。

Group对象

在前面例子的视图定义中,我们通过Group对象将一组相关的Item对象组织在一起。本节详细介绍如何使用Group对象组织界面。

在程序“traitsUI_views.py”中,View对象中直接放置了多个Group对象,其效果如【图:使用外部视图和内部视图定义界面的显示】所示,它使用标签页的形式显示在View下定义的多个Group对象。

如果我们希望能同时看到两个Group,可以像程序“traitsUI_multi_models.py”一样,再创建一个Group将两个Group包括起来,其效果如【图:用一个视图同时显示多个模型对象】所示。由于外层的Group的orientation属性为’horizontal’,因此它内部的两个Group对象将水平方向排列。下面的代码显示View对象中的Group的嵌套关系:

View(
    Group(
        Group(...),
        Group(...),
        orientation = 'horizontal'
    )
)

在创建Group时,可以通过设置其orientation和layout等属性,改变Group的内容呈现方式。由于某些设置会经常用到,因此TraitsUI还提供了几个Group的派生类覆盖这些属性的缺省值。例如下面是从Group类继承的HSplit类的代码:

class HSplit ( Group ):
    # ... ...
    layout      = 'split'
    orientation = 'horizontal'

HSplit对象将其所包括的内容按照水平排列,并且在两块区域之间添加一个可调整位置的分隔条,使用HSplit和如下的代码等价:

Group( ... , layout = 'split', orientation = 'horizontal')

为了正确显示分隔条,其内容中需要有一个Group对象具有scrollable属性,如下面的省略代码所示:

view1 = View(
    HSplit(
        VGroup(
            ... ...,
            scrollable = True
        ),
        VGroup(
            ... ...
        ),
    ),
    resizable = True,
    width = 400,
    height = 150
)

下面的程序演示了四种不同的界面分组方式,其效果如【图:在界面中用Group对象进行分组】所示。

07-traitsui/traitsUI_group.py

用Group对View中的编辑器进行分组

/tech/static/books/scipy/_images//traitsUI_group.png

在界面中用Group对象进行分组

下面是Group的各种派生类及其对应的属性的缺省设置:

  • HGroup:内容水平排列:

    Group(orientation= 'horizontal')
    
  • HFlow:内容水平排列,当超过水平宽度时,将自动换行。show_labels属性为False表示隐藏其中的所有编辑器的标签文字:

    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:按照多列的网格进行垂直排列,columns属性决定网格的列数:

    Group(orientation= 'vertical', columns=2)
    
  • VSplit:内容垂直排列,中间插入分隔条:

    Group(orientation= 'vertical', layout='split')
    

除了上述的orientation、layout、show_labels、columns等属性之外,Group对象还提供了许多其它的属性控制其显示效果。和Item一样,读者可以在Group类的源程序中找到每个属性的详细说明。

下面我们通过一个例子说明visible_when和enabled_when属性的用法。这两个属性控制Group对象在界面中是否显示以及是否有效。

Item也提供了visible_when和enabled_when属性,其用法和Group的完全相同。

07-traitsui/traitsUI_group_condition.py

控制编辑器的显示和有效

class Shape(HasTraits):
    shape_type = Enum("rectangle", "circle")
    editable = Bool
    x, y, w, h, r = [Int]*5

    view = View(
        VGroup(
            HGroup(Item("shape_type"), Item("editable")),
            VGroup(Item("x"), Item("y"), Item("w"), Item("h"),
                visible_when="shape_type=='rectangle'", enabled_when="editable"),
            VGroup(Item("x"), Item("y"), Item("r"),
                visible_when="shape_type=='circle'",  enabled_when="editable"),
        ), resizable = True)

程序中,Shape是一个表示矩形或圆形的类,其具体形状由shape_type属性决定,而图形的参数则由x、y、w、h、r等属性决定。editable属性决定是否能通过用户界面修改图形参数。在视图定义中,使用VGroup对象定义了两个编辑器组,分别编辑矩形参数和圆形参数。通过设置VGroup的visible_when和enabled_when属性,将模型对象的shape_type属性和editable属性与编辑器的界面显示联系起来。

visible_when和enabled_when属性都是表示布尔表达式的字符串。当布尔表达式中涉及的模型对象的属性发生变化时,将会对字符串进行求值,并根据求值结果更新界面的显示。【图:演示visible_when和enabled_when属性的用法】是程序的显示效果,其中左图所对应的shape_type属性为”rectangle”,editable属性为True。当shape_type属性为”rectangle”时,将显示矩形参数的编辑器,隐藏圆形参数的编辑器。当editable属性为False时,所有编辑器都变成无效。

/tech/static/books/scipy/_images//traitsui_group_condition.png

演示visible_when和enabled_when属性的用法

配置视图

前面介绍了如何使用Item和Group等对象组织窗口界面中的内容,本节介绍如何配置窗口本身的属性。

通过kind属性可以修改View对象的显示类型:

  • ‘modal’:模式窗口, 非即时更新
  • ‘live’:非模式窗口,即时更新
  • ‘livemodal’:模式窗口,即时更新
  • ‘nonmodal’:非模式窗口,非即时更新
  • ‘wizard’:向导类型
  • ‘panel’、’subpanel’:嵌入到其它窗口中的面板,即时更新,非模式

其中’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.traits.ui.menu中还预定义了一些命令按钮列表,方便用户直接使用整套按钮:

OKCancelButtons = [OKButton, CancelButton ]
ModalButtons = [ ApplyButton, RevertButton, OKButton, CancelButton, HelpButton ]
LiveButtons = [ UndoButton, RevertButton, OKButton, CancelButton, HelpButton ]

內容目录

上一个主题

缺省界面

下一个主题

用Handler控制界面和模型

本页

loading...