javafx TreeTableView string array columns - java

All existing answers were using a class object to display multiple columns. Do I have to use a class? Can I just use a string array like C#'s ListViewItem? If I can, how?
For example, display "hello" in the first column and "world" in the second column.
public class HelloController {
#FXML
private TreeTableView mytree;
#FXML
private TreeTableColumn colFirst;
#FXML
private TreeTableColumn colSecond;
#FXML
void initialize()
{
TreeItem<String[]> item = new TreeItem<String[]>(new String[]{"hello", "world"});
colFirst.setCellValueFactory((CellDataFeatures<Object, String[]> p)
-> new ReadOnlyStringWrapper(p.getValue().toString()));
mytree.setRoot(item);
}
}
fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox alignment="CENTER" spacing="20.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.fx2.HelloController">
<TreeTableView fx:id="mytree" prefHeight="200.0" prefWidth="200.0">
<columns>
<TreeTableColumn id="colFirst" prefWidth="75.0" text="First" />
<TreeTableColumn id="colSecond" prefWidth="75.0" text="Second" />
</columns>
</TreeTableView>
</VBox>

Never use raw types: parameterize your types properly:
public class HelloController {
#FXML
private TreeTableView<String[]> mytree;
#FXML
private TreeTableColumn<String[], String> colFirst;
#FXML
private TreeTableColumn<String[], String> colSecond;
// ...
}
Then in the lambda expression, p is a TreeTableColumn.CellDataFeatures<String[], String>, so p.getValue() is a TreeItem<String[]> and p.getValue().getValue() is the String[] representing the row.
So you can do
#FXML
void initialize() {
TreeItem<String[]> item = new TreeItem<String[]>(new String[]{"hello", "world"});
colFirst.setCellValueFactory(p
-> new ReadOnlyStringWrapper(p.getValue().getValue()[0]));
mytree.setRoot(item);
}

Related

Issues with setting bounds of NumberAxis based on user input in FXML JavaFX application

