JavaFX Custom-Komponente mit Custom-Controller

M4cM4rco0707

Neues Mitglied
Hallo,

ich versuche einen Custom-Button mit einem Custom-Controller zu erstellen. Die Custom-Komponente wird bereits im Application-Window angezeigt, aber ich hab einige Probleme mit den Properties.
Projekt Struktur:
  • Main.class --> Zeigt das Application Window
  • TopBarBtn.class --> Custom-Controller
  • TopBarCloseBtn.class --> Custom-Komponente
  • closeBtn.fxml --> Style für die Custom-Komponente
Custom-Controller (TopBarBtn.class):
[CODE lang="java" title="Custom-Controller"]package controller;

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import java.net.URL;
import java.util.ResourceBundle;

public class TopBarBtn implements Initializable {

public ImageView getIv() {
return iv;
}

public HBox getHbox() {
return hbox;
}

@FXML private ImageView iv;

@FXML private HBox hbox;

@Override
public void initialize(URL url, ResourceBundle resourceBundle) {

}
}[/CODE]

Custom-Komponente (TopBarCloseBtn.class):
[CODE lang="java" title="Custom-Komponente (TopBarCloseBtn.class):"]package custom;

import controller.TopBarBtn;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.layout.HBox;
import java.io.IOException;

public class TopBarCloseBtn extends HBox {
TopBarBtn controller;
private final StringProperty imageUrl = new SimpleStringProperty("/res/image/white/closeX.png");

public final StringProperty imageUrl() { return imageUrl; }

public final String getImageUrl() { return imageUrl.get(); }

public final void setImageUrl(String imageUrl) {
this.imageUrl.set(imageUrl);
controller.getIv().setImage(new Image(imageUrl));
}

public TopBarCloseBtn() {
super();
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/res/template/closeBtn.fxml"));
controller = new TopBarBtn();
loader.setController(controller);
Node n = loader.load();
this.getChildren().add(n);
controller.getIv().setImage(new Image(imageUrl.get()));
} catch (IOException e) {

}
}
}[/CODE]

closeBtn.fxml:
[CODE lang="xml" title="closeBtn.fxml"]<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>

<HBox id="hbox" xmlns:fx="http://javafx.com/fxml/1" alignment="CENTER" prefHeight="27" prefWidth="40"
styleClass="TopBarButtonClose" stylesheets="@../style/TopBarButton.css">
<children>
<ImageView id="iv" fitHeight="13" fitWidth="13" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="/res/image/white/closeX.png"/>
</image>
</ImageView>
</children>
</HBox>[/CODE]


Das Problem besteht darin, dass in der Zeile controller.getIv().setImage(new Image(imageUrl.get())); in meiner Komponenten Klasse eine NullPointerException erzeugt (am Ende der komplette Fehlertext). Im Debug Modus wird mir auch angezeigt das alle Variabeln des Custom Controllers nicht initialisiert sind. Ich dachte allerdings, dass der @FXML zusatz ein Feld angibt welches durch den FXMLLoader initialisiert wird. Ich finde einfach meinen Fehler nicht. Was habe ich falsch gemacht? Gerne könnt ihr auch den restlichen Code kritisieren und paar verbesserungs Vorschläge geben, da dies meine ersten Schritte mit JavaFX sind.

MfG
Marco

Caused by: java.lang.NullPointerException: Cannot invoke "javafx.scene.image.ImageView.setImage(javafx.scene.image.Image)" because the return value of "controller.TopBarBtn.getIv()" is null at custom.TopBarCloseBtn.(TopBarCloseBtn.java:33) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:78) at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499) at java.base/java.lang.reflect.ReflectAccess.newInstance(ReflectAccess.java:128) at java.base/jdk.internal.reflect.ReflectionFactory.newInstance(ReflectionFactory.java:350) at java.base/java.lang.Class.newInstance(Class.java:642) at javafx.fxml/javafx.fxml.FXMLLoader$InstanceDeclarationElement.constructValue(FXMLLoader.java:1021) at javafx.fxml/javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:756) at javafx.fxml/javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2808) at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2634) ... 17 more
 
Du erzeugst den Controller ja auch selbst:
Java:
controller = new TopBarBtn();

Dadurch hast Du natürlich keinerlei Injection vom FXMLLoader.

Daher solltest Du den Controller im FXML hinterlegen und durch den FXMLLoader erstellen lassen.
 
Du erzeugst den Controller ja auch selbst:
Java:
controller = new TopBarBtn();

Dadurch hast Du natürlich keinerlei Injection vom FXMLLoader.

Daher solltest Du den Controller im FXML hinterlegen und durch den FXMLLoader erstellen lassen.
Ich habe gerade das Problem gelöst. In meiner closeBtn.fxml fehlte die fx:id. Jetzt funktioniert mein Ansatz auch.
Soweit ich mich selbst informiert habe, kann ich den Controller selber erzeugen, solange meine fxml nicht mit <fx:root> startet (ist etwas ungewöhnlich, aber so konnte ich eben den Controller und die Componente trennen)
 
Ich würde vermutlich eher ein custom control daraus machen. Hier ein kleiner Einstieg, es fehlen noch die Events und States usw. aber die Idee sollte klar werden. Deine Komponente, nur als JavaFx control.
Java:
package de.sphw.samples.controls;

import javafx.scene.control.Control;
import javafx.scene.control.Skin;

public class ImageBtn extends Control {

    protected String imageSource;

    public ImageBtn(String imageSource) {
        getStyleClass().add("image-btn");

        this.imageSource = imageSource;
    }

    @Override
    public String getUserAgentStylesheet() {
        return ImageBtn.class.getResource("image-btn.css").toExternalForm();
    }

    @Override
    protected Skin<?> createDefaultSkin() {
        return new ImageBtnSkin(this);
    }
}
Java:
package de.sphw.samples.controls;

import javafx.scene.control.SkinBase;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

public class ImageBtnSkin extends SkinBase<ImageBtn> {

    private ImageView imageView;
    private Image image;

    public ImageBtnSkin(ImageBtn control) {
        super(control);

        image = new Image(getSkinnable().imageSource);
        imageView = new ImageView(image);
        imageView.setFitHeight(25);
        imageView.setFitWidth(25);
        imageView.setPreserveRatio(true);
        imageView.setPickOnBounds(true);

        getChildren().add(imageView);
    }

    @Override
    protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
        return imageView.minWidth(height);
    }

    @Override
    protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
        return imageView.minHeight(width);
    }

    @Override
    protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
        return imageView.prefWidth(height) + leftInset + rightInset;
    }

    @Override
    protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
        return imageView.prefHeight(width) + topInset + bottomInset;
    }

    @Override
    protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
        return computePrefWidth(height, topInset, rightInset, bottomInset, leftInset);
    }

    @Override
    protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
        return computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
    }

    @Override
    protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) {
        imageView.resizeRelocate(contentX, contentY, contentWidth, contentHeight);
    }
}
CSS:
.image-btn {
   // hier dein default style
}

Java:
ImageBtn imageBtn = new ImageBtn("/res/image/white/closeX.png");
 

Zurück
Oben