与为每个字段属性绑定编写一行代码相比,将Vaadin 8 @PropertyId 注释与 Binder::bindInstanceFields 一起使用肯定会更短,更轻松。

Person person;  // `name` is String, `yearOfBirth` is Integer.
…
@PropertyId ( "name" )
final TextField nameField = new TextField ( "Full name:" ); // Bean property.

@PropertyId ( "yearOfBirth" )
final TextField yearOfBirthField = new TextField ( "Year of Birth:" ); // Bean property.
…
// Binding
Binder < Person > binder = new Binder <> ( Person.class );
binder.bindInstanceFields ( this );
binder.setBean ( person );
但是,由于yearOfBirth属性是一个Integer,所以我们抛出了一个异常,并且这种易于完成的绑定方法缺少转换器。

严重:
java.lang.IllegalStateException:属性类型'java.lang.Integer'与字段类型'java.lang.String'不匹配。绑定应使用转换器手动配置。

这是否意味着 Binder::bindInstanceFields 只能用于完全由String数据类型的属性组成的bean?
有没有一种方法可以指定 Converter (例如 StringToIntegerConverter )而不必逐项列出代码中的每个绑定?

最佳答案

参见Vaadin Framework, Vaadin Data Model, Binding Data to Forms:

转换次数

即使类型不匹配,您也可以将应用程序数据绑定到UI字段组件。

Binder#bindInstanceFields() 说:

由于字段的类型不兼容,因此并不总是可以将字段绑定到属性。例如。需要自定义转换器来绑定HasValue<String>Integer属性(这是“age”属性的情况)。在这种情况下,除非在调用IllegalStateException方法之前手动配置了该字段,否则bindInstanceFields(Object)将被抛出

[...]:bindInstanceFields(Object)方法不会覆盖现有绑定。

[我的重点。]

因此,AFAIU,这应该工作:

private final TextField siblingsCount = new TextField( "№ of Siblings" );

...

binder.forField( siblingsCount )
    .withNullRepresentation( "" )
    .withConverter(
        new StringToIntegerConverter( Integer.valueOf( 0 ), "integers only" ) )
    .bind( Child::getSiblingsCount, Child::setSiblingsCount );
binder.bindInstanceFields( this );

但是它仍然抛出:

java.lang.IllegalStateException: Property type 'java.lang.Integer' doesn't match the field type 'java.lang.String'. Binding should be configured manually using converter. ... at com.vaadin.data.Binder.bindInstanceFields(Binder.java:2135) ...
你在跟我开玩笑吗?那就是我所做的,不是吗?我相当怀疑“不会覆盖现有绑定”。或者,如果实际上未被覆盖,则似乎至少在bindInstanceFields()中将它们忽略了。

当不使用 Binder#bindInstanceFields() 而不是使用每个字段具有单独绑定的方法时,可以使用相同的手动绑定配置。

另请参见Vaadin Framework数据绑定论坛中的Binding from Integer not working线程和issue #8858 Binder.bindInstanceFields() overwrites existing bindings

解决方法

比@cfrick的答案更令人费解:
/** Used for workaround for Vaadin issue #8858
 *  'Binder.bindInstanceFields() overwrites existing bindings'
 *  https://github.com/vaadin/framework/issues/8858
 */
private final Map<String, Component> manualBoundComponents = new HashMap<>();
...
// Commented here and declared local below for workaround for Vaadin issue #8858
//private final TextField siblingsCount = new TextField( "№ of Siblings" );
...

public ChildView() {
    ...

    // Workaround for Vaadin issue #8858
    // Declared local here to prevent processing by Binder#bindInstanceFields()
    final TextField siblingsCount = new TextField( "№ of Siblings" );
    manualBoundComponents.put( "siblingsCount", siblingsCount );
    binder.forField( siblingsCount )
            .withNullRepresentation( "" )
            .withConverter( new StringToIntegerConverter( Integer.valueOf( 0 ), "integers only" ) )
            .bind( Child::getSiblingsCount, Child::setSiblingsCount );
    binder.bindInstanceFields( this );

    ...

    // Workaround for Vaadin issue #8858
    addComponent( manualBoundComponents.get( "siblingsCount" ) );
    //addComponent( siblingsCount );

    ...
}

更新

Fix #8998 Make bindInstanceFields not bind fields already bound using functions

source code for that fix至少出现在Vaadin 8.1.0 alpha 4 pre-release(可能还有其他)中。