SUMMARY: I am trying to design an application where the user inputs into TextFields, and upon clicking a button ONCE:
The values are retrieved
The scene switches to one containing a lineChart
The number axes' bounds are set corresponding to the values entered before.
However I'm finding it very hard to understand how to make the program flow correctly for this to work. My main issues are with switching the scene then accessing x_axis and y_axis, where for some reason "this.x_axis" is null.
Main Class:
public class Main extends Application {
#Override
public void start(Stage stage) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("menuScene.fxml"));
Scene scene = new Scene(root);
stage.setTitle("FT_Tool");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
Controller: (calculate is called after clicking button)
public class MenuController {
private Stage stage;
private Scene scene;
private Parent root;
#FXML
private TextField funcBox;
#FXML
private TextField sampBox;
#FXML
private TextField limitBox;
Expression func;
double sampFreq;
double limit;
#FXML
public void calculate(ActionEvent event) throws IOException{
func = new ExpressionBuilder(funcBox.getText()).variables("x").build();
sampFreq = Double.parseDouble(sampBox.getText());
limit = Double.parseDouble(limitBox.getText());
//=== SWITCHING SCENES ===//
root = FXMLLoader.load(getClass().getResource("graphScene.fxml"));
stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
scene = new Scene(root);
stage.setScene(scene);
stage.show();
graphing();
}
#FXML
private LineChart sampleGraph;
#FXML
private NumberAxis x_axis;
#FXML
private NumberAxis y_axis;
public void graphing(){
x_axis.setUpperBound(limit);
}
}
2nd Scene:
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
<children>
<LineChart fx:id="sampleGraph" layoutX="24.0" layoutY="16.0" prefHeight="368.0" prefWidth="552.0" title="Samples taken">
<xAxis>
<NumberAxis fx:id="x_axis" autoRanging="false" label="Time (s)" tickUnit="1.0"/>
</xAxis>
<yAxis>
<NumberAxis fx:id="y_axis" autoRanging="false" label="Amplitude (m)"/>
</yAxis>
</LineChart>
</children>
</AnchorPane>
When I run it, application runs fine and after clicking button the scene switches and linechart is shown but without the right bounds.
This line of code: x_axis.setUpperBound(limit) causes the following error:
Caused by: java.lang.NullPointerException: Cannot invoke "javafx.scene.chart.NumberAxis.setUpperBound(double)" because "this.x_axis" is null
Despite not having the fx:controller attribute defined in your FXML file, it looks like—based on your FXML controller class—you're trying to use the same FXML controller class with multiple FXML files. This is strongly discouraged. By default, a new controller instance is created every time the FXML file is loaded. Consequently, it quickly becomes difficult to reason about which fields should have been injected (i.e., non-null) at any given time. An FXML file should have a one-to-one mapping with an FXML controller class (or possibly no controller).
You have two views, which suggests two FXML files and thus two FXML controller classes. When you load the second view, you should communicate to its controller the needed information. For trivial applications you can do this directly, but for more complex applications it would be better to communicate via a model shared between the two controllers.
Example
Here's an example (based on your code but simplified for demonstration). It only asks for the upper bounds of the X and Y axes and then graphs random data within those bounds. It also makes use of TextFormatter to make it easier to grab the values out of the TextFields, and to ensure the user only enters positive integers.
FXML files are assumed to be located in the com.example package.
Main.java:
package com.example;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
var root = FXMLLoader.<Parent>load(Main.class.getResource("Form.fxml"));
primaryStage.setScene(new Scene(root, 1000, 650));
primaryStage.show();
}
}
Form.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.GridPane?>
<GridPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
fx:controller="com.example.FormController" alignment="CENTER" vgap="20" hgap="15">
<Label text="Upper X Bound:" GridPane.rowIndex="0" GridPane.columnIndex="0"/>
<TextField fx:id="xField" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.columnSpan="2"/>
<Label text="Upper Y Bound:" GridPane.rowIndex="1" GridPane.columnIndex="0"/>
<TextField fx:id="yField" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.columnSpan="2"/>
<Button text="Graph" onAction="#handleOnGraph" defaultButton="true" GridPane.rowIndex="2"
GridPane.columnIndex="2" GridPane.halignment="RIGHT"/>
</GridPane>
FormController.java:
package com.example;
import java.io.IOException;
import java.util.function.UnaryOperator;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.util.converter.IntegerStringConverter;
public class FormController {
#FXML private TextField xField;
#FXML private TextField yField;
private TextFormatter<Integer> xFieldFormatter;
private TextFormatter<Integer> yFieldFormatter;
#FXML
private void initialize() {
UnaryOperator<TextFormatter.Change> filter =
change -> {
if (change.getControlNewText().matches("\\d*")) {
return change;
}
return null;
};
xFieldFormatter = new TextFormatter<>(new IntegerStringConverter(), 50, filter);
xField.setTextFormatter(xFieldFormatter);
yFieldFormatter = new TextFormatter<>(new IntegerStringConverter(), 50, filter);
yField.setTextFormatter(yFieldFormatter);
}
#FXML
private void handleOnGraph(ActionEvent event) throws IOException {
event.consume();
int upperX = xFieldFormatter.getValue();
int upperY = yFieldFormatter.getValue();
var loader = new FXMLLoader(FormController.class.getResource("Chart.fxml"));
xField.getScene().setRoot(loader.load());
loader.<ChartController>getController().graph(upperX, upperY);
}
}
Chart.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>
<LineChart fx:id="chart" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
fx:controller="com.example.ChartController">
<xAxis>
<NumberAxis fx:id="xAxis" upperBound="50" autoRanging="false"/>
</xAxis>
<yAxis>
<NumberAxis fx:id="yAxis" upperBound="50" autoRanging="false"/>
</yAxis>
</LineChart>
ChartController.java:
package com.example;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
public class ChartController {
#FXML private LineChart<Number, Number> chart;
#FXML private NumberAxis xAxis;
#FXML private NumberAxis yAxis;
void graph(int upperX, int upperY) {
xAxis.setUpperBound(upperX);
yAxis.setUpperBound(upperY);
generateAndGraphRandomData();
}
private void generateAndGraphRandomData() {
var data = FXCollections.<XYChart.Data<Number, Number>>observableArrayList();
for (int x = 0; x <= xAxis.getUpperBound(); x++) {
int y = (int) (Math.random() * yAxis.getUpperBound());
data.add(new XYChart.Data<>(x, y));
}
var series = new XYChart.Series<>("Random Demo Data", data);
chart.getData().add(series);
}
}

