正如这里在Swing的上下文中所指出的,GUI设计经常使用观察者模式。经常使用EventListenerList
中规定的方案后,是否有一个Java的FX示例,例如Converter
,专注于模式本身?
正如这里所指出的,JavaFX架构倾向于通过实现可观察
接口的类绑定GUI元素。为此,Irina Fedortsova在《JavaFX for Swing Developers:在JavaFX中实现Swing应用程序》第5章中将原始的Converter
改编为JavaFX。
我总结了下面的程序,更新到Java8,并删除了对现已弃用的构建器API的依赖。在下面的变体中,
>
名为米
的DoubleProperty
用作应用程序的可观察
模型。
Control
的实例,例如TextField
、ComboBox
和Slider
,每个功能都作为模型的视图,以及为用户提供控制交互的方式。
在ConversionPanel
中,添加到ComboBox
的InvalidationListener
根据需要更新模型的TextField
视图,以反映当前选择的Unit
;添加到TextField
的类似侦听器会随着用户键入而更新模型本身。
Slider
在ConversionPanel
的实例之间共享相同的模型,链接滑块和监听模型的任何控件。
slider.valueProperty().bindBidirectional(meters);
每个ComboBox
也有一个模型,可观察列表
,用户可以从中选择Unit
的实例。
代码:转换器/Unit.java
/*
* 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;
}
}
代码:转换器/ConversionPanel.java
/*
* 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;
}
}
代码:转换器/Converter.java
/*
* 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 https://stackoverflow.com/a/31909942/230513
* @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();
}
}