Java FX TableView not editable with FXML - java

I am currently developing a Java FX Application that includes a TableView. In this TableView I want to use an editable CheckBoxTableCell.
First I had all components added in the Java Code using MiG Layout. With that configuration everything was editable (size of columns, order of columns, checkbox). When I transferred all the code to FXML using Scene Builder everything worked fine except the TableView. I could not figure out how to set the CellFactory and the CellValueFactory in the FXML file (I've seen a couple of examples, but couldn't get it to work). So I decided to set those factories in the initialize() method:
voteCol.setCellValueFactory(new Callback<CellDataFeatures<DataItem, Boolean>, ObservableValue<Boolean>>() {
#Override
public ObservableValue<Boolean> call(
CellDataFeatures<DataItem, Boolean> arg0) {
return arg0.getValue().voteProperty();
}
});
voteCol.setCellFactory(new Callback<TableColumn<DataItem, Boolean>, TableCell<DataItem, Boolean>>() {
#Override
public TableCell<DataItem, Boolean> call(
TableColumn<DataItem, Boolean> arg0) {
return new CheckBoxTableCell<DataItem, Boolean>();
}
});
voteCol.setEditable(true);
dataTableView.setEditable(true);
Here is the FXML Code generated by Scene Builder
<TableView fx:id="dataTableView" editable="true"
mouseTransparent="true" pickOnBounds="false" prefHeight="-1.0"
prefWidth="-1.0" AnchorPane.bottomAnchor="41.0"
AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0"
AnchorPane.topAnchor="80.0">
<columns>
<TableColumn fx:id="voteCol" maxWidth="5000.0" minWidth="10.0"
prefWidth="32.0" text="Vote" visible="true" />
...
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
The databinding with the DataItem is working correctly.
If anyone could point me in the right direction I'd be very thankful.

Most probably your initialization is never called.
Make sure your Controller implements Initializable interface and initialize() method has correct signature:
public class MyController implements Initializable {
#Override
public void initialize(URL location, Resources resources) {
//....
}
}

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.

Java FX Scene Builder How to make event key pressed

I'm new in Java and Java FX and I'm trying to make a panel with buttons using scene builder. I want my application to respond only on arrow key pressed. I made the following method in my Controller class:
public void keyPressed(KeyEvent key) {
switch(key.getCode()) {
...some code here
}
}
After that I selected this method in scene builder, but when I run my application nothing happens when I press an arrow key.
Can somebody help me?
Without seeing the rest of your code and FXML it is difficult to tell, here is full example
Possible things you missed
Adding keyPress as an action in the FXML
Adding the #FXML annotation to the keyPressed() method
Code
public class Main extends Application {
private class Controller {
#FXML // <== perhaps you had this missing??
void keyPressed(KeyEvent event) {
switch (event.getCode()) {
case LEFT:
case KP_LEFT:
System.out.println("to the left");
break;
case RIGHT:
case KP_RIGHT:
System.out.println("to the right");
break;
default:
break;
}
}
}
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/foo.fxml"));
loader.setController(new Controller());
primaryStage.setScene(new Scene(loader.load()));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<GridPane onKeyPressed="#keyPressed" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Button mnemonicParsing="false" text="Button" />
</children>
</GridPane>
KeyCode also allows to compare with a specific key
#FXML
private void keyPressed(KeyEvent keyEvent)
if (keyEvent.getCode() == KeyCode.ENTER) {
// do some actions
}
}
You can get all the key code from here. Without using switch it's one of the good way.
KeyCode has a method isArrowKey(), so if you're calling your keyPressed method from your event handler, you could do:
public void keyPressed(KeyEvent key){
if(key.getCode().isArrowKey()){
...some code here
}
}
If you need to do different things based on which arrow key is pressed, make sure your switch cases are comparing to KeyCode.UP/DOWN/LEFT/RIGHT. If they are, it's likely that either you didn't set the event handler properly or your GUI is hung because of a threading problem. Post where your handling the event if you need more help.

javafx 8 order children in custom control