Interact between several FXMLs

I'm trying to write my first application with javaFX and FXML files, but I got stuck with accesing variables of the FXMLs.
I am able to start the program with the first scene (first FXML). There is a button I'm able to interact with, and the first scene hides and the second scene appears. In that scene, the user has to insert a value in a text field. I'm able to save this into a variable by confirming this input over another button.
This button click hides the second scene and opens the first scene again. Now I want to set the value of a label in that first scene to the value of the variable I saved the user's input in. But this throws an error message.
It seems like this label value is not accessable, even if this label is part of the currently loaded FXML, but I am able to get the value of a label of the second FXML (user interaction field), which isn't visible anymore.
Can anyone help me, how to make this Label of the first FXML readable and changable?
My code is quite long, but I tired to give you the most important parts:
#FXML
private Label RollsMax;
#FXML
private TextField roll_input;
#FXML
private String amountRolls;
... // several more declarations
#FXML
void btn_confirmInput_onClick(ActionEvent event){ //btn_confirmInput is part of scene2.fxml
event.consume();
amountRolls=roll_input.getText();
System.out.println(amountRolls); // here I get the result of the user's input
try{
root=FXMLLoader.load(getClass().getResource("project/scene1.fxml"));
stage=(Stage)((Node)event.getSource()).getScene().getWindow();
scene=new Scene(root);
stage.setScene(scene);
stage.show();
} //end try
catch (IOException e){
e.printStackTrace();
} //end catch
RollsMax.setText(amountRolls); // THIS LINE IS NOT WORKING AS RollsMax SEEMS NOT AVAILABLE, EVEN IF IT IS PART OF SCENE1.fxml
} // end btn_confirmInput_onClick()
Any ideas? The error I get is 'java.lang.RuntimeException: java.lang.reflect.InvocationtargetException' as well as a'java.lang.NullPointerException'.
Thanks a lot!
Endcoder
To share data between the scenes, we'll introduce a Model class that holds the shared information:
public class Model {
private SimpleStringProperty valueProperty = new SimpleStringProperty("N/A");
public SimpleStringProperty getValueProperty() {
return valueProperty;
}
}
And an interface to be implemented by the controllers of the two scenes (actually the controllers of the two roots of the two scenes).
The interface adds the functionality of injection a Model into the controller:
public interface Controller {
void setModel(Model model);
}
sceneOne.fxml with a button to switch scene and a label to display user's input:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
prefHeight="300.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/16"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="SceneOneController">
<children>
<Label layoutX="159.0" layoutY="40.0" text="Scene 1">
<font>
<Font size="24.0" />
</font>
</Label>
<Button layoutX="264.0" layoutY="246.0" mnemonicParsing="false" onAction="#changeScene" prefHeight="26.0" prefWidth="102.0" text="Get Input" />
<Label fx:id="inputValue" layoutX="187.0" layoutY="142.0" text="Label" />
</children>
</AnchorPane>
And its controller which implements the Controller interface:
public class SceneOneController implements Controller {
#FXML Label inputValue;
private Model model;
public void changeScene(ActionEvent e) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("sceneTwo.fxml"));
Parent root = loader.load();
Controller controller = loader.getController(); //get a reference to sceneTwo controller
controller.setModel(model);
Stage stage = (Stage)((Node)e.getSource()).getScene().getWindow();
stage.setScene(new Scene(root));
} catch (Exception ex) {
ex.printStackTrace();
}
}
#Override
public void setModel(Model model) {
this.model = model;
if(model != null){
inputValue.textProperty().unbind();
inputValue.textProperty().bind(model.valueProperty);
}
}
}
sceneTwo.fxml with a button to switch scene and a TextField for the user's input:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane fx:id="main" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="300.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="SceneTwoController">
<children>
<Label layoutX="150.0" layoutY="41.0" text="Scene 2">
<font>
<Font size="24.0" />
</font>
</Label>
<Button layoutX="264.0" layoutY="246.0" mnemonicParsing="false" onAction="#changeScene" prefHeight="26.0" prefWidth="102.0" text="Update" />
<TextField fx:id="inputValue" layoutX="121.0" layoutY="138.0" prefHeight="25.0" prefWidth="162.0" />
</children>
</AnchorPane>
And its controller:
public class SceneTwoController implements Controller {
#FXML TextField inputValue;
private Model model;
public void changeScene(ActionEvent e) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("sceneOne.fxml"));
Parent root = loader.load();
Controller controller = loader.getController(); //get a reference to sceneOne controller
controller.setModel(model);
Stage stage = (Stage)((Node)e.getSource()).getScene().getWindow();
stage.setScene(new Scene(root));
} catch (Exception ex) {
ex.printStackTrace();
}
}
#Override
public void setModel(Model model) {
this.model = model;
if(model != null){
inputValue.textProperty().unbind();
model.valueProperty.bind(inputValue.textProperty());
}
}
}
And finally the application to test it all:
public class SwitchSceneMVC extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
FXMLLoader loader = new FXMLLoader(getClass().getResource("sceneOne.fxml"));
Parent root = loader.load();
Model model = new Model();
Controller controller = loader.getController();
controller.setModel(model);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(null);
}
}
Use a controller class for every FXML. Then you should be able to interact between them with public methods.
Especially when just hidinng the first scene you should then be able to just write from one scene to the other one.
Alternatively: while starting the second scene make a parameter where you bring the instance of the first scene. Than you should be able to use the needed methods on the first scenes instance.
Like
Code Scene one
#FXML
private void startSceneTwoButton(){
new SceneTwo(this);
}
Code Scene two
FirstScene scene;
public SecondScene(FirstScene scene){
this.scene = scene;
//some code
}
#FXML
private void button(){
scene.enterValue(value);
}

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.

