JavaFx element not binding to controller variable on fx:id [duplicate] - java

This question already has answers here:
JavaFX FXML controller - constructor vs initialize method
(3 answers)
Closed 6 years ago.
This is likely pilot error, but the FXML attribute is not binding to the controller class on fx:id. I've whittled it down to a trivial example, but still "no joy". What am I overlooking?
FXML file...
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane fx:id="mainFrame" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.controller.BorderPaneCtrl">
<left>
<AnchorPane fx:id="anchorPaneLeft" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
</left>
</BorderPane>
The associated Java code is...
package sample.controller;
import javafx.fxml.FXML;
import javafx.scene.layout.AnchorPane;
public class BorderPaneCtrl {
#FXML private AnchorPane anchorPaneLeft;
public BorderPaneCtrl() {
/* so, #FXML-annotated variables are accessible, but not
* yet populated
*/
if (anchorPaneLeft == null) {
System.out.println("anchorPaneLeft is null");
}
}
/* this is what was missing...added for "completeness"
*/
#FXML
public void initialize() {
/* anchorPaneLeft has now been populated, so it's now
* usable
*/
if (anchorPaneLeft != null) {
// do cool stuff
}
}
Ego is not an issue here, I'm pretty sure I'm overlooking something simple.

FXML elements are not assigned yet in constuctor, but you can use Initializable interface where elements are already assigned.
public class Controller implements Initializable {
#FXML
AnchorPane anchorPaneLeft;
public Controller() {
System.out.println(anchorPaneLeft); //null
}
#Override
public void initialize(URL location, ResourceBundle resources) {
System.out.println(anchorPaneLeft); //AnchorPane
}
}
I assume that you know that you should create controllers with FXML by using for example: FXMLLoader.load(getClass().getResource("sample.fxml");

Related

JavaFx actions between controllers

How i can do button action for editing TableView. I need to put text from TextArea to table when i touch button. And if put System.out.println in inputToTable() it is work.
public class InputController {
public TextArea inputArea;
public Button inputButton;
private TableController tableController;
public void initialize() {
tableControllerInit();
}
public void inputToTable() {
if(inputArea.getText() != "") {
tableController.tableInfo.setItems(FXCollections.observableArrayList(new InputObject(inputArea.getText())));
}
}
private void tableControllerInit() {
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("table.fxml"));
fxmlLoader.load();
tableController = fxmlLoader.getController();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class TableController {
#FXML TableView<InputObject> tableInfo;
#FXML TableColumn<InputObject, String> col1;
public void initialize() {
col1.setCellValueFactory(new PropertyValueFactory<>("text"));
}
}
public class Controller implements Initializable {
#Override
public void initialize(URL location, ResourceBundle resources) {
}
}
public class InputObject {
String text;
public InputObject(String text) {
this.text = text;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
<BorderPane fx:controller="sample.Controller" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1">
<left>
<fx:include source="table.fxml"/>
</left>
<center>
<fx:include source="input.fxml"/>
</center>
</BorderPane>
<TableView fx:controller="sample.TableController" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:id="tableInfo" prefHeight="400.0" prefWidth="330.0">
<columns>
<TableColumn fx:id="col1" prefWidth="75.0" text="Output" />
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
<VBox fx:controller="sample.InputController" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" alignment="TOP_CENTER" prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
<children>
<TextArea fx:id="inputArea" prefHeight="188.0" prefWidth="270.0" />
<Button fx:id="inputButton" onAction="#inputToTable" mnemonicParsing="false" text="Input">
<VBox.margin>
<Insets bottom="30.0" left="30.0" right="30.0" top="30.0" />
</VBox.margin>
</Button>
</children>
</VBox>
You load table.fxml twice: once via the fx:include in the main FXML file, and once in InputController, via the FXMLLoader you create in the tableControllerInit() method. Consequently, two instances of TableController are created, one associated with the first UI you load from table.fxml, and one associated with the second UI you load from table.fxml.
The UI you load via the fx:include is displayed in the VBox defined in the main FXML file. The UI you load with the FXMLLoader is never displayed (in fact, you never even keep a reference to it, you just call loader.load() and discard the result). When you try to update the table's items (do you really intend to replace all the existing items, by the way?), you refer to the second controller instance, which is associated with the UI which is never displayed. Consequently, you are updating a table that is not displayed, and you never see any results.
What you really need to do is share the same data between the two controllers associated with the two fx:includes. You can do this simply by injecting those two controllers into the main controller, as described in the "Nested Controllers" section in the documentation.
First, give the fx:include elements fx:id attributes:
<BorderPane fx:controller="sample.Controller" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1">
<left>
<fx:include fx:id="table" source="table.fxml"/>
</left>
<center>
<fx:include fx:id="input" source="input.fxml"/>
</center>
</BorderPane>
Then you can inject the controllers into the main controller by creating fields with the word "Controller" appended to the fx:id. Create a single observable list, which will represent the list of items displayed in the table, and pass it to each controller:
public class Controller implements Initializable {
#FXML
private TableController tableController ;
#FXML
private InputController inputController ;
#Override
public void initialize(URL location, ResourceBundle resources) {
ObservableList<InputObject> items = FXCollections.observableArrayList();
tableController.setTableItems(items);
inputController.setTableItems(items);
}
}
Finally, just define the obvious methods in each of the other two controllers:
public class TableController {
#FXML
private TableView<InputObject> tableInfo;
#FXML
private TableColumn<InputObject, String> col1;
public void initialize() {
col1.setCellValueFactory(new PropertyValueFactory<>("text"));
}
public void setTableItems(ObservableList<InputObject> tableItems) {
tableInfo.setItems(tableItems);
}
}
Now the table is displaying the contents of the items list created in the main controller's initalize() method, and the InputController has a reference to the same list. So all you need to do is update that list in the InputController. I assume you just want to add items to the table (not replace them all):
public class InputController {
#FXML
private TextArea inputArea;
#FXML
private Button inputButton;
private ObservableList<InputObject> tableItems ;
public void setTableItems(ObservableList<InputObject> tableItems) {
this.tableItems = tableItems ;
}
public void inputToTable() {
if(! inputArea.getText().isEmpty()) {
tableItems.add(new InputObject(inputArea.getText()));
}
}
}
More generally, if you have more data to share among the different controllers, you would create one or more "model" classes and share a model instance with the controllers. Then you can observe the properties of the model and update them. See Applying MVC With JavaFx for a more comprehensive example.

JavaFX - RadioButtons in TableCell with Add Data Button

I'm currently working on an application where I use a TableView including RadioButtons in one TableCell. For that I created an own RadioButtonCell class. I also have a "Add new Row"-Button to let the user add some additional rows. After clicking the "Add" button a third time, I get more RadioButtonCells than rows. I don't find the mistake in my code.
Here a screenshot after clicking the Button a third time:
RadioButtonCell:
import java.util.EnumSet;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TableCell;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
public class RadioButtonCell<S,T extends Enum<T>> extends TableCell<S,T>{
private EnumSet<T> enumeration;
public RadioButtonCell(EnumSet<T> enumeration) {
this.enumeration = enumeration;
}
#Override
protected void updateItem(T item, boolean empty)
{
super.updateItem(item, empty);
if (!empty)
{
// GUI
HBox hb = new HBox(7);
hb.setAlignment(Pos.CENTER);
final ToggleGroup group = new ToggleGroup();
// create a radio button for each 'element' of the enumeration
for (Enum<T> enumElement : enumeration) {
RadioButton radioButton = new RadioButton(enumElement.toString());
radioButton.setUserData(enumElement);
radioButton.setToggleGroup(group);
hb.getChildren().add(radioButton);
if (enumElement.equals(item)) {
radioButton.setSelected(true);
}
}
// issue events on change of the selected radio button
group.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
#SuppressWarnings("unchecked")
#Override
public void changed(ObservableValue<? extends Toggle> observable,
Toggle oldValue, Toggle newValue) {
getTableView().edit(getIndex(), getTableColumn());
RadioButtonCell.this.commitEdit((T) newValue.getUserData());
}
});
setGraphic(hb);
}
}
}
Model:
public class Points {
private final SimpleObjectProperty<Participation> participation = new SimpleObjectProperty<Participation>(); // radio buttons
public static enum Participation {
NONE, HALO, BORDER;
public String toString() {
return super.toString().toLowerCase();
};
}
/**
* Constructor.
* #param <Participation>
*/
public Points(Participation p) {
this.participation.setValue(p);
public void setParticipation(Participation p){
participation.set(p);
}
public Participation getParticipation(){
return participation.get();
}
public SimpleObjectProperty<Participation> ParticipationProperty() {
return participation;
}
}
FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox prefHeight="900.0" prefWidth="1250.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="address.view.DataOverviewController">
<children>
<SplitPane fx:id="splitPaneVertical" prefHeight="776.0" prefWidth="1200.0" VBox.vgrow="ALWAYS">
<items>
<TabPane fx:id="tabPane" stylesheets="#Theme.css">
<tabs>
<Tab text="Points">
<content>
<SplitPane fx:id="splitPaneHorizontal" dividerPositions="0.949874686716792" orientation="VERTICAL" stylesheets="#Theme.css">
<items>
<TableView fx:id="pointsDataTable">
<columns>
<TableColumn fx:id="pointsBackgroundColumn" prefWidth="200.0" resizable="false" text="Background" />
</columns>
</TableView>
<AnchorPane SplitPane.resizableWithParent="false">
<children>
<HBox fx:id="pointsHBoxButton" alignment="CENTER" prefHeight="35.0" prefWidth="1016.0" spacing="20.0" stylesheets="#Theme.css" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Button fx:id="pointsAddButton" mnemonicParsing="false" onAction="#handleAddPoints" text="Add" />
</children>
</HBox>
</children>
</AnchorPane>
</items>
</SplitPane>
</content>
</Tab>
</tabs>
</TabPane>
</items>
</SplitPane>
Controller:
public class DataOverviewController {
#FXML
private TableView<Points> pointsDataTable;
#FXML
private TableColumn<Points, Participation> pointsBackgroundColumn;
#FXML
private Button pointsButtonAdd;
public DataOverviewController() {
}
#FXML
private void initialize() {
// select multipe rows, make rows editable
pointsDataTable.setEditable(true);
pointsDataTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
linesDataTable.setEditable(true);
pointsBackgroundColumn.setCellFactory((param) -> new RadioButtonCell<Points, Participation>(EnumSet.allOf(Participation.class)));
pointsBackgroundColumn.setCellValueFactory(new PropertyValueFactory<Points, Participation>("Participation"));
pointsBackgroundColumn.setOnEditCommit(
new EventHandler<CellEditEvent<Points, Participation>>() {
#Override
public void handle(CellEditEvent<Points, Participation> t) {
((Points) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setParticipation(t.getNewValue());
}
}
);
public void setMainConfig(MainConfig mainConfig) {
// Add observable list data to the table
pointsDataTable.setItems(mainConfig.getTableDataPoints());
}
#FXML
private void handleAddPoints() {
// create new record and add it to the tableview
Points dataPoints = new Points(0, "Points", "TabFileName.tab",Participation.NONE, false, false, 18, "new");
pointsDataTable.getItems().add(dataPoints);
}
}
}
I reduced my code to the important parts. Maybe someone can help?
Thanks.
Items can be added as well as removed from TableCells. This means you need to handle the case where the TableCell becomes empty by undoing any changes done to the cell when a item was added. Otherwise there may be TableCells that look as if they contain a item although they are actually empty.
A updateItem method should look like this:
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
// modifications to restore the empty state
} else {
// customized way of displaying the item
}
}
Furthermore I do not recommend recreating the Nodes for displaying the item in the cell every time the item is changed, since this means you'll have a lot of unnecessary node creations which is the thing TableView is trying to avoid by reusing cells. Store them in fields and keep them for later use instead.

Adding a list of specific node on a custom JavaFX FXML control

I'm trying to create a toolbar in JavaFX to add buttons using FXML like this:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.net.*?
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import com.supridatta.javafx.*?>
<BorderPane xmlns:fx="http://javafx.com/fxml/1" prefHeight="200" prefWidth="320" fx:controller="com.supridatta.javafx.MainController">
<stylesheets>
<URL value="#main.css"/>
</stylesheets>
<top>
<com.supridatta.javafx.ButtonBar fx:id="buttonBar">
<buttons>
<ButtonBarButton path="/com/supridatta/javafx/icons/plus.png"/>
<ButtonBarButton path="/com/supridatta/javafx/icons/minus.png"/>
<ButtonBarButton path="/com/supridatta/javafx/icons/last.png"/>
</buttons>
</com.supridatta.javafx.ButtonBar>
</top>
<bottom>
<Button text="Exibir todos" onAction="#showAllButtons"/>
</bottom>
</BorderPane>
Here is the corresponding java class:
package com.supridatta.javafx;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
public class ButtonBar extends StackPane {
private final HBox contentPane = new HBox();
private final ObservableList<ButtonBarButton> buttons = FXCollections.observableArrayList();
public ButtonBar() {
initButtonBar();
}
private void initButtonBar() {
getChildren().add(contentPane);
setAlignment(Pos.CENTER);
buttons.addListener(new ListChangeListener<Node>() {
#Override
public void onChanged(ListChangeListener.Change<? extends Node> c) {
while (c.next()) {
if (c.wasAdded()) {
for (Node node : c.getAddedSubList()) {
contentPane.getChildren().add(node);
}
}
if (c.wasRemoved()) {
for (Node node : c.getAddedSubList()) {
contentPane.getChildren().remove(node);
}
}
}
}
});
}
public void setButtons(ObservableList<ButtonBarButton> contents) {
this.buttons.setAll(contents);
}
public ObservableList<ButtonBarButton> getButtons() {
return buttons;
}
public void addButton(ButtonBarButton button) {
buttons.add(button);
}
public void removeButton(ButtonBarButton button) {
buttons.remove(button);
}
}
When I run the project I'm getting this exception:
java.lang.IllegalArgumentException: Unable to coerce ButtonBarButton#2fd0ac8f[styleClass=button ButtonBarButton]'' to
interface javafx.collections.ObservableList.
Thanks in advance.
Since you have a setButtons method defined, the FXMLLoader is going to attempt to pass in the value defined by the elements inside <buttons>...</buttons> to that method. (See Introduction to FXML, and note in particular "A read-only list property is a Bean property whose getter returns an instance of java.util.List and has no corresponding setter method." - my emphasis.)
If you have a getButtons() method returning a list, and no setButtons method, then the FXMLLoader will do what you want, and pass the value corresponding to each element to the add method invoked on the result of calling getButtons(). I.e. it does
getButtons().add(new ButtonBarButton(...));
getButtons().add(new ButtonBarButton(...));
...
You either want it to do this, or you want it to do
setButtons(FXCollections.observableArrayList(new ButtonBarButton(...), new ButtonBarButton(...), ...));
So you have two fixes: either remove the setButtons(...) method from ButtonBar, or arrange to pass in an ObservableList:
<buttons>
<FXCollections fx:factory="observableArrayList">
<ButtonBarButton path="/com/supridatta/javafx/icons/plus.png"/>
<ButtonBarButton path="/com/supridatta/javafx/icons/minus.png"/>
<ButtonBarButton path="/com/supridatta/javafx/icons/last.png"/>
</FXCollections>
</buttons>

Anchorpane not supporting my controller on javafx

My program is refusing to run because the "anchorpane" is refusing to support my controller on all my fxml files, hence my fxml page is not loading. Also, it is also giving me error message that my main class could not be found. I would welcome any help or suggestion on the stated.
My fxml file
<AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" pickOnBounds="false" prefHeight="614.0" prefWidth="1015.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="winnaccs.WinSecHomeController">
The Controller
public class WinSecHomeController implements Initializable, ControlledScreen {
ScreensController myController;
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
public void setScreenParent (ScreensController screenParent){
myController = screenParent;
}
#FXML
private void goToLogin(ActionEvent event) {
myController.setScreen(Winnaccs.screen2);
}
}
just try to change this one
fx:controller="winnaccs.WinSecHomeController"
with this other
fx:controller="winnaccs/WinSecHomeController"

How to close and iconify a JavaFx stage

In my Java Fx application I create a two stages. The first stage is the default in the main controller class, HomeController. The second, AddNewEmailController, is created by calling a method, showNewComposeNewEmail(), in the AddNewEmailController class.
The new stage gets created fine, but none of the methods I try to call, like closing the AddNewEmailController stage, will work on the AddNewEmailController stage.
How can I get the methods to work?
I'd also like to have the HomeController stage inaccessible once the AddNewEmailController stage gets open, like how pop-up windows do.
I can't even get the AddNewEmailController stage to iconify?
Thank you all in advance.
The HomeController class:
public class HomeController implements Initializable {
// Initializes the controller class.
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
#FXML
public void windowClose() {
Platform.exit();
}
#FXML
public void showNewComposeNewEmail() throws Exception {
new AddNewEmailController().newnewcomposeNewEmailStage();
}
}
The AddNewEmailController class:
public class AddNewEmailController implements Initializable {
public void setScreenParent(ScreensController screenParent) {
myController = screenParent;
}
// Initializes the controller class.
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
public Stage newComposeNewEmail;
public void newnewcomposeNewEmailStage() throws IOException {
newComposeNewEmail = new Stage();
newComposeNewEmail.initStyle(StageStyle.UNDECORATED);
newComposeNewEmail.initStyle(StageStyle.TRANSPARENT);
Parent newComposeNewEmailRoot = FXMLLoader.load(getClass().getResource("/wakiliproject/Forms/AddNew/NewEmail/NewEmail.fxml"));
StageDraggable.stageDraggable(newComposeNewEmailRoot, newComposeNewEmail);
Scene newComposeNewEmailScene = new Scene(newComposeNewEmailRoot, 590, 670);
newComposeNewEmail.setScene(newComposeNewEmailScene);
newComposeNewEmail.show();
}
#FXML
private void newComposeNewEmailClose() {
newComposeNewEmail.close();
}
#FXML
private void newComposeNewEmailIconify() {
newComposeNewEmail.setIconified(true);
}
}
UPDATE:
The NewEmail FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="wakiliproject.Forms.AddNew.NewEmail.TryEMailController">
<children>
<Label layoutX="190.0" layoutY="61.0" onMouseClicked="#newComposeNewEmailStageIconify" text="Minimise Window" />
<Label layoutX="300.0" layoutY="61.0" onMouseClicked="#newComposeNewEmailStageClose" text="Close Window" />
</children>
</AnchorPane>
In your fxml, your controller is
fx:controller="wakiliproject.Forms.AddNew.NewEmail.TryEMailController"
while in your code, the class name is
AddNewEmailController
Please make them same and recheck, your method will be called.
For making the new Stage as child of the Parent, please use
newStage.initModality(Modality.WINDOW_MODAL);
newStage.initOwner(PrimaryStage);

Categories

Resources