Facebook Twitter LinkedIn E-mail
magnify
Home 2012 八月

Vaadin Web应用开发教程(44): 使用Container接口管理一组Item

单个属性使用Property接口,一组Property使用Item接口来管理,Container接口则管理一组具有相同属性的Item。Container所包含的Item使用Item标识符(IID)来区分。
Item通过方法addItem()方法向Container添加Item。 查询某个属性可以先通过getItem()取得Item对象,然后再使用getItemProperty()方法,或者直接使用getContainerProperty 方法来读取。
Vaadin在设计Container接口考虑到灵活性和高效性,它包括了一些可选的接口一支持内部Item的排序,索引或者以层次关系来访问Item,从而为实现Table,Tree,Select等UI组件提供了实现基础。
和其它数据模型类似,Container接口也提供了数据变动事件的支持。
Container可以是无序的,有序的,带索引或是支持层次关系,因此可以支持几乎现实中所有数据模型。Vaadin 内部实现支持一些常用的数据源,比如简单的二维表(IndexedContainer)和文件系统(FileSystemContainer)等。
除了上面通用的Container实现,一些UI组件本身就实现了Container接口,比如Select组件。
使用BeanContainer

BeanContainer 为使用内存来管理JavaBean对象的Container类型。每个其中的Item为使用BeanItem封装的Java对象。Item的属性会根据setter, getter 自动识别,因此需要使用的Java Bean具有public 修饰符。也只有同类型的Java Bean对象才可以添加到BeanContainer中。

BeanContainer为一generic 类型,使用时给出所包含的Bean类型和Item 标识符的类型。参考下面例子:

// Here is a JavaBean
public class Bean implements Serializable {
    String name;
    double energy; // Energy content in kJ/100g
    
    public Bean(String name, double energy) {
        this.name   = name;
        this.energy = energy;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public double getEnergy() {
        return energy;
    }
    
    public void setEnergy(double energy) {
        this.energy = energy;
    }
}

void basic(VerticalLayout layout) {
    // Create a container for such beans with
    // strings as item IDs.
    BeanContainer<String, Bean> beans =
        new BeanContainer<String, Bean>(Bean.class);
    
    // Use the name property as the item ID of the bean
    beans.setBeanIdProperty("name");

    // Add some beans to it
    beans.addBean(new Bean("Mung bean",   1452.0));
    beans.addBean(new Bean("Chickpea",    686.0));
    beans.addBean(new Bean("Lentil",      1477.0));
    beans.addBean(new Bean("Common bean", 129.0));
    beans.addBean(new Bean("Soybean",     1866.0));

    // Bind a table to it
    Table table = new Table("Beans of All Sorts", beans);
    layout.addComponent(table);
}

嵌套属性
如果Java Bean有个嵌套的Java Bean类型,而且具有和这个嵌套Java Bean具有1对1的关系,你可以将这个嵌套类的属性添加到Container中,就如同直接包含在其中Java Bean的属性一样。同样此时嵌套的Java Bean也必须具有public 修饰符。
如下例:
先定义两个Java Bean类型,其中EqCoord作为Star的嵌套类,一个Star类对应一个EqCoord,一一对应的关系。

/** Bean to be nested */
public class EqCoord implements Serializable {
    double rightAscension; /* In angle hours */
    double declination;    /* In degrees     */

    ... constructor and setters and getters for the properties ...
}

/** Bean containing a nested bean */
public class Star implements Serializable {
    String  name;
    EqCoord equatorial; /* Nested bean */

