Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to extend custom JavaFX components that use FXML

How do I properly extend a custom JavaFX component to add or modify its GUI components if it uses FXML for the view?

As an example scenario, suppose I use the following approach of creating a custom JavaFX component:

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();
    }
}

The SpecializedButton class loads the FXML view (SpecializedButton.fxml) and acts as the controller for it.

The SpecializedButton FXML view just creates an HBox with two AnchorPanes and a Label and Button in the left and right sides, respectively. When the button is clicked, it calls doSomething() in the SpecializedButton controller class.


Problem

With this setup, I am using FXML to separate the view from the application logic.

However, if I want to create a new HighlySpecializedButton class that extends SpecializedButton, I don't know how to go about "extending" the view as well.

If I wanted to use the SpecializedButton view, but modify it for the HighlySpecializedButton, I'd have to copy the view code into a new FXML file, resulting in duplicate code instead of proper inheritance.

If I wasn't using FXML, and was creating the GUI components within a Java class instead, extending that class would give me the ability to properly inherit and add/modify the components from the super class.

I am clearly misunderstanding some very important concepts about JavaFX.


Question

Are FXML views in this case so "dumb" that I'd have to create an entirely new FXML view every time I wanted to inherit from a custom component?

Or, if I am misunderstanding the real issue, what is the proper way of creating extendable custom JavaFX components, with or without FXML?

I'd greatly appreciate any insight. Thanks in advance!

like image 228
ThatGuyThatDoesThings Avatar asked Jul 22 '15 16:07

ThatGuyThatDoesThings


People also ask

Can two FXML files have the same controller?

It's possible to use the same controller class for multiple FXML files, but your code will be really hard to follow, and it's a very bad idea. (Also note that using the fx:controller attribute, you will have a different controller instance each time you call FXMLLoader.

How do controllers work in JavaFX using FXML?

Specifying Inside the FXML File Directly Following is a syntax to Specifying inside the FXML file directly. Explanation: fx:controller is used to including controller class. Button onAction attribute used for performing clicking action.


1 Answers

From what I understand, you want to be able to extend existing components and avoid copy & pasting the entire markup for the new component.

You can define an extension point for the component with the @DefaultProperty annotation. Look at e.x. javafx.scene.layout.Pane: There is a ObservableList getChildren() defined with @DefaultProperty("children"). In this way, when you use e.x an HBox (extends Pane) as the root element for your component, all child components are added to the children property defined by the Pane.

In the same way you can define extension points in FXML:

SpecializedButton.fxml (I simplified it a little for this example)

<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>

The HBox fx:id="extension" will be my extension point:

@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>

And:

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

As you can see, I need only to define fxml for the super-specialized component.

This code will work only on JavaFX >= 2.1. Beforehand the component injection for the super class didn't worked with the FXMLLoader.

I added an working example on github: https://github.com/matrak/javafx-extend-fxml

like image 77
mrak Avatar answered Oct 03 '22 12:10

mrak