Populating table data with javafx using fxml

It has been so difficult for me to try and get an answer for this. Oracle doesn't document on their website for some reason?! It has been asked on stackoverflow already (JavaFX: TableView(fxml) filling with data, and JavaFX FXML table; why won't my data display), but when i try to recreate with the answers given, I am still unable to get it to work. I would be extremely grateful if someone would be able to help! I'll give out all the code of have put together.
public class Heroes extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
try {
AnchorPane page = FXMLLoader.load(Heroes.class.getResource("FXMLDocument.fxml"));
Scene scene = new Scene(page);
primaryStage.setScene(scene);
primaryStage.setTitle("Sample Window");
primaryStage.setResizable(false);
primaryStage.show();
} catch (Exception e) {
}
}
}
public class FXMLDocumentController implements Initializable {
/**
* Initializes the controller class.
*/
#FXML
TableView<Table> tableID;
#FXML
TableColumn<Table, Integer> tID;
#FXML
TableColumn<Table, String> tName;
#FXML
TableColumn<Table, String> tDate;
#FXML
TableColumn<Table, String> tPrice;
int iNumber = 1;
ObservableList<Table> data =
FXCollections.observableArrayList(
new Table(iNumber++, "Superman", "12/02/2013", "20"),
new Table(iNumber++, "Batman", "2/02/2013", "40"),
new Table(iNumber++, "Aquaman", "1/02/2013", "100"),
new Table(iNumber++, "The Flash", "12/02/2013", "16"));
#Override
public void initialize(URL url, ResourceBundle rb) {
// System.out.println("called");
tID.setCellValueFactory(new PropertyValueFactory<Table, Integer>("rID"));
tName.setCellValueFactory(new PropertyValueFactory<Table, String>("rName"));
tDate.setCellValueFactory(new PropertyValueFactory<Table, String>("rDate"));
tPrice.setCellValueFactory(new PropertyValueFactory<Table, String>("rPrice"));
tableID.setItems(data);
}
}
public class Table {
SimpleIntegerProperty rID;
SimpleStringProperty rName;
SimpleStringProperty rDate;
SimpleStringProperty rPrice;
public Table(int rID, String rName, String rDate, String rPrice) {
this.rID = new SimpleIntegerProperty(rID);
this.rName = new SimpleStringProperty(rName);
this.rDate = new SimpleStringProperty(rDate);
this.rPrice = new SimpleStringProperty(rPrice);
System.out.println(rID);
System.out.println(rName);
System.out.println(rDate);
System.out.println(rPrice);
}
public Integer getrID() {
return rID.get();
}
public void setrID(Integer v) {
this.rID.set(v);
}
public String getrDate() {
return rDate.get();
}
public void setrDate(String d) {
this.rDate.set(d);
}
public String getrName() {
return rName.get();
}
public void setrName(String n) {
this.rDate.set(n);
}
public String getrPrice() {
return rPrice.get();
}
public void setrPrice(String p) {
this.rPrice.set(p);
}
}
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml" fx:controller="heroes.FXMLDocumentController">
<children>
<SplitPane dividerPositions="0.535175879396985" focusTraversable="true" orientation="VERTICAL" prefHeight="400.0" prefWidth="600.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<items>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
<children>
<TableView fx:id="tableID" prefHeight="210.0" prefWidth="598.0" tableMenuButtonVisible="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columns>
<TableColumn minWidth="20.0" prefWidth="40.0" text="ID" fx:id="tID" >
<cellValueFactory>
<PropertyValueFactory property="tID" />
</cellValueFactory>
</TableColumn>
<TableColumn prefWidth="100.0" text="Date" fx:id="tDate" >
<cellValueFactory>
<PropertyValueFactory property="tDate" />
</cellValueFactory>
</TableColumn>
<TableColumn prefWidth="200.0" text="Name" fx:id="tName" >
<cellValueFactory>
<PropertyValueFactory property="tName" />
</cellValueFactory>
</TableColumn>
<TableColumn prefWidth="75.0" text="Price" fx:id="tPrice" >
<cellValueFactory>
<PropertyValueFactory property="tPrice" />
</cellValueFactory>
</TableColumn>
</columns>
</TableView>
</children>
</AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0" />
</items>
</SplitPane>
</children>
</AnchorPane>
The PropertyValueFactory tries to find your data values in two ways:
1) Looking for a method e.g. nameProperty where name is what you specify in the constructor, in your example e.g. "rID".
Thus you need to a methods named like this (name of the field + "Property"):
public IntegerProperty rIDProperty() {
return rID;
}
And similar for your other properties.
2) If these aren't present, it looks for methods named e.g. getName where Name is what you specifiy in the constructor with the first letter capitalized (following the Java Beans convention).
In your example you don't have 1) but you seem to have 2) - but you haven't capitalized the first letter of the property name in the getters and setters!
So change these to e.g.
public Integer getRID() {
return rID.get();
}
And similar.
You need either 1) or 2) but it would be good practice to have both.
Also note that the problem is not related to FXML in any way.

Categories

Resources