    ... constructor and setters and getters for the properties ...
}

在创建好Container之后,可以通过方法addNestedContainerProperty将嵌套类的属性添加到Container中。

// Create a container for beans
final BeanItemContainer<Star> stars =
    new BeanItemContainer<Star>(Star.class);
 
// Declare the nested properties to be used in the container
stars.addNestedContainerProperty("equatorial.rightAscension");
stars.addNestedContainerProperty("equatorial.declination");
 
// Add some items
stars.addBean(new Star("Sirius",  new EqCoord(6.75, 16.71611)));
stars.addBean(new Star("Polaris", new EqCoord(2.52, 89.26417)));

如果将这个Container绑定到一个TableUI组件,你可能需要为表的列定义列名称。嵌套类的属性也作为单独的列显示在表格中,如果需要隐藏某个列,可以通过方法setVisibleColumns修改例的可见性。

// Put them in a table
Table table = new Table("Stars", stars);
table.setColumnHeader("equatorial.rightAscension", "RA");
table.setColumnHeader("equatorial.declination",    "Decl");
table.setPageLength(table.size());

// Have to set explicitly to hide the "equatorial" property
table.setVisibleColumns(new Object[]{"name",
    "equatorial.rightAscension", "equatorial.declination"});

使用BeanItemContainer
BeanItemContainer 用来管理一组由BeanItem封装的Java Bean对象。Item的属性会根据setter, getter 自动识别,因此需要使用的Java Bean具有public 修饰符。也只有同类型的Java Bean对象才可以添加到BeanItemContainer中。
BeanItemContainer为BeanContainer的一个特别版本,它不需要指明Item 标识符的类型,而直接使用Item对象来区分Item。因此比BeanContainer使用更简单。

// Create a container for the beans
BeanItemContainer<Bean> beans =
    new BeanItemContainer<Bean>(Bean.class);
    
// Add some beans to it
beans.addBean(new Bean("Mung bean",   1452.0));
beans.addBean(new Bean("Chickpea",    686.0));
beans.addBean(new Bean("Lentil",      1477.0));
beans.addBean(new Bean("Common bean", 129.0));
beans.addBean(new Bean("Soybean",     1866.0));
 
// Bind a table to it
Table table = new Table("Beans of All Sorts", beans);

遍历Container
Container 所包含的Item对象并不一定需要排过序,遍历整个Container可以通过Iterator接口。Container 的getItemIds()返回一个Collection集合支持枚举。下例为遍历一个Table,检查其中为Checkbox的某个列,选择出所有选中的Item。

// Collect the results of the iteration into this string.
String items = "";

// Iterate over the item identifiers of the table.
for (Iterator i = table.getItemIds().iterator(); i.hasNext();) {
    // Get the current item identifier, which is an integer.
    int iid = (Integer) i.next();
    
    // Now get the actual item from the table.
    Item item = table.getItem(iid);
    
    // And now we can get to the actual checkbox object.
    Button button = (Button)
            (item.getItemProperty("ismember").getValue());
    
    // If the checkbox is selected.
    if ((Boolean)button.getValue() == true) {
        // Do something with the selected item; collect the
        // first names in a string.
        items += item.getItemProperty("First Name")
                     .getValue() + " ";
    }
}
 
// Do something with the results; display the selected items.
layout.addComponent (new Label("Selected items: " + items));

过滤Container
对应Container的Item对象,可以定义一些查询条件来过滤掉一些Item。如同数据查询时使用WHERE语句来查询表格。比如下面代码定义一个简单的过滤器来匹配name 列以Douglas 开头的Item

Filter filter = new SimpleStringFilter("name",
        "Douglas", true, false);
table.addContainerFilter(filter);

Filter可以为单个(atomic)或是组合(Composite)类型。单个Filter 定义单独的一个条件,如上面的SimpleStringFilter,组合的Filter有多个单个的Filter通过NOT,OR,AND 组合而成。例如:

filter = new Or(new SimpleStringFilter("name",
        "Douglas", true, false),
        new Compare.Less("age", 42));

Vaadin定义了常用的Filter类型,如SimpleStringFilter,IsNull,Equal, Greater, Less, GreaterOrEqual, LessOrEqual,And, Or 和Not 等,也可以自定义一个Filter类型,如:

class MyCustomFilter implements Container.Filter {
    protected String propertyId;
    protected String regex;
    
    public MyCustomFilter(String propertyId, String regex) {
        this.propertyId = propertyId;
        this.regex      = regex;
    }

