在 JavaFX GUI 设计中利用观察者模式

Leveraging the observer pattern in JavaFX GUI design

如前所述here in the context of Swing, GUI design makes frequent use of the observer pattern. Having frequently used the scheme prescribed in EventListenerList, is there a Java FX example, such as Converter,重点是模式本身?

如前所述here, the JavaFX architecture tends to favor binding GUI elements via classes that implement the Observable interface. Toward this end, Irina Fedortsova has adapted the original Converter to JavaFX in Chapter 5 of JavaFX for Swing Developers: Implementing a Swing Application in JavaFX

我概括了下面的程序,更新为 Java 8 and removing the dependency on the now deprecated builder API。在下面的变体中,

  • 名为 metersDoubleProperty 作为应用程序的 Observable 模型.

  • Control 的实例,例如 TextFieldComboBoxSlider,每个函数都作为 视图[= 模型的78=],以及为用户提供一种控制交互的方法。

  • 中一个ConversionPanel,一个InvalidationListener添加到ComboBox更新TextField查看 model 根据需要反映当前 selected Unit;添加到 TextField 的类似侦听器会在用户输入时更新 模型 本身。

  • 相同的 模型SliderConversionPanel 个实例之间共享 , 链接滑块和任何监听 model.

    的控件
      slider.valueProperty().bindBidirectional(meters);
    
  • 每个ComboBox也有一个模型ObservableList,用户可以从中select Unit.

代码:

/*
 * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle or the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package converter;

/**
 * @see http://docs.oracle.com/javafx/2/swing/port-to-javafx.htm
 */
public class Unit {

    String description;
    double multiplier;

    Unit(String description, double multiplier) {
        super();
        this.description = description;
        this.multiplier = multiplier;
    }

    @Override
    public String toString() {
        String s = "Meters/" + description + " = " + multiplier;
        return s;
    }
}

/*
 * Copyright (c) 2012, 2013 Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 */
package converter;

/**
 * @see http://docs.oracle.com/javafx/2/swing/port-to-javafx.htm
 */
import java.text.NumberFormat;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.DoubleProperty;
import javafx.collections.ObservableList;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.util.StringConverter;

public class ConversionPanel extends TitledPane {

    private static final int MAX = 10000;
    private static final int DIGITS = 3;

    private final TextField textField = new TextField();
    private final Slider slider = new Slider(0, MAX, 0);
    private final ComboBox<Unit> comboBox;
    private NumberFormat numberFormat = NumberFormat.getNumberInstance();
    private DoubleProperty meters;

    {
        numberFormat.setMaximumFractionDigits(DIGITS);
    }

    private InvalidationListener fromMeters = (Observable o) -> {
        if (!textField.isFocused()) {
            textField.setText(numberFormat.format(meters.get() / getMultiplier()));
        }
    };

    private InvalidationListener toMeters = (Observable o) -> {
        if (textField.isFocused()) {
            try {
                Number n = numberFormat.parse(textField.getText());
                meters.set(n.doubleValue() * getMultiplier());
            } catch (Exception ignored) {
            }
        }
    };

    public ConversionPanel(String title, ObservableList<Unit> units, DoubleProperty meters) {
        setText(title);
        setCollapsible(false);
        comboBox = new ComboBox<>(units);
        comboBox.getSelectionModel().select(0);
        comboBox.setConverter(new StringConverter<Unit>() {

            @Override
            public String toString(Unit t) {
                return t.description;
            }

            @Override
            public Unit fromString(String string) {
                throw new UnsupportedOperationException("Not supported yet.");
            }
        });
        setContent(new HBox(new VBox(textField, slider), comboBox));

        this.meters = meters;
        meters.addListener(fromMeters);
        comboBox.valueProperty().addListener(fromMeters);
        textField.textProperty().addListener(toMeters);
        slider.valueProperty().bindBidirectional(meters);
        fromMeters.invalidated(null);
    }

    /**
     * Returns the multiplier for the currently selected unit of measurement.
     */
    public double getMultiplier() {
        return comboBox.getValue().multiplier;
    }
}

/*
 * Copyright (c) 2012, 2013 Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 */
package converter;

import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

/**
 * @see 
 * @see http://docs.oracle.com/javafx/2/swing/port-to-javafx.htm
 */
public class Converter extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    private final ObservableList<Unit> metricDistances;
    private final ObservableList<Unit> usaDistances;
    private final DoubleProperty meters = new SimpleDoubleProperty(1);

    public Converter() {
        //Create Unit objects for metric distances, and then
        //instantiate a ConversionPanel with these Units.
        metricDistances = FXCollections.observableArrayList(
            new Unit("Centimeters", 0.01),
            new Unit("Meters", 1.0),
            new Unit("Kilometers", 1000.0));

        //Create Unit objects for U.S. distances, and then
        //instantiate a ConversionPanel with these Units.
        usaDistances = FXCollections.observableArrayList(
            new Unit("Inches", 0.0254),
            new Unit("Feet", 0.3048),
            new Unit("Yards", 0.9144),
            new Unit("Miles", 1609.34));
    }

    @Override
    public void start(Stage stage) {
        stage.setScene(new Scene(new VBox(
            new ConversionPanel("Metric System", metricDistances, meters),
            new ConversionPanel("U.S. System", usaDistances, meters))));
        stage.show();
    }
}