How to allow users to customize the UI
Idea
Take advantage of the declarative design pattern of JavafX/FXML and allow users to customize a certain view without any coding just by opening it with e.g. SceneBuilder to re-arrange the layout or add new controls or even change the style according to the users needs.
The FXML file + CSS can be basically be placed whereever they are reachable via a URL. The user must only know the interface/methods of the assigned controller class inside the FXML.
RemoteController
Assuming this simple demo controller class provides methods to remotely control devices and to send MQTT messages, a user is able to customize his own remote control.
public class RemoteController{ @FXML public void onTest(){ Alert alert = new Alert(Alert.AlertType.INFORMATION); alert.setContentText(""); alert.setHeaderText("WORKS!"); alert.show(); } public void onTest(String value){ Alert alert = new Alert(Alert.AlertType.INFORMATION); alert.setHeaderText("WORKS!"); alert.setContentText(value); alert.show(); } public void onSwitch(String houseCode, int groudId, int deviceId, String command){ Alert alert = new Alert(Alert.AlertType.INFORMATION); alert.setHeaderText("Switch!"); alert.setContentText(String.format("Command: send %s %d %d %s", houseCode, groudId, deviceId, command)); alert.show(); } }
remote.fxml and remote.css
Note the referenced de.jensd.shichimifx.demo.ext.RemoteController
and remote.css
.
So basically controller actions can be called via:
onAction="#onTest".
Nice:
If you add:
<?language javascript?>
to FXML, it’s also possible to pass parameters by a JavaScript call via the controller
-instance.
onAction=controller.onTest('OFF') onAction=controller.onSwitch('a',1,1,'ON')
Unfortunately I can’t find more documentation about this feature than -> this, but somehow it magically it works ;-). Its even possible to pass different types of parameters.
<?xml version="1.0" encoding="UTF-8"?> <?language javascript?> <?import javafx.geometry.*?> <?import java.lang.*?> <?import java.net.*?> <?import java.util.*?> <?import javafx.scene.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <VBox alignment="TOP_CENTER" prefHeight="400.0" prefWidth="600.0" spacing="20.0" styleClass="main-pane" stylesheets="@remote.css" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.jensd.shichimifx.demo.ext.RemoteController"> <children> <Label styleClass="title-label" text="Universal Remote" /> <HBox alignment="CENTER_RIGHT" spacing="20.0"> <children> <Label layoutX="228.0" layoutY="96.0" styleClass="sub-title-label" text="Light Frontdoor" /> <Button layoutX="43.0" layoutY="86.0" mnemonicParsing="false" onAction="#onTest" prefWidth="150.0" styleClass="button-on" text="ON" /> <Button layoutX="411.0" layoutY="86.0" mnemonicParsing="false" onAction="#onTest" prefWidth="150.0" styleClass="button-off" text="OFF" /> </children> <padding> <Insets left="10.0" right="10.0" /> </padding> </HBox> <HBox alignment="CENTER_RIGHT" spacing="20.0"> <children> <Label layoutX="228.0" layoutY="96.0" styleClass="sub-title-label" text="Light Garden" /> <Button layoutX="43.0" layoutY="86.0" mnemonicParsing="false" onAction="controller.onTest('ON')" prefWidth="150.0" styleClass="button-on" text="ON" /> <Button layoutX="411.0" layoutY="86.0" mnemonicParsing="false" onAction="controller.onTest('OFF')" prefWidth="150.0" styleClass="button-off" text="OFF" /> </children> <padding> <Insets left="10.0" right="10.0" /> </padding> </HBox> <HBox alignment="CENTER_RIGHT" spacing="20.0"> <children> <Label layoutX="228.0" layoutY="96.0" styleClass="sub-title-label" text="Light Garden" /> <Button layoutX="43.0" layoutY="86.0" mnemonicParsing="false" onAction="controller.onSwitch('a', 1,1,'ON')" prefWidth="150.0" styleClass="button-on" text="ON" /> <Button layoutX="411.0" layoutY="86.0" mnemonicParsing="false" onAction="controller.onTest('OFF')" prefWidth="150.0" styleClass="button-off" text="OFF" /> </children> <padding> <Insets left="10.0" right="10.0" /> </padding> </HBox> </children> <padding> <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" /> </padding> </VBox>
Based on this example a user is able to simple open the FXMl with SceneBuilder and to add new Button calling the controller.onSwitch() method to control different/new devices installed for home automation.
FxmlUtils
The next release of ShichimiFX will contain a new Utilily class to load FXML as shown in the ExternalFXMLDemoController
. Note that the loaded Pane is added to the center of the externalPane
(BorderPane) of the Demo-Application via onLoadExternalFxml()
:
public class ExternalFXMLDemoController { @FXML private ResourceBundle resources; @FXML private BorderPane externalPane; @FXML private TextField fxmlFileNameTextField; @FXML private Button chooseFxmlFileButton; @FXML private Button loadFxmlFileButton; private StringProperty fxmlFileName; public void initialize() { fxmlFileNameTextField.textProperty().bindBidirectional(fxmlFileNameProperty()); loadFxmlFileButton.disableProperty().bind(fxmlFileNameProperty().isEmpty()); } public StringProperty fxmlFileNameProperty() { if (fxmlFileName == null) { fxmlFileName = new SimpleStringProperty(""); } return fxmlFileName; } public String getFxmlFileName() { return fxmlFileNameProperty().getValue(); } public void setFxmlFileName(String fxmlFileName) { this.fxmlFileNameProperty().setValue(fxmlFileName); } @FXML public void chooseFxmlFile() { FileChooser chooser = new FileChooser(); chooser.setTitle("Choose FXML file to load"); if (getFxmlFileName().isEmpty()) { chooser.setInitialDirectory(new File(System.getProperty("user.home"))); } else { chooser.setInitialDirectory(new File(getFxmlFileName()).getParentFile()); } File file = chooser.showOpenDialog(chooseFxmlFileButton.getScene().getWindow()); if (file != null) { setFxmlFileName(file.getAbsolutePath()); } } @FXML public void onLoadExternalFxml() { try { Optional<URL> url = FxmlUtils.getFxmlUrl(Paths.get(getFxmlFileName())); if (url.isPresent()) { Pane pane = FxmlUtils.loadFxmlPane(url.get(), resources); externalPane.setCenter(pane); } else { Alert alert = new Alert(Alert.AlertType.WARNING); alert.setContentText(getFxmlFileName() + " could not be found!"); alert.show(); } } catch (IOException ex) { Dialogs.create().showException(ex); } } }
Reference: | How to allow users to customize the UI from our JCG partner Jens Deters at the JavaFX Delight blog. |