    /** Tells if this filter works on the given property. */
    @Override
    public boolean appliesToProperty(Object propertyId) {
        return propertyId != null &&
               propertyId.equals(this.propertyId);
    }
 

Vaadin Web应用开发教程(43): 使用Item接口管理一组Property

Item接口用来管理一组命名的Property对象。每个Property由一个标识符(PID)来定义,Item通过PID使用方法getItemProperty()来读写其中的Property。
使用Item的地方例如Table的一行,每个属性(Property)对应行的每个字段(列column)。或者是Tree的一个节点,以及绑定到表单From的数据,此时Item中的每个属性对应的表单中的一个输入域(Field)。
使用在面向对象概念来说,Item 对应到一个对象,但Item可以支持配置及通过事件处理机制(主要是数据变化事件)。
使用Item的最简单的方法是使用Vaadin的一些内置实现,比如PropertysetItem 或BeanItem。此外Form也实现了Item接口因此可当作Item对象来使用。Form可以通过Item自动创建其中的UI组件,可以参见Vaadin Web应用开发教程(23):UI组件-Form组件
Item 接口定义了一些接口来管理其中的Property或监听Property值变化事件。

使用PropertysetItem
PropertysetItem 是实现了Item接口的通用实现,可以用来存放属性值。属性通过addItemProperty添加到集合中。

PropertysetItem item = new PropertysetItem();
item.addItemProperty("name", new ObjectProperty("Zaphod"));
item.addItemProperty("age", new ObjectProperty(42));
        
// Bind it to a component
Form form = new Form();
form.setItemDataSource(item);

使用BeanItem
BeanItem也实现了Item接口可以用来包容一个JavaBean对象。实际上只使用Java Bean规范中的setter 和getter而未使用其它JavaBean功能,因此BeanItem也可以用在一般的Java对象(POJO)上。

// Here is a bean (or more exactly a POJO)
class Person {
    String name;
    int    age;
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public Integer getAge() {
        return age;
    }
    
    public void setAge(Integer age) {
        this.age = age.intValue();
    }
}

// Create an instance of the bean
Person bean = new Person();
        
// Wrap it in a BeanItem
BeanItem<Person> item = new BeanItem<Person>(bean);
        
// Bind it to a component
Form form = new Form();
form.setItemDataSource(item);

嵌套使用Bean
在使用聚合类,某个类包含其它某个类,比如下面的Planet类定义了一个发现人Discovery(为Person 类)

// Here is a bean with two nested beans
public class Planet implements Serializable {
    String name;
    Person discoverer;
    
    public Planet(String name, Person discoverer) {
        this.name = name;
        this.discoverer = discoverer;
    }

