Rudolphine
Rudolphine

Reputation: 29

How to load FXML in to a class instance?

I need to load fxml form to class object and manipulate it. Is it possible ? Currently I'm trying to do so:

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

<!-- imports -->
<?import javafx.geometry.Insets ?>
<?import javafx.scene.control.Label ?>
<?import javafx.scene.text.Font ?>
<?import org.bsoftware.parcel.domain.components.LogItem ?>

<!-- layout -->
<LogItem spacing = "3" xmlns:fx = "http://javafx.com/fxml" >
   <padding>
      <Insets bottom = "2" left = "5" right = "5" top = "2" />
   </padding>
   <Label fx:id = "labelTimestamp">
      <font>
         <Font name = "System Bold" size = "11" />
      </font>
   </Label>
   <Label fx:id = "labelMessage">
      <font>
         <Font name = "System Italic" size = "11" />
      </font>
   </Label>
</LogItem>

@Getter
public class LogItem extends HBox
{
    @FXML
    private Label labelTimestamp;

    @FXML
    private Label labelMessage;
}

And I'm trying to access it like so

final LogItem logItem = FXMLLoader.load(Objects.requireNonNull(LogView.class.getResource("/fxml/components/log_item.fxml")));

But here i always get null pionter exeption

logItem.getLabelTimestamp()

Upvotes: 0

Views: 843

Answers (1)

jewelsea
jewelsea

Reputation: 159291

Use the fx:root construct as documented in:

Example App

image

Implementation notes:

  • Uses the fx:root construct to link the FXML loaded nodes to a Java object.
  • Adds a CSS file to separate style from layout.
  • Applies an MVC pattern, abstracting out a model object (LogItem) from a view object (LogItemView).
  • Encapsulates the view elements (nodes) within the view, rather than exposing them via API.
  • Exposes the LogItem as a property of the LogItemView, so that the log item can be queried directly for data and also replaced within the view if necessary.
  • Uses a record for an immutable read-only LogItem rather than Lombok.
  • Uses an Instant to represent a time.
  • Uses a date formatter to format the instant as a date string in the default time zone.
  • Uses a change listener to refresh the view if the logitem property associated with the view changes.

module-info.java

module com.example.logdemo {
    requires javafx.controls;
    requires javafx.fxml;

    opens com.example.logdemo to javafx.fxml;
    exports com.example.logdemo;
}

LogApplication.java

package com.example.logdemo;

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

import java.io.IOException;
import java.time.Instant;

public class LogApplication extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        LogItemView logItemView = new LogItemView();
        logItemView.setLogItem(
            new LogItem(Instant.now(), "hello, world")
        );

        stage.setScene(new Scene(logItemView));
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}

LogItem.java

package com.example.logdemo;

import java.time.Instant;

public record LogItem(
    Instant timestamp, 
    String message
) {}

LogItemView.java

package com.example.logdemo;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;

import java.io.IOException;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;

public class LogItemView extends HBox  {
    @FXML
    private Label timestamp;

    @FXML
    private Label message;

    private final ObjectProperty<LogItem> logItem = new SimpleObjectProperty<>();

    private static final DateTimeFormatter formatter =
            DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
                    .withZone(ZoneId.systemDefault());

    public LogItemView() throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(
            LogItemView.class.getResource(
                "log-item.fxml"
            )
        );
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);
        fxmlLoader.load();

        logItem.addListener((observable, oldValue, newValue) -> {
            if (newValue == null) {
                timestamp.setText(null);
                message.setText(null);
            } else {
                timestamp.setText(
                        formatter.format(
                           logItem.getValue().timestamp()
                        )
                );
                message.setText(
                        logItem.getValue().message()
                );
            }
        });
    }

    public LogItem getLogItem() {
        return logItem.get();
    }

    public void setLogItem(LogItem logItem) {
        this.logItem.set(logItem);
    }

    public ObjectProperty<LogItem> logItemProperty() {
        return logItem;
    }
}

log-item.fxml

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

<?import javafx.scene.control.Label ?>
<?import com.example.logdemo.LogItemView?>

<?import java.net.URL?>
<fx:root type="com.example.logdemo.LogItemView" styleClass="log-item" xmlns:fx = "http://javafx.com/fxml">
    <Label fx:id = "timestamp" styleClass="timestamp"/>
    <Label fx:id = "message" styleClass="message"/>
    <stylesheets>
        <URL value="@log-item.css" />
    </stylesheets>
</fx:root>

log-item.css

.log-item {
    -fx-padding: 2px 5px 2px 5px;
    -fx-spacing: 8px;

    -fx-font-family: monospace;
    -fx-font-size: 15px;
}

.log-item > .timestamp {
    -fx-font-weight: bold;
}

.log-item > .message {
    -fx-font-style: italic;
}

Upvotes: 2

Related Questions