3

Binder在 Vaadin 8 中将 a 与具有只读计算属性的 bean 一起使用时,该属性的值从另一个属性派生,当驱动属性的值发生更改时,如何让 aTextField自动更新派生计算结果的显示?

在以下示例中,当用户更改“出生年份”字段时,如何让“年龄”字段更新其计算?

在此处输入图像描述

Vaadin 8 的完整工作示例。

package com.example.val;

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;
import java.time.LocalDate;
import java.time.ZoneId;

/**
 * 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;
    Binder < Person > binder;

    @Override
    protected void init ( VaadinRequest vaadinRequest ) {
        // Data model
        this.person = new Person ( "Jean-Luc", 1955 );

        // Widgets
        final TextField nameField = new TextField ( "Type the person’s name here:" );
        final TextField yearOfBirthField = new TextField ( "Type the year of birth here:" );
        final TextField ageField = new TextField ( "Approximate age:" );
        ageField.setReadOnly ( true );
        final Label beanToString = new Label ( );

        // Binder
        this.binder = new Binder <> ( );
        binder.forField ( nameField )
                .bind ( Person:: getName, Person:: setName );
        binder.forField ( yearOfBirthField )
                .withConverter ( new StringToIntegerConverter ( "Input must be Integer" ) )
                .bind ( Person:: getYearOfBirth, Person:: setYearOfBirth );
        binder.forField ( ageField )
                .withConverter ( new StringToIntegerConverter ( "" ) )
                .bind ( Person:: getAge, null );
        binder.setBean ( this.person );

        final Button button = new Button ( "Save" );
        button.addClickListener ( event -> {
            if ( binder.validate ( ).isOk ( ) ) {
                // With `setBear`, the Person object is always up-to-date as long as there are no validation errors.
                // MyBackend.updatePersonInDatabase(person);
                beanToString.setValue ( this.person.toString ( ) );
            } else {  // Else bean flunks validation.
                beanToString.setValue ( "The Person bean has invalid state." );
            }
        } );


        this.setContent ( new VerticalLayout ( nameField, yearOfBirthField, ageField, button, beanToString ) );
    }

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

class Person {
    private Integer yearOfBirth;
    private String name;


    public Person ( String name_, Integer yearOfBirth_ ) {
        this.name = name_;
        this.yearOfBirth = yearOfBirth_;
    }

    public String getName ( ) {
        return name;
    }

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

    public Integer getYearOfBirth ( ) {
        return yearOfBirth;
    }

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

    // Read-only property 'age', calculated rather than stored.
    public Integer getAge ( ) {
        LocalDate today = LocalDate.now ( ZoneId.systemDefault ( ) );
        Integer years = ( today.getYear ( ) - this.yearOfBirth );
        return years;
    }

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

2 回答 2

3

我找到的最简单的方法是通过yearOfBirthField以下方式修改绑定代码:

binder.forField(yearOfBirthField)
    .withConverter(new StringToIntegerConverter("Input must be Integer"))
    .bind(Person::getYearOfBirth, (Setter<Person, Integer>) (person1, integer) -> {
                person1.setYearOfBirth(integer);
                ageField.setValue(person1.getAge().toString());
            });

这有效地将 绑定yearOfBirthFieldyearOfBirth属性并更新ageperson bean 的属性。以任何方式改变 person bean 的成员,例如通过调用person.setYearOfBirth(1977)对字段没有立即影响。Vaadin 的更新机制只在一个方向起作用。字段修改被转移到 bean,但反之则不然。

编辑

在 Vaadin 8 中,没有内置方法可以在 bean 属性发生更改时自动更新字段。当yearOfBirthField's 的值改变时,新值被传播到 person bean 并且 person 的yearOfBirth成员通过绑定自动改变。但之后 Vaadin 不会获取 bean 的所有绑定字段的当前值。所以ageField没有更新,也没有反映当前值。

因此,要使ageField显示更新值,您必须以ageField编程方式更新。您可以在 binder 上再次设置整个 bean,这会导致调用所有 getter,但最简单的方法是在设置 ageField 后设置它的值yearOfBirth。这是在 setter 绑定的建议修改中完成的。

于 2017-03-28T09:49:25.427 回答
1

显然,Binder API 的行为发生了变化,快速浏览源代码后,似乎不支持这种刷新功能。话虽如此,Vaadin 的人正在考虑这个案子……

我想说最干净/最安全的方法是在字段本身上添加一个侦听器,而不是依赖这个的Binding API 的某些行为。事实上,嗯......“新”,它可能会在即将发布的 Vaadin 版本中得到增强。更重要的是,UI 字段的更新可能是即时的。

编辑

再想一想,您可以做的最低限度是在成功验证后重新读取 bean(这与 @Morfic 通过再次设置 bean 所建议的没有太大区别):

if (binder.validate().isOk()) {

  // Force update of the Fields...
  binder.readBean(binder.getBean());

  beanToString.setValue(person.toString());
} else {
  beanToString.setValue("The Person bean has invalid state.");
}
于 2017-03-28T09:57:55.197 回答