Basil Bourque更新…

如上所示,您的想法是在手动绑定不兼容(Integer)属性后使用Binder::bindInstanceFields确实对我有用。您抱怨说,在实验代码中,对Binder::bindInstanceFields的调用未能遵循记录的行为,即该调用“不覆盖现有绑定”。

但这似乎对我有用。这是Vaadin 8.1.0 alpha 3的示例应用程序。首先,我手动绑定yearOfBirth属性。然后,我使用binder.bindInstanceFields绑定带注释的@PropertyIdname属性。这两个属性的字段都会显示并响应用户的编辑。

我是否错过了某些东西,或者它是否可以正常工作?如果我输入有误,请删除此部分。
package com.example.vaadin.ex_formatinteger;

import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.data.Binder;
import com.vaadin.data.converter.StringToIntegerConverter;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.ui.*;

import javax.servlet.annotation.WebServlet;

/**
 * This UI is the application entry point. A UI may either represent a browser window
 * (or tab) or some part of a html page where a Vaadin application is embedded.
 * <p>
 * The UI is initialized using {@link #init(VaadinRequest)}. This method is intended to be
 * overridden to add component to the user interface and initialize non-component functionality.
 */
@Theme ( "mytheme" )
public class MyUI extends UI {
    Person person;

    //@PropertyId ( "honorific" )
    final TextField honorific = new TextField ( "Honorific:" ); // Bean property.

    //@PropertyId ( "name" )
    final TextField name = new TextField ( "Full name:" ); // Bean property.

    // Manually bind property to field.
    final TextField yearOfBirthField = new TextField ( "Year of Birth:" ); // Bean property.

    final Label spillTheBeanLabel = new Label ( ); // Debug. Not a property.

    @Override
    protected void init ( VaadinRequest vaadinRequest ) {
        this.person = new Person ( "Ms.", "Margaret Hamilton", Integer.valueOf ( 1936 ) );

        Button button = new Button ( "Spill" );
        button.addClickListener ( ( Button.ClickEvent e ) -> {
            spillTheBeanLabel.setValue ( person.toString ( ) );
        } );

        // Binding
        Binder < Person > binder = new Binder <> ( Person.class );
        binder.forField ( this.yearOfBirthField )
              .withNullRepresentation ( "" )
              .withConverter ( new StringToIntegerConverter ( Integer.valueOf ( 0 ), "integers only" ) )
              .bind ( Person:: getYearOfBirth, Person:: setYearOfBirth );
        binder.bindInstanceFields ( this );
        binder.setBean ( person );


        setContent ( new VerticalLayout ( honorific, name, yearOfBirthField, button, spillTheBeanLabel ) );
    }

    @WebServlet ( urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true )
    @VaadinServletConfiguration ( ui = MyUI.class, productionMode = false )
    public static class MyUIServlet extends VaadinServlet {
    }
}

和简单的Person类。
package com.example.vaadin.ex_formatinteger;

import java.time.LocalDate;
import java.time.ZoneId;

/**
 * Created by Basil Bourque on 2017-03-31.
 */
public class Person {

    private String honorific ;
    private String name;
    private Integer yearOfBirth;

    // Constructor
    public Person ( String honorificArg , String nameArg , Integer yearOfBirthArg ) {
        this.honorific = honorificArg;
        this.name = nameArg;
        this.yearOfBirth = yearOfBirthArg;
    }

    public String getHonorific ( ) {
        return honorific;
    }

    public void setHonorific ( String honorific ) {
        this.honorific = honorific;
    }

    // name property
    public String getName ( ) {
        return name;
    }

    public void setName ( String nameArg ) {
        this.name = nameArg;
    }

    // yearOfBirth property
    public Integer getYearOfBirth ( ) {
        return yearOfBirth;
    }

    public void setYearOfBirth ( Integer yearOfBirth ) {
        this.yearOfBirth = yearOfBirth;
    }

    // age property. Calculated, so getter only, no setter.
    public Integer getAge ( ) {
        int age = ( LocalDate.now ( ZoneId.systemDefault ( ) )
                             .getYear ( ) - this.yearOfBirth );
        return age;
    }

    @Override
    public String toString ( ) {
        return "Person{ " +
                "honorific='" + this.getHonorific () + '\'' +
                ", name='" + this.getName ()  +
                ", yearOfBirth=" + this.yearOfBirth +
                ", age=" + this.getAge () +
                " }";
    }
}

07-26 02:58