I'm writing a custom control which displays an error icon and a message in a tooltip if the validation in a form fails. My version without the custom control looks like this:
<HBox>
<TextField fx:id="name"></TextField>
<Label fx:id="error" focusTraversable="false" visible="false">
<graphic>
<ImageView fitHeight="24.0" fitWidth="24.0" pickOnBounds="true" preserveRatio="true"/>
</graphic>
<tooltip>
<Tooltip fx:id="errorTooltip"/>
</tooltip>
</Label>
</HBox>
The result is this:
My efforts to create a custom control lead to this:
<fx:root type="javafx.scene.layout.HBox" xmlns:fx="http://javafx.com/fxml">
<children/>
<Label fx:id="error" focusTraversable="false" visible="false">
<graphic>
<ImageView fitHeight="24.0" fitWidth="24.0" pickOnBounds="true" preserveRatio="true"/>
</graphic>
<tooltip>
<Tooltip fx:id="errorToolTip"/>
</tooltip>
</Label>
</fx:root>
This is the code behind the fxml:
package control;
[imports omitted for brevity]
#DefaultProperty(value = "children")
public final class ValidatedControl extends HBox implements Initializable {
#FXML
private Label error;
#FXML
private Tooltip errorToolTip;
private StringProperty errorToolTipProperty;
public ValidatedControl() {
final FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("ValidatedControl.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
public void setErrorToolTip(final String errorToolTip) {
this.getErrorToolTipProperty().setValue(errorToolTip);
}
public String getErrorToolTip() {
return this.getErrorToolTipProperty().getValueSafe();
}
#Override
public void initialize(final URL location, final ResourceBundle resources) {
this.errorToolTip.textProperty().bind(this.getErrorToolTipProperty());
this.error.visibleProperty().bind(this.getErrorToolTipProperty().isNotEmpty());
}
public StringProperty getErrorToolTipProperty() {
if (this.errorToolTipProperty == null) {
this.errorToolTipProperty = new SimpleStringProperty();
}
return this.errorToolTipProperty;
}
}
I can use the control in fxml but the child component I add is always the last child which means the error icon is displayed to its left.
My control is used like this:
<ValidatedControl>
<TextField>
</TextField>
</ValidatedControl>
How do I get it to display the icon on the right side?
Now I do understand your problem. This might not fix your problem when you add your ValidatedControl in FXML, but when you do it programmatically try this:
ValidatedControl vc = new ValidatedControl();
TextField textField = new TextField();
vc.getChildren().add(textField);
textField.toBack();
Another way would be to go ItachiUchiha's way and add a Pane in your FXML as first child. But instead of overwriting the getChildren() method, write a new Method addNode(Node n) and add the node to the Pane.
forget about my first answer ;)

Maintaining the underlying value of a TableColumn but displaying something else, for sorting purposes

Relevant extract from the FXML:
<TableView fx:id="events">
<columns>
<TableColumn fx:id="eventTime" text="Time"/>
<TableColumn fx:id="eventType" text="Event type"/>
<TableColumn fx:id="eventRule" text="Matcher"/>
<TableColumn fx:id="eventIndex" text="Index"/>
<TableColumn fx:id="eventDepth" text="Depth"/>
<TableColumn fx:id="eventPath" text="Matcher path"/>
</columns>
</TableView>
The eventTime column is defined as:
#FXML
TableColumn<TraceEvent, Long> eventTime;
I bind the property as such:
public DefaultTraceTabView(final TraceTabUi ui)
{
this.ui = ui;
bindColumn(ui.eventTime, "nanoseconds");
// other non relevant code
}
//
private static <T> void bindColumn(final TableColumn<TraceEvent, T> column,
final String propertyName)
{
column.setCellValueFactory(new PropertyValueFactory<>(propertyName));
}
OK, this works; I can sort the column, but the display is... Well... Nanoseconds.
And what I'd like to display in the column, instead of for instance 98343420, is 98 ms, 343.420 μs. But keep the sort order defined for the property...
How do I achieve that?
Answer to self...
Found the solution on this link:
ui.eventTime.setCellFactory(param -> new TableCell<TraceEvent, Long>()
{
#Override
protected void updateItem(final Long item, final boolean empty)
{
super.updateItem(item, empty);
setText(empty ? null : nanosToText(item));
}
});
and nanosToText() is a method which does what its name says.

JavaFX Thread with progressindicator not spinning, work done in non FXThread

I would like to indicate a loading process with the JavaFX progressindicator.
The problem is that the indicator isn't rotating when the code is executed the first time.
On the second time it is rotating that means the bindings are working, both the disable and the visible-property.
I also change the state in the FX thread and do the work on a seperate thread so i can
see no error here.
Does anyone see the problem?
Controller:
//vars
#FXML private ProgressIndicator loadingIndicator;
private BooleanProperty isSaving = new SimpleBooleanProperty(false);
#Override
public void initialize(URL location, ResourceBundle resources) {
parentToDisable.disableProperty().bind(isSaving);
loadingIndicator.visibleProperty().bind(isSaving);
}
#FXML
void onSave(ActionEvent event) {
isSaving.set(true); //<<<<<<<<<problem
// separate non-FX thread
new Thread() {
// runnable for that thread
public void run() {
//++++++++++//long running task......+++++++++++++++
// update ProgressIndicator on FX thread
Platform.runLater(new Runnable() {
public void run() {
isSaving.set(false); //<<<<<<<<<problem
}
});
}
}.start();
}
Fxml:
<ScrollPane fitToHeight="true" fitToWidth="true" prefHeight="600.0"
prefWidth="500.0" xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com
/fxml/1">
<content>
<StackPane>
<children>
<VBox fx:id="parentToDisable">
<!-- shortened -->
<Button fx:id="btnSave" mnemonicParsing="false" onAction="#onSave"
text="Speichern" />
<!-- shortened -->
</VBox>
<ProgressIndicator fx:id="loadingIndicator"
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
minWidth="-Infinity" prefHeight="60.0" prefWidth="60.0"
visible="false" />
</children>
</StackPane>
</content>
</ScrollPane>
That looks like a bug: RT-33261. It seems to be fixed in the latest pre-release of JDK 8u20.
My guess is it's to do with the progress indicator not being visible when the scene graph is first constructed. As a workaround, remove the visible="false" attribute from the ProgressIndicator in the fxml file, and wrap the binding in a Platform.runLater(...) call:
public void initialize(...) {
Platform.runLater(() -> loadingIndicator.visibleProperty().bind(isSaving));
}

Categories

Resources