it-swarm.com.ru

Как расширить пользовательские компоненты JavaFX, использующие FXML

Как правильно расширить пользовательский компонент JavaFX для добавления или изменения его компонентов GUI, если он использует FXML для представления?

В качестве примера сценария предположим, что я использую следующий подход к созданию пользовательского компонента JavaFX:

SpecializedButton.Java (Controller)

package test;

import Java.io.IOException;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;

public class SpecializedButton extends HBox
{
    @FXML private Button button;
    @FXML private Label label;

    public SpecializedButton() 
    {
        FXMLLoader loader = new FXMLLoader( getClass().getResource( "SpecializedButton.fxml" ) );

        loader.setRoot( this );
        loader.setController( this );

        try {
            loader.load();
        } catch ( IOException e ) {
            throw new RuntimeException( e );
        }
    }

    // specialized methods for this specialized button
    // ...
    public void doSomething() {
        button.setText( "Did something!" );
    }
}

SpecializedButton.fxml (View)

<?xml version="1.0" encoding="UTF-8"?>

<?import Java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.control.Button?>

<fx:root type="HBox" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <AnchorPane HBox.hgrow="ALWAYS">
         <children>
            <Label fx:id="label" text="Click to do something: " AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
         </children>
      </AnchorPane>
      <AnchorPane HBox.hgrow="ALWAYS">
         <children>
            <Button fx:id="button" onAction="#doSomething" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
         </children>
      </AnchorPane>
   </children>
</fx:root>

MyApp.Java

package test;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class MyApp extends Application
{
    public static void main( String[] args )
    {
        launch( args );
    }

    public void start( Stage stage ) throws Exception
    {
        stage.setScene( new Scene( new SpecializedButton(), 300, 50 ) );
        stage.show();
    }
}

Класс SpecializedButton загружает представление FXML (SpecializedButton.fxml) и выступает в качестве контроллера для него.

Представление SpecializedButton FXML просто создает HBox с двумя AnchorPanes и меткой и кнопкой слева и справа соответственно. Когда кнопка нажата, она вызывает doSomething () в классе контроллера SpecializedButton.


Проблема

С этой настройкой я использую FXML, чтобы отделить представление от логики приложения.

Однако, если я хочу создать новый класс HighlySpecializedButton, который расширяет SpecializedButton, я также не знаю, как можно "расширить" представление.

Если бы я хотел использовать представление SpecializedButton, но изменить его для HighlySpecializedButton, мне пришлось бы скопировать код представления в новый файл FXML, что привело бы к дублированию кода вместо правильного наследования.

Если бы я не использовал FXML и вместо этого создавал компоненты GUI внутри класса Java, расширение этого класса дало бы мне возможность правильно наследовать и добавлять/изменять компоненты из суперкласса.

Я явно неправильно понимаю некоторые очень важные понятия о JavaFX.


Вопрос

Являются ли представления FXML в этом случае настолько "глупыми", что мне придется создавать совершенно новое представление FXML каждый раз, когда я хочу наследовать от пользовательского компонента?

Или, если я неправильно понимаю реальную проблему, как правильно создать расширяемые пользовательские компоненты JavaFX с FXML или без него?

Я был бы очень признателен за понимание. Заранее спасибо!

8
ThatGuyThatDoesThings

Насколько я понимаю, вы хотите иметь возможность расширять существующие компоненты и избегать копирования и вставки всей разметки для нового компонента.

Вы можете определить точку расширения для компонента с помощью аннотации @DefaultProperty. Посмотрите на e.x. javafx.scene.layout.Pane: есть ObservableList getChildren(), определенная с @DefaultProperty("children"). Таким образом, когда вы используете e.x HBox (расширяет Pane) в качестве корневого элемента для вашего компонента, все дочерние компоненты добавляются в свойство children, определенное Pane.

Таким же образом вы можете определить точки расширения в FXML:

SpecializedButton.fxml (я немного упростил это для этого примера)

<fx:root type="HBox">
  <HBox>
    <Label fx:id="label" text="Click to do something: " />
    <HBox fx:id="extension"></HBox>
  </HBox>

  <HBox>
    <Button fx:id="button" onAction="#doSomething"/>
  </HBox>
</fx:root>

HBox fx:id="extension" будет моей точкой расширения:

@DefaultProperty(value = "extension")
public class SpecializedButton extends HBox
{
    @FXML private HBox extension;

    public ObservableList<Node> getExtension() {
        return extension.getChildren();
    }
    // ... more component specific code
}

SuperSpecializedButton.fxml

<fx:root type="SpecializedButton">
  <Label text="EXTENDED HALLO"/>
</fx:root>

А также:

public class SuperSpecializedButton extends SpecializedButton
{
    // ... more component specific code
}

Как видите, мне нужно только определить fxml для суперспециализированного компонента.

Этот код будет работать только на JavaFX> = 2.1 . Предварительно внедрение компонента для суперкласса не работало с FXMLLoader.

Я добавил рабочий пример на github: https://github.com/matrak/javafx-extend-fxml

5
mrak

Я не делал этого раньше, но вы могли бы реализовать интерфейс Initializable для настройки компонента представления при создании нового пользовательского компонента.

Чтобы добавить новый компонент (метки, кнопки, img и т.д.), Я бы обернул все дочерние компоненты в файле представления, в AnchorPane, GridPane, VBox или в тот, который вы предпочитаете добавлять больше компонентов программно. Так как вы расширяете свой класс с HBox, вы уже можете это сделать.

Вот что я бы сделал:

public class SpecializedButton extends HBox implements Initializable{

@FXML private Button button;
@FXML private Label label;

public SpecializedButton(){
    FXMLLoader loader = new FXMLLoader( getClass().getResource( "SpecializedButton.fxml" ) );

    loader.setRoot( this );
    loader.setController( this );

    try {
        loader.load();
    } catch ( IOException e ) {
        throw new RuntimeException( e );
    }
}

// specialized methods for this specialized button
// ...
public void doSomething() {
    button.setText( "Did something!" );
}

@Override
public void initialize(URL url, ResourceBundle rb) {
    button.setText("Initialized Text");
}  
}

Итак, для класса HighlySpecializedButton:

public class HighlySpecializedButton extends SpecializedButton {



public HighlySpecializedButton (){
    super();
}

// HighlySpecializedButton methods


@Override
public void initialize(URL url, ResourceBundle rb) {
    this.getChildren().add(new Button("New Button"));

}  
}

Надеюсь, это поможет вам

0
Miguel Cardenas