    ... getters and setters ...
}

...
// Create an instance of the bean
Planet planet = new Planet("Uranus",
                    new Person("William Herschel", 1738));

当需要在Form显示时,你可以希望在显示Planet属性的同时显示Disoveryer的一些属性,可以通过MethodProperty或NestedMethodProperty单独绑定这些属性,通常此时需要隐藏嵌套类本身被绑定到属性,可以在构造函数中只列出需要绑定的属性,比如只绑定Planet的Name 属性。

// Wrap it in a BeanItem and hide the nested bean property
BeanItem<Planet> item = new BeanItem<Planet>(planet,
        new String[]{"name"});
    
// Bind the nested properties.
// Use NestedMethodProperty to bind using dot notation.
item.addItemProperty("discoverername",
    new NestedMethodProperty(planet, "discoverer.name"));
    
// The other way is to use regular MethodProperty.
item.addItemProperty("discovererborn",
     new MethodProperty<Person>(planet.getDiscoverer(),
                                "born"));

NestedMethodProperty 和MethodProperty 不同点在于NestedMethodProperty只在需要访问属性值时才访问嵌套类,而MethodProperty则是在创建这个Method Property就访问嵌套类(如Person)。

下面代码将Item 绑定到一个Form

// Bind it to a component
Form form = new Form();
form.setItemDataSource(item);
    
// Nicer captions
form.getField("discoverername").setCaption("Discoverer");
form.getField("discovererborn").setCaption("Born");

 

Vaadin Web应用开发教程(42):数据绑定-Property接口

Property接口为Vaadin数据模型的基本接口,它提供了读写单个数据对象的标准API。 一个Property对象总是有数据类型的,尽管它支持可选的数据类型转换。Property的数据可以为任意的Java对象,Property 也提供了监听数据变化的事件消息。
Property的读写方法为getValue()和setValue() 。getValue() 返回通用的Object 类型的对象,因此可以强制转换成所需的数据类型。Property的类型可以通过getType()取得。
Property值发生变化说触发ValueChangeEvent事件,可以通过ValueChangeListener监听这个事件。

final TextField tf = new TextField("Name");
        
// Set the value
tf.setValue("The text field value");
        
// When the field value is edited by the user
tf.addListener(new Property.ValueChangeListener() {
    public void valueChange(ValueChangeEvent event) {
        // Get the value and cast it to proper type
        String value = (String) tf.getValue();
        
        // Do something with it
        layout.addComponent(new Label(value));
    }
});

使用Property接口,一是实现Property接口,而是使用Vaadin内置的两个Property接口实现:MethodProperty 主要用于Java Bean,而是ObjectProperty用于简单的Java对象。

与Property接口关系紧密的还有两个接口Property.EditorProperty.Viewer 可以用来显示和编译Property值,大部分的UI组件,尤其是Field组件实现了这两个接口,因此Field组件可以直接绑定到Property对象,用来显示或编辑Property数据。

下例使用Label 来显示一个ObjectProperty 对象

// Have a data model
ObjectProperty property =
    new ObjectProperty("Hello", String.class);
        
// Have a component that implements Viewer
Label viewer = new Label();
        
// Bind it to the data
viewer.setPropertyDataSource(property);

同样可以使用一个TextField来编辑并显示一个ObjectProperty对象

// Have a data model
ObjectProperty property =
    new ObjectProperty("Hello", String.class);
        
// Have a component that implements Viewer
TextField editor = new TextField("Edit Greeting");
        
// Bind it to the data
editor.setPropertyDataSource(property);

前面说过所有Field组件也实现了Property接口,因此也可以把Field组件绑定到实现了Property.Viewer接口的UI组件,如Label。下例把一个Label绑定到一个TextField,因此Label显示的内容会和TextField的值变化而变化。

Label viewer = new Label();
viewer.setPropertyDataSource(editor);
 
// The value shown in the viewer is updated immediately
// after editing the value in the editor (once it
// loses the focus)
editor.setImmediate(true);

此外,你也可以自行实现Property接口,然后绑定到Field组件。

class MyProperty implements Property {
    Integer data     = 0;
    boolean readOnly = false;
    
    // Return the data type of the model
    public Class<?> getType() {
        return Integer.class;
    }

    public Object getValue() {
        return data;
    }
    
    // Override the default implementation in Object
    @Override
    public String toString() {
        return Integer.toHexString(data);
    }

    public boolean isReadOnly() {
        return readOnly;
    }

    public void setReadOnly(boolean newStatus) {
        readOnly = newStatus;
    }

