JavaFX FXML include fxml causes NullPointerException - javafx

I want to extract a button to a new fxml file and change the main label with it. Without extraction it works perfectly.
main.fxml:
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Label fx:id="label" text="default"/>
<Button onAction="#changeLabel" text="sayHello" />
</VBox>
MainController:
public class MainController {
#FXML
private Label label;
#FXML
private void changeLabel() {
label.setText("Changed");
}
}
With extraction I get NullPointerException in MainController.changeLabel()
main.fxml with include:
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Label fx:id="label" text="default"/>
<fx:include source="button.fxml"/>
</VBox>
button.fxml:
<AnchorPane xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Button onAction="#changeLabel" text="sayHello" />
</AnchorPane>
What can cause this NPE?

You should (almost?) always use a different class for controllers for different FXML files. (The only exception I can think of is if you want to define different FXML files to represent different layouts for the same controls.)
One approach is to inject the controller for the included FXML (the "Nested Controller") into the main controller. (See documentation.)
public class MainController {
#FXML
private Label label;
#FXML
private ButtonController buttonController ;
#FXML
private void initialize() {
buttonController.setOnButtonPressed(this::changeLabel);
}
private void changeLabel() {
label.setText("Changed");
}
}
public class ButtonController {
private Runnable onButtonPressed ;
public void setOnButtonPressed(Runnable onButtonPressed) {
this.onButtonPressed = onButtonPressed ;
}
public Runnable getOnButtonPressed() {
return onButtonPressed ;
}
#FXML
private void changeLabel() {
if (onButtonPressed != null) {
onButtonPressed.run();
}
}
}
And then the FXML files look like
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Label fx:id="label" text="default"/>
<fx:include fx:id="button" source="button.fxml"/>
</VBox>
and
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.ButtonController">
<Label fx:id="label" text="default"/>
<fx:include source="button.fxml"/>
</VBox>
Generally speaking, it's a bad idea for controllers to have references to each other, as it breaks encapsulation and adds unnecessary dependencies. A better approach is to use a MVC design.
public class Model {
private final StringProperty text = new SimpleStringProperty() ;
public StringProperty textProperty() {
return text ;
}
public final String getText() {
return textProperty().get();
}
public final void setText(String text) {
textProperty().set(text);
}
}
Now you can do
public class MainController {
#FXML
private Label label;
private final Model model ;
public MainController(Model model) {
this.model = model ;
}
#FXML
private void initialize() {
label.textProperty().bind(model.textProperty());
}
}
and
public class ButtonController {
private final Model model ;
public ButtonController(Model model) {
this.model = model ;
}
#FXML
private void changeLabel() {
model.setText("Changed");
}
}
The FXML files are as above, and you need to specify a controller factory when you load the FXML (so that the controllers are instantiated by passing the model instance to the constructors):
final Model model = new Model();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/path/to/main.fxml");
loader.setControllerFactory(type -> {
if (type.equals(MainController.class)) return new MainController(model);
if (type.equals(ButtonController.class)) return new ButtonController(model);
throw new IllegalArgumentException("Unexpected controller type: "+type);
});
Parent root = loader.load();
// ...

Related

JavaFX getController not updating FXML controller

