Статьи

Как разрешить пользователям настраивать пользовательский интерфейс

идея

Воспользуйтесь преимуществами декларативного шаблона проектирования JavafX / FXML и разрешите пользователям настраивать определенное представление без какого-либо кодирования, просто открывая его, например, с помощью SceneBuilder, чтобы переупорядочить макет или добавить новые элементы управления или даже изменить стиль в соответствии с потребностями пользователей.

Файл FXML + CSS могут быть размещены в любом месте, где они доступны через URL. Пользователь должен знать только интерфейс / методы назначенного класса контроллера внутри FXML.

Пульт дистанционного управления

Предполагая, что этот простой класс демонстрационного контроллера предоставляет методы для удаленного управления устройствами и отправки сообщений MQTT, пользователь может настроить свое собственное дистанционное управление.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 и remote.css

Обратите внимание на ссылки de.jensd.shichimifx.demo.ext.RemoteController и remote.css .

Таким образом, в основном действия контроллера могут быть вызваны через:

1
onAction="#onTest".

Ницца:

Если вы добавите:

1
<?language javascript?>

в FXML также возможно передавать параметры посредством вызова JavaScript через экземпляр controller .

1
2
onAction=controller.onTest('OFF')
onAction=controller.onSwitch('a',1,1,'ON')

К сожалению, я не могу найти больше документации об этой функции, чем -> это , но каким-то образом это волшебным образом работает 😉 Можно даже передавать различные типы параметров.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?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>

На основе этого примера пользователь может просто открыть FXMl с помощью SceneBuilder и добавить новую кнопку, вызывающую метод controller.onSwitch () для управления различными / новыми устройствами, установленными для домашней автоматизации.

FxmlUtils

Следующая версия ShichimiFX будет содержать новый класс Utilily для загрузки FXML, как показано в ExternalFXMLDemoController . Обратите внимание, что загруженная панель добавляется в центр externalPane (BorderPane) демонстрационного приложения через onLoadExternalFxml() :

Click to share on Twitter
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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);
        }
    }
}