Walfrat
Walfrat

Reputation: 5353

How to specialize a components of JavaFX and hide its API to only show mine

As said in the title I want to specialize the usage of a TableView that I will reuse many time, that speciazation contains :

I use raw FXML files and Controller, no UI drag and drop building.

In order to keep the usage of my component the easiest possible I would like to hide the component part of JavaFX and only allow my methods, how to do it ?

Upvotes: 0

Views: 121

Answers (2)

fabian
fabian

Reputation: 82461

Create a class extending Control. Add the methods you want the user to access to this class.

Create a skin for this class and implement the behavior you don't want the user to access there.

Benefits:

  • Hides the implementation details from user.
  • Allows the user to replace the "private" behavior, if neccessary.
  • Allows you to access the "public" behavior form the node directly.

Example

Control

public class MyControl extends Control {

    @Override
    protected Skin<?> createDefaultSkin() {
        return new MyControlSkin(this);
    }

    private final StringProperty text = new SimpleStringProperty();

    public final String getText() {
        return this.text.get();
    }

    public final void setText(String value) {
        this.text.set(value);
    }

    public final StringProperty textProperty() {
        return this.text;
    }

}

Skin

public class MyControlSkin extends SkinBase<MyControl> {

    public MyControlSkin(MyControl control) {
        super(control);

        Text text = new Text();
        text.textProperty().bind(control.textProperty());

        getChildren().setAll(text);
    }

}

Use

@Override
public void start(Stage primaryStage) {
    final MyControl control = new MyControl();
    Button btn = new Button("Say 'Hello World'");
    btn.setOnAction((ActionEvent event) -> {
        control.setText("Hello World!");
    });

    Scene scene = new Scene(new VBox(10, btn, control));

    primaryStage.setScene(scene);
    primaryStage.show();
}

Note that it does not matter how you create the UI. It could be directly created from java or loaded from fxml - this does not matter. You could e.g. use the skin as fxml controller and root:

<fx:root type="javafx.scene.control.SkinBase" xmlns:fx="http://javafx.com/fxml">
    <children>
        <Text fx:id="text"/>
    </children>
</fx:root>
@FXML
private Text text;

public MyControlSkin(MyControl control) throws IOException {
    super(control);
    getChildren().clear();

    FXMLLoader loader = new FXMLLoader(someUrl);
    loader.setRoot(this);
    loader.setController(this);
    loader.load();

    text.textProperty().bind(control.textProperty());
}

BTW: The duplicate filtering would IMHO be better of in a seperate class, like a TransformationList. This way you could reuse the behavior independent of the UI which would allow easier reuse (e.g. For use with a ListView).

Upvotes: 2

Walfrat
Walfrat

Reputation: 5353

Here is the current way I found to do it use an interface an use that interface when using @FXML injection.

My specialized components :

public class EquipmentTableView extends AnchorPane implements IEquipmentTableView{[...]}

Where AnchorPane is from JavaFX and the interface from me.

The FXML of the component :

<fx:root type="javafx.scene.layout.AnchorPane" xmlns:fx="http://javafx.com/fxml">
    <TableView fx:id="tableView"
        AnchorPane.topAnchor="0.0" AnchorPane.bottomAnchor="0.0"
        AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">

    </TableView>
</fx:root>

The FXML to include the component :

<EquipmentTableView fx:id="tableView"/>

The injection in the controller

public class ReadController {  
        // use the interface here 
        @FXML private IEquipmentTableView tableView;

        public static String FXML="read.fxml";
        public static String TITLE_KEY="read.title";

        @FXML
        void initialize(){
            tableView.addAll(Arrays.asList(
                    new Equipment("name1", "partNumber1", "MFC1", "00000000001", "3400000000000001"),
                    new Equipment("name2", "partNumber2", "MFC2", "00000000002", "3400000000000002"),
                    new Equipment("name3", "partNumber3", "MFC3", "00000000003", "3400000000000003")));
        }
}

To sum it up :

  • By using the TableView inside the components instead of inheriting it I don't allow to manipulate the TableView. I inherit AnchorPane instead because I want my table view to auto-resize, you can inherits from Pane otherwise.
  • I am allowed to manipulate it using only the interface methods since I cannot access the TableView.
  • Using only the interface disallow myself to manipulate the AnchorPane for other purposes or having to search through the methods that are mines.

Upvotes: 1

Related Questions