I have two controllers:
Main which contains TableView Tab which displays SQLITE database tables (which contain different number of columns) in tabular format, and
Rem which contains a button which dictates which database to display (there are many such controllers).
Now if I create a method in Main which is responsible for updating and displaying TableView contents, how do I use that method in other controller?
I created two static ObservableList variables columns and rows and bind them to Tab in Main's initialize.
APP.java
public class App extends Application {
private static Scene scene;
public static Connection conn;
#Override
public void start(Stage stage) throws IOException {
try{
conn=java.sql.DriverManager.getConnection("jdbc:sqlite:sample.db");
if(conn!=null){
conn.createStatement().execute("CREATE TABLE IF NOT EXISTS tab1(Field0 TEXT, Field1 TEXT, Field2 TEXT);");
conn.createStatement().execute("CREATE TABLE IF NOT EXISTS tab2(Field3 TEXT, Field4 TEXT);");
}
}catch(Exception ex){
System.out.println(ex);
}
scene = new Scene(loadFXML("main"), 640, 480);
stage.setScene(scene);
stage.show();
}
private static Parent loadFXML(String fxml) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource(fxml + ".fxml"));
return fxmlLoader.load();
}
public static void main(String[] args) {
launch();
}
public static ResultSet getRS(String table){
String sql="SELECT * FROM "+table;
ResultSet rs=null;
try{
rs=conn.createStatement().executeQuery(sql);
}catch(Exception ex){
System.out.println(ex);
}
return rs;
}
}
MAIN.FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nic.testfx.Main">
<children>
<MenuBar>
<menus>
<Menu mnemonicParsing="false" text="File">
<items>
<MenuItem mnemonicParsing="false" onAction="#loadRem" text="Load Remaining" />
</items>
</Menu>
</menus>
</MenuBar>
<TableView fx:id="Tab" prefHeight="200.0" prefWidth="200.0" />
<VBox fx:id="remPane" prefHeight="200.0" prefWidth="100.0" />
</children>
</VBox>
MAIN.JAVA
public class Main implements Initializable {
#FXML
public TableView<ObservableList<String>> Tab;
#FXML
private VBox remPane;
#Override
public void initialize(URL url, ResourceBundle rb) {
Tab.itemsProperty().bind(getTabRow());
}
public void makeTable(ResultSet rs){
try{
var rsm=rs.getMetaData();
ObservableList<TableColumn<ObservableList<String>,String>>cols=FXCollections.observableArrayList();
for(int i=0;i<rsm.getColumnCount();i++){
final int j=i;
TableColumn<ObservableList<String>,String> col=new TableColumn(rsm.getColumnName(i+1));
col.setCellValueFactory(param->new SimpleStringProperty(param.getValue().get(j)));
cols.add(col);
}
ObservableList<ObservableList<String>> arows=FXCollections.observableArrayList();
while(rs.next()){
ObservableList<String> row=FXCollections.observableArrayList();
for(int i=1;i<=rsm.getColumnCount();i++){
row.add(rs.getString(i));
}
arows.add(row);
}
setTable(cols,arows);
}catch(Exception ex){
System.out.println(ex);
}
}
private static final ObservableList<TableColumn<ObservableList<String>,String>> columns=FXCollections.observableArrayList();
private static final javafx.beans.property.ListProperty<ObservableList<String>> rows=new javafx.beans.property.SimpleListProperty();
private javafx.beans.property.ListProperty getTabRow(){
Tab.getColumns().clear();
Tab.getColumns().addAll(columns);
return rows;
}
private static void setTable(ObservableList<TableColumn<ObservableList<String>,String>>c,ObservableList<ObservableList<String>> r){
columns.setAll(c);
rows.set(r);
}
#FXML
private void loadRem(ActionEvent event) {
try{
for(int i=remPane.getChildren().size()-1;i>=0;i--){
remPane.getChildren().remove(i);
}
javafx.fxml.FXMLLoader fxmlLoader=new javafx.fxml.FXMLLoader();
fxmlLoader.setLocation(getClass().getResource("rem.fxml"));
javafx.scene.layout.Pane panel=fxmlLoader.load();
remPane.getChildren().add(panel);
makeTable(App.getRS("tab1"));
}catch(Exception ex){
System.out.println(ex);
}
}
}
REM.FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nic.testfx.Rem">
<children>
<Button fx:id="btn" mnemonicParsing="false" onAction="#loadTab" text="Button" />
</children>
</VBox>
REM.JAVA
public class Rem implements Initializable {
#FXML
private Button btn;
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
#FXML
private void loadTab(ActionEvent event) {
try{
var fxml=new javafx.fxml.FXMLLoader(App.class.getResource("main.fxml"));
fxml.load();
Main controller=fxml.getController();
var rs=App.getRS("tab2");
controller.makeTable(rs);
}catch(Exception ex){
System.out.println(ex);
}
}
}
OK I got it.
Created a java class which encapsulate method makeTable:
MODEL.JAVA
public class Model{
private final TableView table;
public Model(TableView t){
this.table=t;
}
public void makeTable(ResultSet rs){
ObservableList<TableColumn<ObservableList<String>,String>>tabcols=FXCollections.observableArrayList();
ObservableList<ObservableList<String>>tabrows=FXCollections.observableArrayList();
//Create TableColumns and add to tabcols;
while(rs.next()){
//Create ObservableList<String>, get data from rs, and add to tabrows
}
this.table.getColumns().setAll(tabcols);
this.table.getItems().setAll(tabrows);
}
}
Next, in Controller file Main.java created a static instance of Model:
MAIN.JAVA
public class Main implements Initializable{
#FXML
private TableView<ObservableList<String>> Tab;
static Model model;
#Override
public void initialize (URL url, ResourceBundle rb){
model=new Model(Tab);
}
}
Now I can use model from Rem controller:
REM.JAVA
public class Rem implements Initializable{
#FXML
private Button btn;
#Override
public void initialize (URL url, ResourceBundle rb){
//TODO
}
#FXML
private void loadTab(ActionEvent event){
var rs=App.getRS("tab2");
Main.model.makeTable(rs);
}

How to bind nested Task progress property to TableView in JavaFX?

Enironment:
OpenJDK12, JavaFX 11
Context: I'm trying to show the Task progress to a TableView, for that, when my code was less complex, my Task object included the bean properties, and the TableView datamodel was my Task object.
public class MyTask extends Task<Void>{
private String name;
//other properties
public Void call() {
//"progress" property is inherited from Task.
//do something and updateProgress()
}
}
public class MyController {
...
#FXML
private TableView<MyTask> dataTable;
#FXML
private TableColumn<MyTask,Double> progressCol;
...
progressCol.setCellValueFactory(new PropertyValueFactory<MyTask, Double>("progress"));
progressCol.setCellFactory(ProgressCell.<Double>forTableColumn());
...
}
That worked fine. But I wanted to separate the Task from the bean properties, so I decided to make a kind of wrapper, but I'm unable to retrieve the progress property anymore.
EDIT
Sample Code:
MyApp
public class MyApp extends Application {
#Override
public void start(Stage stage) throws IOException {
stage.setMinWidth(800);
stage.setMinHeight(500);
FXMLLoader sceneLoader = new FXMLLoader(MyApp.class.getResource("MyScene.fxml"));
Parent parent = sceneLoader.load();
Scene scene = new Scene(parent);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
MyController
public class MyController implements Initializable{
#FXML
private TableView<MyWrapper> dataTable;
#FXML
private TableColumn<MyWrapper, String> nameColumn;
#FXML
private TableColumn<MyWrapper, Double> progressColumn;
public MyController() {
}
#Override
public void initialize(URL location, ResourceBundle resources) {
nameColumn.setCellValueFactory((TableColumn.CellDataFeatures<MyWrapper, String> download) -> download.getValue()
.getMyBean().nameProperty());
//This line only works when MyWrapper has progressPropery() method
//progressColumn.setCellValueFactory(new PropertyValueFactory<>("progress"));
progressColumn.setCellFactory(ProgressCell.<Double>forTableColumn());
MyWrapper w1 = new MyWrapper("qqqqqqq");
MyWrapper w2 = new MyWrapper("wwwwww");
MyWrapper w3 = new MyWrapper("eeeeeee");
ObservableList<MyWrapper> obsList = FXCollections.observableArrayList();
obsList.addAll(w1,w2,w3);
dataTable.setItems(obsList);
Thread t1 = new Thread(w1.getMyTask());
t1.start();
}
MyWrapper
public class MyWrapper {
private SimpleObjectProperty<MyBean> myBean;
private SimpleObjectProperty<MyTask> myTask;
public MyWrapper(String name) {
myBean = new SimpleObjectProperty<MyBean>();
myBean.setValue(new MyBean());
myBean.getValue().setName(name);
myTask = new SimpleObjectProperty<MyTask>();
myTask.setValue(new MyTask());
}
public MyBean getMyBean() {
return myBean.getValue();
}
public MyTask getMyTask() {
return myTask.getValue();
}
}
MyBean
public class MyBean {
private SimpleStringProperty name;
public MyBean() {
name = new SimpleStringProperty("--");
}
public SimpleStringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.setValue(name);
}
}
MyTask
public class MyTask extends Task<Void>{
#Override
protected Void call() throws Exception {
// Set the total number of steps in our process
double steps = 1000;
// Simulate a long running task
for (int i = 0; i < steps; i++) {
Thread.sleep(10); // Pause briefly
// Update our progress and message properties
updateProgress(i, steps);
updateMessage(String.valueOf(i));
} return null;
}
}
ProgressCell
public class ProgressCell extends TableCell<MyWrapper, Double> {
private ProgressBar bar;
private ObservableValue<Double> observable;
private StringProperty colorProperty = new SimpleStringProperty();
public ProgressCell() {
bar = new ProgressBar();
bar.setMaxWidth(Double.MAX_VALUE);
bar.setProgress(0f);
bar.styleProperty().bind(colorProperty);
}
public static <S> Callback<TableColumn<MyWrapper, Double>, TableCell<MyWrapper, Double>> forTableColumn() {
return param -> new ProgressCell();
}
#Override
protected void updateItem(Double item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
} else {
final TableColumn<MyWrapper, Double> column = getTableColumn();
observable = column == null ? null : column.getCellObservableValue(getIndex());
if (observable != null) {
bar.progressProperty().bind(observable);
} else if (item != null) {
bar.setProgress(item);
}
setGraphic(bar);
}
}
}
MyScene.fxml
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.effect.Blend?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.StackPane?>
<AnchorPane xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="main.java.MyController">
<StackPane BorderPane.alignment="CENTER">
<children>
<TableView id="dataTable" fx:id="dataTable" prefHeight="193.0" prefWidth="678.0" snapToPixel="false">
<columns>
<TableColumn fx:id="nameColumn" editable="false" prefWidth="88.0" text="Name" />
<TableColumn fx:id="progressColumn" editable="false" prefWidth="75.0" text="Progress" />
</columns>
<effect>
<Blend />
</effect>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
</children>
</StackPane>
</AnchorPane>
I don't know how to get the progress bar working, without adding the progressProperty() method in MyWrapper. I was expecting to access the progress property like the name property. Is there some way ? How do you think it would be better?
Any help appreciated.
There is no support for nested properties (as you noticed and I confirmed in a comment that mysteriously disappeared .. ) - providing the property in a custom cellValueFactory that walks down the tree is the way to go: just do the same for the progress of the task as you do for the name of the bean.
A working code snippet:
// column setup
nameColumn.setCellValueFactory(cc -> cc.getValue().getMyBean().nameProperty());
progressColumn.setCellValueFactory(cc -> cc.getValue().getMyTask().progressProperty().asObject());
progressColumn.setCellFactory(ProgressBarTableCell.forTableColumn());
new Thread(w1.getMyTask()).start();
Note the conversion of DoubleProperty to ObjectProperty<Double> (as Slaw noted in a comment that disappeared as well ;)
Whether or not such deep diving is a good idea depends on your context: it's okay as long as the data is read-only and doesn't change over its lifetime. Otherwise, you would need to take precautions to guard against such change. Which will require additonal logic in the wrapper anyway, so exposing the properties of interest in that layer probably would be the cleaner approach.
The first error is thrown because your MyObject class doesn't have a progressProperty function.
If you add this function to your wrapper class it will work.
public ReadOnlyDoubleProperty progressProperty() {
return task.progressProperty();
}
.
progressCol.setCellValueFactory(new PropertyValueFactory<>("progress"));

Concatenate Javafx fx:Id

I'm kinda new to JavaFX and currently trying to do a Calendar application for a school project. I was wondering if there was a way to concatenate a fx:id such a
#FXML
private Label Box01;
(In function)
String ExampleNum = "01";
(Box+ExampleNum).setText("Test");
In addition to the methods mentioned by #jewelsea here are 2 more ways to do this:
Create & inject a Map containing the boxes as values from the fxml:
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxml.Controller">
<children>
<Label text="foo" fx:id="a"/>
<Label text="bar" fx:id="b"/>
<Spinner fx:id="number">
<valueFactory>
<SpinnerValueFactory.IntegerSpinnerValueFactory min="1" max="2"/>
</valueFactory>
</Spinner>
<Button text="modify" onAction="#modify"/>
<fx:define>
<HashMap fx:id="boxes">
<box1>
<fx:reference source="a"/>
</box1>
<box2>
<fx:reference source="b"/>
</box2>
</HashMap>
</fx:define>
</children>
</VBox>
Controller
public class Controller {
private Map<String, Label> boxes;
#FXML
private Spinner<Integer> number;
#FXML
private Label box1;
#FXML
private Label box2;
#FXML
private void modify(ActionEvent event) {
boxes.get("box"+number.getValue()).setText("42");
}
}
Pass the namespace of the FXMLLoader, which is a Map<String, Object> mapping fx:ids to the associated Objects, to the controller:
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxml.Controller">
<children>
<Label text="foo" fx:id="box1"/>
<Label text="bar" fx:id="box2"/>
<Spinner fx:id="number">
<valueFactory>
<SpinnerValueFactory.IntegerSpinnerValueFactory min="1" max="2"/>
</valueFactory>
</Spinner>
<Button text="modify" onAction="#modify"/>
</children>
</VBox>
Controller
public class Controller implements NamespaceReceiver {
private Map<String, Object> namespace;
#FXML
private Spinner<Integer> number;
#FXML
private Label box1;
#FXML
private Label box2;
#FXML
private void modify(ActionEvent event) {
((Label)namespace.get("box" + number.getValue())).setText("42");
}
#Override
public void setNamespace(Map<String, Object> namespace) {
this.namespace = namespace;
}
}
public interface NamespaceReceiver {
public void setNamespace(Map<String, Object> namespace);
}
Code for loading the fxml:
public static <T> T load(URL url) throws IOException {
FXMLLoader loader = new FXMLLoader(url);
T result = loader.load();
Object controller = loader.getController();
if (controller instanceof NamespaceReceiver) {
((NamespaceReceiver) controller).setNamespace(loader.getNamespace());
}
return result;
}
Various possible solutions:
You could use reflection, but that would be ugly and I wouldn't advise it.
Normally, if you have a lot of things, you put them in a collection like a list or array. The label will be a child of some layout pane, so you can get the children of the pane and lookup an item by index with something like:
((Label) parent.getChildren().get(0)).setText("Text");
If the label has been assigned a css id then you can use that to lookup the label.
For example, in your FXML define:
<Label text="hello" fx:id="Box01" id="Box01"/>
Then you can lookup the label using:
String boxNum = "01";
Label box = (Label) parent.lookup("#Box" + boxNum);
Just refer to the item by it's reference:
#FXML private Label box01;
box01.setText("Test");
Aside: Please use camel case as per standard Java conventions.

Add setOnAction method to custom Scene Builder Control

So I followed this blog (part 2 in particular) to create a custom control and import it into Scene Builder.
This is what it looks like:
Here is the fxml File:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="30.0" prefWidth="150.0" type="AnchorPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<HBox alignment="CENTER" layoutX="-65.0" layoutY="-56.0" spacing="5.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Button fx:id="previousButton" minWidth="25.0" mnemonicParsing="false" prefWidth="25.0" text="<" />
<Label fx:id="label" alignment="CENTER" contentDisplay="CENTER" prefWidth="500.0" text="Label" />
<Button fx:id="nextButton" minWidth="25.0" mnemonicParsing="false" prefWidth="25.0" text=">" />
</children>
</HBox>
</children>
</fx:root>
Here is the class File:
package swipeselector;
import java.io.IOException;
import java.util.ArrayList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
/**
*
* #author patrickb
*/
public class SwipeSelector extends AnchorPane {
#FXML
private Button previousButton;
#FXML
private Label label;
#FXML
private Button nextButton;
ArrayList<String> items;
int selectedIndex;
private boolean repetitive;
public SwipeSelector() {
FXMLLoader fxmlLoader = new FXMLLoader(
getClass().getResource("swipeSelectorFXML.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
items = new ArrayList<>();
selectedIndex = 0;
previousButton.setOnAction((ActionEvent event) -> {
setPrevious();
});
nextButton.setOnAction((ActionEvent event) -> {
setNext();
});
}
public void setItems(ArrayList<String> items) {
this.items = items;
selectFirst();
}
public void setPrevious() {
updateItem(selectedIndex - 1);
}
public void setNext() {
updateItem(selectedIndex + 1);
}
public void selectFirst() {
updateItem(0);
}
private void selectLast() {
updateItem(items.size() - 1);
}
private void updateItem() {
if (items.isEmpty()) {
label.setText("");
} else {
if (selectedIndex < 0) {
if (repetitive) {
selectedIndex = items.size() - 1;
} else {
selectedIndex = 0;
}
}
if (selectedIndex >= items.size()) {
if (repetitive) {
selectedIndex = 0;
} else {
selectedIndex = items.size() - 1;
}
}
label.setText(items.get(selectedIndex));
}
}
private void updateItem(int selectedIndex) {
this.selectedIndex = selectedIndex;
updateItem();
}
public void setRepetitive(boolean cyclesThrough) {
this.repetitive = cyclesThrough;
}
}
Everything works fine, BUT: When I click the next or previous button, I want something to happen in my original project. I would usually do this by adding a setOnAction(new EventHandler ... method to the object. This method does not exist though, so I somehow need to add this to my custom control. How do I make my custom control invoke a ActionEvent every time one of the two buttons is clicked, and how do I create a setOnAction Method for my object that will work?
You'll need to create a custom event. Unfortunately, there is little information about this. You can have a look at the source code of ButtonBase to get a sample.
You'll need to define an onAction property:
ObjectProperty<EventHandler<ActionEvent>> onAction
I've implemented a utility class for this: SimpleEventHandlerProperty
You can find a complete sample using SimpleEventHandlerProperty here: LeftTestPane.java
You can get the library from Maven Central:
<dependency>
<groupId>org.drombler.commons</groupId>
<artifactId>drombler-commons-fx-core</artifactId>
<version>0.6</version>
</dependency>
I found a way to make it work. The following code is added to the Class File (it is basically copied from ButtonBase.
public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() { return onAction; }
public final void setOnAction(EventHandler<ActionEvent> value) { onActionProperty().set(value); }
public final EventHandler<ActionEvent> getOnAction() { return onActionProperty().get(); }
private ObjectProperty<EventHandler<ActionEvent>> onAction = new ObjectPropertyBase<EventHandler<ActionEvent>>() {
#Override protected void invalidated() {
setEventHandler(ActionEvent.ACTION, get());
}
#Override
public Object getBean() {
return SwipeSelector.this;
}
#Override
public String getName() {
return "onAction";
}
};
Also, whenever something happens where you want that Action Event to be fired, invoking the method fireEvent(new ActionEvent()); will do this for you. This method goes back to the Node Class, which is inherited to this class as it extends AnchorPane (which inherited the fireEvent method from Node).
So in my case, I replaced my two onAction methods for the two buttons I have by this:
previousButton.setOnAction((ActionEvent event) -> {
setPrevious();
fireEvent(event);
});
nextButton.setOnAction((ActionEvent event) -> {
setNext();
fireEvent(event);
});
Now, whenever one of the two buttons are pressed, an ActionEvent is fired (I just passed on the Buttons Event for this purpose) and I can catch that in my project by adding
swipeSelector.setOnAction((ActionEvent event) -> {
System.out.println("Event fired!!!");
//DO SOMETHING
});
as usual.

How can I create my own icon with propertise in JavaFX

Maybe somebody knows the answer and try help me.
I am creating own button.
<fx:root maxHeight="100.0" maxWidth="100.0" minHeight="50.0" minWidth="50.0" prefHeight="80.0" prefWidth="80.0" style="-fx-background-color: red;" type="StackPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" >
<children>
<ImageView fx:id="baseImage" fitHeight="66.0" fitWidth="72.0" pickOnBounds="true" preserveRatio="true" StackPane.alignment="TOP_CENTER" />
<Label fx:id="textBtn" alignment="BOTTOM_LEFT" prefHeight="17.0" prefWidth="75.0" textFill="WHITE" textOverrun="CLIP" StackPane.alignment="BOTTOM_LEFT" />
</children>
</fx:root>
So I need to change my button (Image and Label), when I am creating this in FXML file.
<MyButton layoutX="200.0" layoutY="162.0" />
e.g
<MyButton layoutX="200.0" layoutY="162.0" image="" text="" />
Can somebody help me ?
My Java Code
public class MyButton extends StackPane
{
#FXML
private ImageView baseImage;
#FXML
private Label textBtn;
public MyButton()
{
FXMLLoader fxmlLoader =new FXMLLoader(getClass().getResource("/pl/edu/wat/wcy/pz/icons/MyButtonView.fxml"));
fxmlLoader.setController(this);
fxmlLoader.setRoot(this);
init();
try {
fxmlLoader.load();
}
catch (IOException exception) {
throw new RuntimeException(exception);
}
}
public Label getTextBtn() {
return textBtn;
}
public void setTextBtn(Label textBtn) {
this.textBtn = textBtn;
}
public ImageView getBaseImage() {
return baseImage;
}
public void setBaseImage(Image location) {
this.baseImage.setImage(location);
}
public void setButton(Label textBtn, Image location){
this.baseImage.setImage(location);
this.textBtn = textBtn;
}
But I care about icon are changed in FXML file, not JavaCode
}
If you want to set properties in FXML:
<MyButton layoutX="200.0" layoutY="162.0" image="" text="" />
you must define those properties in the corresponding class. In particular, MyButton must define setImage(...) and setText(...) methods (it already has setLayoutX(...) and setLayoutY(...) which are inherited from StackPane). It's hard to know exactly what functionality you want here, but you probably want to set these up as JavaFX Properties. If the intention is to map these into the Label and ImageView defined in the FXML file, you can just expose the relevant properties from the controls. You might need to work a bit to map the string from the image property into the correct thing.
public class MyButton extends StackPane
{
#FXML
private ImageView baseImage;
#FXML
private Label textBtn;
public MyButton()
{
FXMLLoader fxmlLoader =new FXMLLoader(getClass().getResource("/pl/edu/wat/wcy/pz/icons/MyButtonView.fxml"));
fxmlLoader.setController(this);
fxmlLoader.setRoot(this);
// not sure what this is:
// init();
// note that if you define
// public void initialize() {...}
// it will be called
// automatically during the FXMLLoader.load() method
try {
fxmlLoader.load();
}
catch (IOException exception) {
throw new RuntimeException(exception);
}
}
public StringPropergty textProperty() {
return textBtn.textProperty();
}
public final String getText() {
return textProperty().get();
}
public final void setText(String text) {
textProperty().set(text);
}
// similarly expose a property for image, but you need to be able to coerce it from a String
}
(Incidentally, I assume this is just an example for the purposes of understanding how to do this. Everything you have in the example can be done using a regular button. Just wanted to make that clear for any others reading this post.)

Resources