Kava Pava Gava Tava Nava Zava Java

everything about Java
随笔 - 15, 文章 - 0, 评论 - 1, 引用 - 0
数据加载中……

Exploring Vaadin (3) 阅读 com.vaadin.ui.Form 源代码

Form 是 vaadin 用户界面的一个组成部分,下面结合其源代码(ver 6.1.5)进行分析。

public class Form extends AbstractField implements Item.Editor, Buffered, Item,
        Validatable {

Form 实现了 vaadin 数据模型的上述接口,其中最重要的应该是 Item.Editor,这个接口代表 Form 是用来编辑 Item 的。Item 可以是一个 pojo,一个数据表中的一行等等。总之,Item 是 Property 的集合。具体请看我的第一篇 Data 部分。

    private Object propertyValue;
    private Layout layout;
    private Item itemDatasource;
    private final LinkedList<Object> propertyIds = new LinkedList<Object>();


保存加入的 id,仅在addItemProperty(Object id, Property property) 和 addField(Object propertyId, Field field) 中被添加。在很多方法中被遍历。与ownProperties的不同之处就在于多了一个方法,即addField中也会添加。

    private Buffered.SourceException currentBufferedSourceException = null;

这个currentBufferedSourceException 在commit()以及discard()时被设置或者清除,用来保存各个field.commit()/field.discard()过程中捕捉到的Buffered.SourceException并且被抛出。在getErrorMessage()中被返回。

    private boolean writeThrough = true;
    private boolean readThrough = true;
    private final HashMap<Object, Field> fields = new HashMap<Object, Field>();
    private final HashMap<Object, Property> ownProperties = new HashMap<Object, Property>();


所有通过addItemProperty(Object id, Property property) 加入的property 被按照 id 加入到ownProperties 中。在 getItemProperty 时仅仅作为第三选择,在没有对应的Field时才被返回。在 removeItemProperty 时也被除去。

    private FormFieldFactory fieldFactory;
    private Collection<Object> visibleItemProperties;
    private Layout formFooter;
    private boolean validationVisibleOnCommit = true;
    // special handling for gridlayout; remember initial cursor pos
    private int gridlayoutCursorX = -1;
    private int gridlayoutCursorY = -1;


上面是 members


    private final ValueChangeListener fieldValueChangeListener = new ValueChangeListener() {
        public void valueChange(com.vaadin.data.Property.ValueChangeEvent event) {
            requestRepaint();
        }
    };


所有的 field 都会被加上这个监听器,这样当任何一个 field 的值发生变化时都会使 Form 重新绘制。

    public ErrorMessage getErrorMessage() {

考虑三个 error 来源:
 - getComponentError() 这个是从 AbstractComponent 继承下来的,可以被设置, 设置时会触发 Component.ErrorEvent。
 - validationError,来自各个Field 的getErrorMessage(),但只抓第一个。详情见下面。
 - currentBufferedSourceException,上一次commit()或者discard()收集到的Buffered.SourceException,见上面介绍。

得到 validationError:如果 isValidationVisible() (默认否),那么对 propertyIds 遍历,从 fields Map中得到各个 Field,如果 field.getErrorMessage() 不为空,则抛出 Validator.InvalidValueException。这个Validator.InvalidValueException 可能就是从field.getErrorMessage() 得到的那个。但是如果消息未空或者有其他问题,建立一个InvalidValueException,以 Field.getCaption() 作为内容。注意,只要一个Field得到 InvalidValueException,其他Field就不再遍历了。

然后,考虑上面说到的三个源。如果都为空,返回空。否则抛出 CompositeErrorMessage,包含这三个错误源。

    public void commit() throws Buffered.SourceException {


如果isInvalidCommitted()为否,并且isValid()为否,进行 validate(),可能抛出异常。

如果没有进行 validate() 或者 validate() 通过,则遍历 propertyIds,从 fields 里面按照 id 取出各个 Field,除非 isReadOnly(),否则调用 commit()。捕捉可能抛出的 Buffered.SourceException, 先加入到一个问题列表中。

最后,如果问题列表为空,清空currentBufferedSourceException ,requestRepaint(),顺利返回。否则,create 一个新的 Buffered.SourceException,把收集到的 Exception 当作 cause,然后设为currentBufferedSourceException ,要求 requestRepaint(),并抛出刚刚生成的 currentBufferedSourceException。

    public void discard() throws Buffered.SourceException {

过程和commit()基本一致,只是调用各个field.commit()换成了调用 field.discard()。同样,也捕捉处理Buffered.SourceException,放在currentBufferedSourceException中。

    public boolean isModified() {

遍历 propertyIds,调用各个 Field 的 isModified(),任何为真则返回真。

    public void setReadThrough(boolean readThrough) {

如果确实有变化,遍历 propertyIds,调用各个 Field 的 setReadThrough()。

    public void setWriteThrough(boolean writeThrough) throws SourceException,

同上处理。

    public boolean addItemProperty(Object id, Property property) {

如果 propertyIds 已经包括 id,也就是说以前加入过,直接返回 false。

否则,通过 fieldFactory 建立Field。配置Field,包括setPropertyDataSource,然后从id.toString()经过长度,首字母大小写调整,设置为Caption。最后调用 addField(id,field)加入 Field。

    public void addField(Object propertyId, Field field) {

将要加入的field 加入到 fields,给要加入的 field 加上fieldValueChangeListener以便在 field 的值改变时重新绘制Form,如果propertyIds不包含 propertyId,加入(这是被外部调用的时候发生的)。调整field的 readthrough / writethrough / immediate 和 Form 一致,最后将 field 加入本 Form 的 Layout。在加入 Layout 时,如果是 CustomLayout,以propertyId.toString()为位置标记。否则只是简单顺次加入。

这里本人对 vaadin 的做法有保留。propertyId.toString()被作为 Field的Caption,又被作为很多 Map 的key,又被作为 CustomLayout 的位置标记,是不是担当的角色太多了?从内部结构标识到给用户的Caption, 会给国际化带来麻烦吗?等我进一步深入探索使用,才能得到结论。

    public Property getItemProperty(Object id) {

依照下面次序返回:
如果 fields.get(id) 得到Field,再从field.getPropertyDataSource() 得到 property,则返回 property
否则,返回 field,因为 field 就是一个 property
再否则,返回 ownProperties.get(id)
关于 ownProperties,详见上面说明

    public Field getField(Object propertyId) {
        return fields.get(propertyId);
    }


    public Collection getItemPropertyIds() {
        return Collections.unmodifiableCollection(propertyIds);
    }


以上两条代码简单明了。Collections.unmodifiableCollection 不错,今后可以多用。


    public boolean removeItemProperty(Object id) {

从 ownProperties, fields, propertyIds, layout 中去掉 id 相对应的元素。这里面从ownProperties去掉后先判断是不是有对应的field,如果有,才从fields, propertyIds, layout 中去掉。为什么这么写?这是因为如果有 propertyId,就一定有相应的 field,加入到 layout?看代码,应该是这样吧。

    public boolean removeAllProperties() {

对所有 propertyIds 依次调用 removeItemProperty。

    public Item getItemDataSource()

就是简单的getter。


    public void setItemDataSource(Item newDataSource) {
        setItemDataSource(newDataSource, newDataSource != null ? newDataSource
                .getItemPropertyIds() : null);
    }

看下面的实现。

    public void setItemDataSource(Item newDataSource, Collection propertyIds) {


首先 removeAllProperties(),设置 itemDatasource,然后遍历参数 propertyIds,从itemDatasource取出相应的 property,通过 fieldFactory 得到 Field,设置field.setPropertyDataSource 为所取出的property,调用 addField 来添加 Field。

奇怪的是这里为什么没有设置 Field 的 Caption? 为什么不是调用 addItemProperty 来处理,而是要写重复代码?很奇怪。原来默认的DefaultFieldFactory里面在 createField 时确实设置 Caption的。createField 调用 createCaptionByPropertyId 来得到Caption,而createCaptionByPropertyId 进行格式处理,将 "firstName" 便成为 "First Name"。这个做法只能是一个默认的方便的做法。看来 FieldFactory确实要自己去写的。可是这样看来,addItemProperty 在 createField 之后又去操作 field.setCaption() 和进行格式化是多余了。注意 addItemProperty 是以 this,也就是这个 Form 作为Item参数去调用 createField的,而setItemDataSource 是以得到的 itemDataSource,也就是 newDataSource 进行的。这样就明白了。addItemProperty 是添加一个不在 itemDataSource中的一个独立的 property。这两个函数,addItemProperty 和 setItemDataSource,要说addItemProperty 的名字起得有点误导。应该是 addFormProperty 或者 addStandaloneProperty 更好些吗?看来“正常”使用的时候,就是用来和一个Item绑定的时候,是应该不会调用addFormProperty 的。除非,除了Item的那些property之外,还要加其他property。


    public Layout getLayout() {

就是 getter

    public void setLayout(Layout newLayout) {


用新的 layout 替换旧的。如果有旧的 layout,把所有的 field 一一从旧 layout 中去掉,再加入到新的里面。

    public Select replaceWithSelect(Object propertyId, Object[] values,
            Object[] descriptions) {


这个没有细看,用到时候再说吧。应该用 FieldFactory 来解决问题。

    @Override
    public void attach() {
        super.attach();
        layout.attach();
    }

    @Override
    public void detach() {
        super.detach();
        layout.detach();
    }


这两个比较简单。

    @Override
    public boolean isValid() {


依次调用 propertyIds 对应的 field,再调用 isValid(),进行与运算。最后与 super.isValid() 进行与运算。

    @Override

与上面顺序相反,先调用 super.validate(),再顺次调用 propertyIds 对应的 field,再调用 validate()

    public boolean isInvalidAllowed() {
        return true;
    }

    public void setInvalidAllowed(boolean invalidValueAllowed) 没有实现,直接抛出异常

    public void setReadOnly(boolean readOnly) {

依次调用 super 以及 propertyIds 对应的各个 field 的相应方法

    public void setFormFieldFactory(FormFieldFactory fieldFactory) {
    public FormFieldFactory getFormFieldFactory() {


就是setter 和 getter

    public Class getType() {
        if (getPropertyDataSource() != null) {
            return getPropertyDataSource().getType();
        }
        return Object.class;
    }

很简单。。。

    protected void setInternalValue(Object newValue) {

首先 super.setInternalValue(newValue),propertyValue = newValue; 然后看是不是和旧的 propertyValue 不同。如果是的话,则调用受保护方法 setFormDataSource(newValue, getVisibleItemProperties())。setFormDataSource 得到 Item (如果 newValue 不是 Item,就生成BeanItem),调用setItemDataSource。visibleItemProperties 不知什么用途。但是总之,看起来 setInternalValue,setVisibleItemProperties是一起用的。注释说这个方法是在Form被当作 Field使用时用的。

    private Field getFirstField() {

得到 propertyIds 第一个元素对应的 Field

    protected void setFormDataSource(Object data, Collection properties) {


如果data是一个Item,用之,否则生成BeanItem。调用setItemDataSource

    public Collection getVisibleItemProperties() {
        return visibleItemProperties;
    }


只是getter

    public void setVisibleItemProperties(Collection visibleProperties) {

设置 visibleItemProperties,然后调用AbstractField的getValue,如果为空则还是用itemDataSource,否则用getValue,去调用 setFormDataSource(value, getVisibleItemProperties());

    public void setVisibleItemProperties(Object[] visibleProperties) {

将array转成collection然后设置

    public void focus() {

focus 在第一个 field

    public void setTabIndex(int tabIndex) {

    public void setImmediate(boolean immediate) {


依次调用各个field

    protected boolean isEmpty() {

依次判断各个field

    public void addValidator(Validator validator) {

不支持。

    public Layout getFooter() {

简单,不再详述。

    public void setFooter(Layout newFormFooter) {

简单,不再详述。

    public void setEnabled(boolean enabled) {

调用 super.setEnabled,奇怪,似乎没有调用各个 field 的setEnabled()。

posted on 2009-12-23 16:10 bing 阅读(732) 评论(0)  编辑  收藏


只有注册用户登录后才能发表评论。


网站导航: