在 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。在下面的变体中,
名为 meters
的 DoubleProperty
作为应用程序的 Observable
模型.
Control
的实例,例如 TextField
、ComboBox
和 Slider
,每个函数都作为 视图[= 模型的78=],以及为用户提供一种控制交互的方法。
在中一个ConversionPanel
,一个InvalidationListener
添加到ComboBox
更新TextField
查看 的 model 根据需要反映当前 selected Unit
;添加到 TextField
的类似侦听器会在用户输入时更新 模型 本身。
相同的 模型 由 Slider
在 ConversionPanel
的 个实例之间共享 , 链接滑块和任何监听 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();
}
}
如前所述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。在下面的变体中,
名为
meters
的DoubleProperty
作为应用程序的Observable
模型.Control
的实例,例如TextField
、ComboBox
和Slider
,每个函数都作为 视图[= 模型的78=],以及为用户提供一种控制交互的方法。在中一个
ConversionPanel
,一个InvalidationListener
添加到ComboBox
更新TextField
查看 的 model 根据需要反映当前 selectedUnit
;添加到TextField
的类似侦听器会在用户输入时更新 模型 本身。相同的 模型 由
的控件Slider
在ConversionPanel
的 个实例之间共享 , 链接滑块和任何监听 model.slider.valueProperty().bindBidirectional(meters);
每个
ComboBox
也有一个模型、ObservableList
,用户可以从中selectUnit
.
代码:
/*
* 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();
}
}