    public void setValue(Object newValue)
            throws ReadOnlyException, ConversionException {
        if (readOnly)
            throw new ReadOnlyException();
            
        // Already the same type as the internal representation
        if (newValue instanceof Integer)
            data = (Integer) newValue;
        
        // Conversion from a string is required
        else if (newValue instanceof String)
            try {
                data = Integer.parseInt((String) newValue, 16);
            } catch (NumberFormatException e) {
                throw new ConversionException();
            }
        else
             // Don't know how to convert any other types
            throw new ConversionException();

        // Reverse decode the hexadecimal value
    }
}
        
// Instantiate the property and set its data
MyProperty property = new MyProperty();
property.setValue(42);
        
// Bind it to a component
final TextField tf = new TextField("Name", property);
 

Vaadin Web应用开发教程(41):数据绑定-概述

本篇介绍Vaadin的数据模型及数据绑定-将数据源绑定到UI组件。
Vaadin通过数据绑定可以支持用户提供UI组件(界面)直接操作数据模型(显示或者直接修改)。
Vaadin的数据模型分为三个层次: Property, Item 和 Container. 类比Excel电子表格,可以分别类比电子表格的单元格,一行和整个表单。

Vaadin数据某些相关的接口和类定义在包 com.vaadin.data 中。要注意的是Vaadin定义的数据模型都是通过接口来定义的,而非实类。这也体现了“编程到接口”的设计模式,因此支持数据绑定的数据源可以多种多样,比如普通Java类, 文件系统,数据库等。

Vaadin的UI组件和数据绑定关系密切。 所有实现了Field接口的UI组件都支持数据绑定。这些UI组件也可以称为Field 组件。与其绑定的值可以通过Property属性来访问。
下图显示了Vaadin数据模型接口之间的关系:

Vaadin的数据模型支持多种功能,比如支持数据更新通知,支持索引,排序,过滤等。
Vaadin 通过了多种内置的数据模型如SQL数据库支持等,也可以通过插件的方式支持其它数据类型,如JPA支持。

 

Vaadin Web应用开发教程(40):使用主题-创建和应用新主题

Vaadin中创建的主题必须放置在VAADIN/themes 目录下。目录名称即为新的主题名称,新主题中必须包含一个styles.css 文件。新主题也必须继承某个Vaadin内置主题,如:

@import "../reindeer/styles.css";

.v-app {
    background: yellow;
}

前面介绍的Vaadin个UI组件和布局都可以通过CSS 修改它们的显示外观。它们都定义了对应的Vaadin 的CSS类名,下表给出了Vaadin中标准UI组件的CSS类名:

Server-Side Component Client-Side Widget CSS Class Name
AbsoluteLayout VAbsoluteLayout v-absolutelayout
Accordion VAccordion v-accordion
Button VButton v-button
CheckBox VCheckBox v-checkbox
CssLayout VCssLayout v-csslayout
CustomComponent VCustomComponent v-customcomponent
CustomLayout VCustomLayout v-customlayout
DateField VDateField v-datefield
VCalendar v-datefield-entrycalendar
VDateFieldCalendar v-datefield-calendar
VPopupCalendar v-datefield-calendar
VTextualDate
Embedded VEmbedded
Form VForm v-form
FormLayout VFormLayout
GridLayout VGridLayout
Label VLabel v-label
Link VLink v-link
OptionGroup VOptionGroup v-select-optiongroup
HorizontalLayout VHorizontalLayout v-horizontallayout
VerticalLayout VVerticalLayout v-verticallayout
Panel VPanel v-panel
Select
VListSelect v-listselect
VFilterSelect v-filterselect
Slider VSlider v-slider
SplitPanel VSplitPanel
VSplitPanelHorizontal
VSplitPanelVertical
Table VScrollTable v-table
VTablePaging v-table
TabSheet VTabSheet v-tabsheet
TextField VTextField v-textfield
VTextArea
VPasswordField
Tree VTree v-tree
TwinColSelect VTwinColSelect v-select-twincol
Upload VUpload
Window VWindow v-window
CalendarEntry
CalendarPanel v-datefield-calendarpanel
ContextMenu v-contextmenu
VUnknownComponent vaadin-unknown
VView
Menubar gwt-MenuBar
MenuItem gwt-MenuItem
Time v-datefield-time

Vaadin 内置了两种主题,reindeer 和  runo, Vaadin 6.0 缺省使用reindeer 主题。 有关主题定义的常数定义在包com.vaadin.ui.themes 中。

setTheme("runo");

Panel panel = new Panel("Regular Panel in the Runo Theme");
panel.addComponent(new Button("Regular Runo Button"));

// A button with the "small" style
Button smallButton = new Button("Small Runo Button");
smallButton.addStyleName(Runo.BUTTON_SMALL);

Panel lightPanel = new Panel("Light Panel");
lightPanel.addStyleName(Runo.PANEL_LIGHT);
lightPanel.addComponent(new Label("With addStyleName(\"light\")"));

Vaadin 的Eclipse插件可以帮助创建新主题,为项目添加一个新主题. New -> Other -> Vaadin -> Vaadin theme

按照向导一步一步,就可以创建一个新的主题,然后修改新主题下的styles.css ,就可以达到自己预期的显示效果。

 

Vaadin Web应用开发教程(39):使用主题-CSS简介

Vaadin主题的使用一个重要的方法是使用CSS风格文件。本篇对CSS的使用做个简单的介绍。

基本CSS规则

一个CSS文件包含一组规则,每个规则包含一个“风格选择符”,由逗号分开。然后通过花括号将风格描述括在里面。例如:

p, td {
 color: blue;
}

td {
 background: yellow;
 font-weight: bold;
}

上例中p,td 匹配到HTML中的<p> 和<td> 元素,第一个规则可以匹配所有<p> 和<td> 元素,第二个规则只匹配<td>元素。
你可以使用浏览器来显示下面的Html文档:

<html>
    <head>
        <link rel="stylesheet" type="text/css"
              href="mystylesheet.css"/>
    </head>
    <body>
        <p>This is a paragraph</p>
		<p>This is another paragraph</p>
		<table>
            <tr>
                <td>This is a table cell</td>
				<td>This is another table cell</td>
			</tr>
        </table>
    </body>
</html>

其中的mystylesheet.css 则包含上面的CSS规则。使用Chrome浏览器显示如下:

CSS 也支持继承机制,也就是说子元素可以从其父元素中继承CSS属性。比如可以从
元素继承。

table {
 color: blue;
 background: yellow;
}

匹配元素类
CSS 直接使用元素类型,如上面的<p>,<td>定义规则的情况并不多见,常见的是使用元素的类名来匹配规则。比如下面的HTML文档,使用class 来为元素定义类名。

<html>
  <body>
    <p class="normal">This is the first paragraph</p>
	<p class="another">This is the second paragraph</p>
	<table>
      <tr>
        <td class="normal">This is a table cell</td>
		<td class="another">This is another table cell</td>
	 </tr>
    </table>
 </body>
</html>

然后使用类名匹配来定义CSS规则


p.normal   {color: red;}
p.another  {color: blue;}
td.normal  {background: pink;}
td.another {background: yellow;}

CSS 匹配支持使用通配符,如* 来匹配任意元素类型。 × 也可以省略,而只用 “.”.如:


.normal {
    color: red;
}

.another {
    blackground: yellow;
}

Vaadin 建议只使用类名匹配来定义规则而不要使用HTML 元素名称来定义规则以提高Web应用的兼容性。

包含关系匹配

CSS支持根据HTML元素之间的包含关系来匹配规则,比如:

<body>
  <p class="mytext">Here is some text inside a
                    paragraph element</p>
 <table class="mytable">
    <tr>
      <td class="mytext">Here is text inside
                      a table and inside a td element.</td>
    </tr>
 </table>
</body>

规则 .mytext 会同时匹配

元素,但如果我们只想匹配表格中的td 元素,可以通过 table 类名做为 前缀来定义规则,如下:

.mytable .mytext {color: blue;}

其中类前缀并不需要为其子元素的直接父容器的类名,只要它们之间存在包含关系即可。 比如 .v-panel . v-button 可以匹配所有包含着 .v-panel 中的 v-button 元素。

下面来看看一个Vaadin的实例。
定义一个Vaadin自定义组件(登录界面)


public class LoginBox extends CustomComponent {
    Panel         panel  = new Panel("Log In");

    public LoginBox () {
        setCompositionRoot(panel);
        
        panel.addComponent(new TextField("Username:"));
        panel.addComponent(new TextField("Password:"));
        panel.addComponent(new Button("Login"));
    }
}

使用内置的runo 主题显示如下:

可以查到 Panel, TextField, Button 等对应的Vaadin CSS类名分别为恶 v-panel, v-textfield, v-button, 通过自定义风格,修改其style.css 定义如下CSS 规则:


.v-panel .v-panel-caption {
 background: #80ff80; /* pale green */
}
 
.v-panel .v-panel-content {
 background: yellow;
}
 
.v-panel .v-textfield {
 background: #e0e0ff; /* pale blue */
}
 
.v-panel .v-button {
 background: pink;
}

显示如下: