I have created a custom Master-Detail pane for my project, where i use a split pane, in each i have two Anchor Panes. In one there is a TableView filled with Users (ObservableList). On each row (User) i have implemented a ChangeListener
table.getSelectionModel().selectedItemProperty().addListener(listElementChangeListener());
when the row is selected, i pass the UserObject for my DetailPane, and visualize User data in TextFields as detail. I have implemented controls, to understand if the User is under modification in Detail, and if so i would like to prevent a row change in my TableView. I tried to remove the ChangeListener from the TableView when i modify the User, but it dosent work well. I'm thinking of a solution like setting the focus and holding it on the row until i cancel or save the User modified.
Is there any nice solutions?
Thanks for your help.
I would probably approach this a little differently. I would bind the controls in the "detail view" bidirectionally to the properties in the User object. That way they will be updated in the object (and the table) as the user edits them. If you like, you can also provide a "cancel" button to revert to the previous values.
Here's a complete solution that uses this approach:
User.java:
package usermasterdetail;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class User {
private final StringProperty firstName = new SimpleStringProperty();
private final StringProperty lastName = new SimpleStringProperty();
private final BooleanProperty admin = new SimpleBooleanProperty();
public User(String firstName, String lastName, boolean admin) {
setFirstName(firstName);
setLastName(lastName);
setAdmin(admin);
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final String lastName) {
this.lastNameProperty().set(lastName);
}
public final BooleanProperty adminProperty() {
return this.admin;
}
public final boolean isAdmin() {
return this.adminProperty().get();
}
public final void setAdmin(final boolean admin) {
this.adminProperty().set(admin);
}
}
DataModel.java:
package usermasterdetail;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class DataModel {
private final ObservableList<User> userList = FXCollections.observableArrayList(
new User("Jacob", "Smith", false),
new User("Isabella", "Johnson", true),
new User("Ethan", "Williams", false),
new User("Emma", "Jones", true),
new User("Michael", "Brown", true)
);
private final ObjectProperty<User> currentUser = new SimpleObjectProperty<>();
public final ObjectProperty<User> currentUserProperty() {
return this.currentUser;
}
public final User getCurrentUser() {
return this.currentUserProperty().get();
}
public final void setCurrentUser(final User currentUser) {
this.currentUserProperty().set(currentUser);
}
public ObservableList<User> getUserList() {
return userList;
}
}
TableController.java:
package usermasterdetail;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
public class TableController {
#FXML
private TableView<User> table ;
#FXML
private TableColumn<User, String> firstNameColumn ;
#FXML
private TableColumn<User, String> lastNameColumn ;
#FXML
private TableColumn<User, Boolean> adminColumn ;
private DataModel model ;
public void initialize() {
firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
lastNameColumn.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty());
adminColumn.setCellValueFactory(cellData -> cellData.getValue().adminProperty());
adminColumn.setCellFactory(CheckBoxTableCell.forTableColumn(adminColumn));
}
public void setDataModel(DataModel dataModel) {
if (model != null) {
model.currentUserProperty().unbind();
}
this.model = dataModel ;
dataModel.currentUserProperty().bind(table.getSelectionModel().selectedItemProperty());
table.setItems(model.getUserList());
}
}
UserEditorController.java:
package usermasterdetail;
import javafx.beans.value.ChangeListener;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TextField;
public class UserEditorController {
#FXML
private TextField firstNameField ;
#FXML
private TextField lastNameField ;
#FXML
private CheckBox adminCheckBox ;
private String cachedFirstName ;
private String cachedLastName ;
private boolean cachedAdmin ;
private ChangeListener<User> userListener = (obs, oldUser, newUser) -> {
if (oldUser != null) {
firstNameField.textProperty().unbindBidirectional(oldUser.firstNameProperty());
lastNameField.textProperty().unbindBidirectional(oldUser.lastNameProperty());
adminCheckBox.selectedProperty().unbindBidirectional(oldUser.adminProperty());
}
if (newUser == null) {
firstNameField.clear();
lastNameField.clear();
adminCheckBox.setSelected(false);
} else {
firstNameField.textProperty().bindBidirectional(newUser.firstNameProperty());
lastNameField.textProperty().bindBidirectional(newUser.lastNameProperty());
adminCheckBox.selectedProperty().bindBidirectional(newUser.adminProperty());
cachedFirstName = newUser.getFirstName();
cachedLastName = newUser.getLastName();
cachedAdmin = newUser.isAdmin();
}
};
private DataModel model ;
public void setDataModel(DataModel dataModel) {
if (this.model != null) {
this.model.currentUserProperty().removeListener(userListener);
}
this.model = dataModel ;
this.model.currentUserProperty().addListener(userListener);
}
#FXML
private void cancel() {
firstNameField.setText(cachedFirstName);
lastNameField.setText(cachedLastName);
adminCheckBox.setSelected(cachedAdmin);
}
}
Table.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TableColumn?>
<StackPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="usermasterdetail.TableController">
<TableView fx:id="table">
<columns>
<TableColumn fx:id="firstNameColumn" text="First Name"/>
<TableColumn fx:id="lastNameColumn" text="Last Name"/>
<TableColumn fx:id="adminColumn" text="Administrator"/>
</columns>
</TableView>
</StackPane>
UserEditor.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>
<GridPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="usermasterdetail.UserEditorController"
hgap="5" vgap="5" alignment="CENTER">
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="NEVER"/>
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES"/>
</columnConstraints>
<padding>
<Insets top="5" left="5" bottom="5" right="5"/>
</padding>
<Label text="First Name:" GridPane.columnIndex="0" GridPane.rowIndex="0"/>
<Label text="Last Name:" GridPane.columnIndex="0" GridPane.rowIndex="1"/>
<Label text="Admin Priviliges:" GridPane.columnIndex="0" GridPane.rowIndex="2"/>
<TextField fx:id="firstNameField" GridPane.columnIndex="1" GridPane.rowIndex="0"/>
<TextField fx:id="lastNameField" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<CheckBox fx:id="adminCheckBox" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
<Button text="Cancel" onAction="#cancel" GridPane.columnIndex="0" GridPane.rowIndex="3" GridPane.columnSpan="2"
GridPane.halignment="CENTER"/>
</GridPane>
MainController.java:
package usermasterdetail;
import javafx.fxml.FXML;
public class MainController {
#FXML
private TableController tableController ;
#FXML
private UserEditorController editorController ;
private final DataModel model = new DataModel();
public void initialize() {
tableController.setDataModel(model);
editorController.setDataModel(model);
}
}
Main.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.SplitPane?>
<SplitPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="usermasterdetail.MainController">
<items>
<fx:include fx:id="table" source="Table.fxml"/>
<fx:include fx:id="editor" source="UserEditor.fxml"/>
</items>
</SplitPane>
And finally Main.java:
package usermasterdetail;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws IOException {
primaryStage.setScene(new Scene(FXMLLoader.load(getClass().getResource("Main.fxml")), 800, 600));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you prefer the user experience you described, you can (as #SSchuette describes in the comments), just bind the table's disable property to the modifying property. This will prevent the user from changing the selection while the data is being edited (i.e. is not consistent with the data in the table). For this you just need the modifying property in the model:
package usermasterdetail;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class DataModel {
private final ObservableList<User> userList = FXCollections.observableArrayList(
new User("Jacob", "Smith", false),
new User("Isabella", "Johnson", true),
new User("Ethan", "Williams", false),
new User("Emma", "Jones", true),
new User("Michael", "Brown", true)
);
private final ObjectProperty<User> currentUser = new SimpleObjectProperty<>();
private final BooleanProperty modifying = new SimpleBooleanProperty();
public final ObjectProperty<User> currentUserProperty() {
return this.currentUser;
}
public final usermasterdetail.User getCurrentUser() {
return this.currentUserProperty().get();
}
public final void setCurrentUser(final usermasterdetail.User currentUser) {
this.currentUserProperty().set(currentUser);
}
public ObservableList<User> getUserList() {
return userList;
}
public final BooleanProperty modifyingProperty() {
return this.modifying;
}
public final boolean isModifying() {
return this.modifyingProperty().get();
}
public final void setModifying(final boolean modifying) {
this.modifyingProperty().set(modifying);
}
}
then in the table controller you can bind the disable property to it:
package usermasterdetail;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
public class TableController {
#FXML
private TableView<User> table ;
#FXML
private TableColumn<User, String> firstNameColumn ;
#FXML
private TableColumn<User, String> lastNameColumn ;
#FXML
private TableColumn<User, Boolean> adminColumn ;
private DataModel model ;
public void initialize() {
firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
lastNameColumn.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty());
adminColumn.setCellValueFactory(cellData -> cellData.getValue().adminProperty());
adminColumn.setCellFactory(CheckBoxTableCell.forTableColumn(adminColumn));
}
public void setDataModel(DataModel dataModel) {
if (model != null) {
model.currentUserProperty().unbind();
}
this.model = dataModel ;
dataModel.currentUserProperty().bind(table.getSelectionModel().selectedItemProperty());
table.setItems(model.getUserList());
table.disableProperty().bind(model.modifyingProperty());
}
}
The only place there is a bit of work to do is to make sure the modifying property is set to true any time the data are not in sync (though it sounds like you have already done this):
package usermasterdetail;
import javafx.beans.value.ChangeListener;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TextField;
public class UserEditorController {
#FXML
private TextField firstNameField ;
#FXML
private TextField lastNameField ;
#FXML
private CheckBox adminCheckBox ;
private DataModel model ;
private ChangeListener<Object> modifyingListener = (obs, oldValue, newValue) -> {
if (model != null) {
if (model.getCurrentUser() == null) {
model.setModifying(false);
} else {
model.setModifying(! (model.getCurrentUser().getFirstName().equals(firstNameField.getText())
&& model.getCurrentUser().getLastName().equals(lastNameField.getText())
&& model.getCurrentUser().isAdmin() == adminCheckBox.isSelected()));
}
}
};
private ChangeListener<User> userListener = (obs, oldUser, newUser) -> {
if (oldUser != null) {
oldUser.firstNameProperty().removeListener(modifyingListener);
oldUser.lastNameProperty().removeListener(modifyingListener);
oldUser.adminProperty().removeListener(modifyingListener);
}
if (newUser == null) {
firstNameField.clear();
lastNameField.clear();
adminCheckBox.setSelected(false);
} else {
firstNameField.setText(newUser.getFirstName());
lastNameField.setText(newUser.getLastName());
adminCheckBox.setSelected(newUser.isAdmin());
newUser.firstNameProperty().addListener(modifyingListener);
newUser.lastNameProperty().addListener(modifyingListener);
newUser.adminProperty().addListener(modifyingListener);
}
};
public void setDataModel(DataModel dataModel) {
if (this.model != null) {
this.model.currentUserProperty().removeListener(userListener);
}
this.model = dataModel ;
this.model.currentUserProperty().addListener(userListener);
}
public void initialize() {
firstNameField.textProperty().addListener(modifyingListener);
lastNameField.textProperty().addListener(modifyingListener);
adminCheckBox.selectedProperty().addListener(modifyingListener);
}
#FXML
private void cancel() {
if (model != null) {
firstNameField.setText(model.getCurrentUser().getFirstName());
lastNameField.setText(model.getCurrentUser().getLastName());
adminCheckBox.setSelected(model.getCurrentUser().isAdmin());
}
}
#FXML
private void update() {
if (model != null && model.getCurrentUser() != null) {
model.getCurrentUser().setFirstName(firstNameField.getText());
model.getCurrentUser().setLastName(lastNameField.getText());
model.getCurrentUser().setAdmin(adminCheckBox.isSelected());
}
}
}
This solution requires an additional button to force the update in the data (and table):
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.HBox?>
<GridPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="usermasterdetail.UserEditorController"
hgap="5" vgap="5" alignment="CENTER">
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="NEVER"/>
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES"/>
</columnConstraints>
<padding>
<Insets top="5" left="5" bottom="5" right="5"/>
</padding>
<Label text="First Name:" GridPane.columnIndex="0" GridPane.rowIndex="0"/>
<Label text="Last Name:" GridPane.columnIndex="0" GridPane.rowIndex="1"/>
<Label text="Admin Priviliges:" GridPane.columnIndex="0" GridPane.rowIndex="2"/>
<TextField fx:id="firstNameField" GridPane.columnIndex="1" GridPane.rowIndex="0"/>
<TextField fx:id="lastNameField" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<CheckBox fx:id="adminCheckBox" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
<HBox spacing="5" alignment="CENTER" GridPane.columnIndex="0" GridPane.rowIndex="3" GridPane.columnSpan="2">
<Button text="Update" onAction="#update"/>
<Button text="Cancel" onAction="#cancel"/>
</HBox>
</GridPane>
Related
I am trying to add an action on the click event on a menu in the menubar using javafx.
Thing is, I saw a lot of posts about it but no answers worked for me.
I manage to do it using the "On Showing" on the menu ,which is fine, but this event is only trigger (as the others) if the menu has at least one menu item.
This is not something I want but I have no choice for now.
Here is the 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.layout.BorderPane?>
<BorderPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="200.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.HangmanGameFXViews.view.MenuesActionsControlleur">
<top>
<MenuBar fx:id="menusBar" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" onMouseReleased="#switchToAbout" prefWidth="400.0" BorderPane.alignment="CENTER">
<menus>
<Menu mnemonicParsing="false" text="Fichiers">
<items>
<MenuItem mnemonicParsing="false" text="Nouveau" />
<MenuItem mnemonicParsing="false" onAction="#switchToScore" text="Scores" />
<MenuItem mnemonicParsing="false" onAction="#switchToRules" text="Règles" />
<MenuItem mnemonicParsing="false" onAction="#exit" text="Quitter" />
</items>
</Menu>
<Menu fx:id="about" mnemonicParsing="false" onShowing="#switchToAbout" text="À propos">
<items>
<!-- THIS THE DUMMY MENU I USE TO BE ABLE TO TRIGGER THE EVENT ON THE PARENT -->
<MenuItem fx:id="dummyMenuItem" mnemonicParsing="false" />
</items></Menu>
</menus>
</MenuBar>
</top>
</BorderPane>
The code of the view controller :
package org.HangmanGameFXViews.view;
import org.HangmanGameFXViews.Main;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.stage.Stage;
public class MenuesActionsControlleur {
private Stage stageDialogue;
private Main main;
#FXML
private Menu about;
#FXML
private MenuBar menusBar;
#FXML
private MenuItem dummyMenuItem;
#FXML
private void initialize() {
about.addEventHandler(Event.ANY, new EventHandler<Event>() {
#Override
public void handle(Event event) {
System.out.println("Showing Menu 1");
System.out.println(event.getTarget().toString());
System.out.println(event.getEventType().toString());
}
});
}
#FXML
public void switchToRules() {
main.switchToRules();
}
#FXML
public void switchToAbout() {
dummyMenuItem.setDisable(true);
main.switchToAbout();
dummyMenuItem.setDisable(false);
}
#FXML
public void clickableMenu(ActionEvent e){
System.out.println("Menu clicked");
}
#FXML
public void switchToNew() {
main.switchToNew();
}
#FXML
public void switchToScore() {
main.switchToScore();
}
public Stage getStageDialogue() {
return stageDialogue;
}
public void setStageDialogue(Stage stageDialogue) {
this.stageDialogue = stageDialogue;
}
#FXML
public void exit() {
stageDialogue.close();
}
public void setMainClass(Main m) {
main = m;
stageDialogue = main.getStagePrincipal();
}
}
Thank you for any help.
As already noted in the comments: a Menu is not meant to act like a Button (or MenuItem) - its "action" is to open a ContextMenu showing its items. Implementing it to fire in that context is possible (beware: it might confuse users and it requires a bit of dirt in using implementation details and internal api).
That said: the basic idea for installing custom handlers is to do so on the styleableNode that represents the Menu. By default, access to that node is implemented only if the Menu is an item in a ContextMenu and not when represented by a MenuButton in a MenuBar (which I would consider a bug, but that's another story).
So we have to do it ourselves
extend Menu
add api to set a containing MenuBar
implement getStyleableNode to access the MenuButton
add some wiring to redirect a mouseReleased (or whatever) into firing the Menu's action
A custom menu might be someting like (obviously not production quality :):
public class MyMenu extends Menu {
private MenuBar parentMenuBar;
private Parent menuBarContainer;
private MenuButton menuButton;
private EventHandler<MouseEvent> redirector = this::redirect;
public MyMenu(String string) {
super(string);
}
public void setParentMenuBar(MenuBar menuBar) {
this.parentMenuBar = menuBar;
// tbd: cleanup if menuBar and/or its skin disposed/changed
if (menuBar != null) {
menuBar.skinProperty().addListener((src, ov, nv) -> {
if (nv instanceof MenuBarSkin && menuBar.getChildrenUnmodifiable().size() == 1) {
menuBarContainer = (Parent) menuBar.getChildrenUnmodifiable().get(0);
menuBarContainer.getChildrenUnmodifiable().addListener((ListChangeListener)c -> {
updateEventRedirector();
});
updateEventRedirector();
}
});
}
}
protected void redirect(MouseEvent e) {
// fire only if there are no items
if (getItems().size() == 0) fire();
}
/**
* Rewire eventHandler when our styleable node is changed
*/
private void updateEventRedirector() {
if (menuButton != null) {
menuButton.removeEventHandler(MouseEvent.MOUSE_RELEASED, redirector);
}
menuButton = getParentMenuButton();
if (menuButton != null) {
menuButton.addEventHandler(MouseEvent.MOUSE_RELEASED, redirector);
}
}
#Override
public Node getStyleableNode() {
if (parentMenuBar != null && parentMenuBar.getChildrenUnmodifiable().size() == 1) {
return menuButton;
}
return super.getStyleableNode();
}
private MenuButton getParentMenuButton() {
if (parentMenuBar == null || parentMenuBar.getChildrenUnmodifiable().size() != 1) return null;
// beware: implementation detail of menuBarSkin!
Parent parent = (Parent) parentMenuBar.getChildrenUnmodifiable().get(0);
for (Node child : parent.getChildrenUnmodifiable()) {
// beware: internal api!
if (child instanceof MenuBarButton) {
MenuBarButton menuButton = (MenuBarButton) child;
if (menuButton.menu == this) {
return menuButton;
}
}
}
return null;
}
}
To use:
bar = new MenuBar();
first = new MyMenu("dummy");
first.setParentMenuBar(bar);
first.setOnAction(e -> System.out.println("menu handler"));
bar.getMenus().addAll(first, new Menu("other"));
I am doing a javafx project which if i click the button, the scene is showing, but if the scene is left open for few seconds, the scene will auto hide.
The actual scene is if i left it open, it will auto hide at the below of the main scene just like the button is pressed.
But i tried alot ways, the scene will either not hiding, or moving upward which if i click the button, the scene is at upward of the scene moving but not on the main scene.
If the scene is not on main scene, nothing will happen.But only if the scene is on main scene, the scene will auto hide and move downward just like when I click the button, the scene appear on the main scene from below and if i click close, the scene will go down and hide until i click again. when i click the button after a few inactive(like move the cursor), the scene will function as usual.
The code which i follow : Call another FXML within a stage
Testinggg.java
package testinggg;
import java.io.IOException;
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.input.InputEvent;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Testinggg extends Application {
private TestController controller;
#Override
public void start(Stage stage) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Test.fxml"));
Parent root = loader.load();
controller = loader.getController();
stage.setScene(new Scene(root));
stage.setFullScreen(true);
stage.show();
Duration duration = Duration.seconds(5);
PauseTransition transition = new PauseTransition(duration);
transition.setOnFinished(evt -> inactive());
stage.addEventFilter(InputEvent.ANY, evt -> transition.playFromStart());
transition.play();
}
private void inactive(){
//hide the status until status is open
}
public static void main(String[] args) {
launch(args);
}
}
TestController.java
package testinggg;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.animation.TranslateTransition;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.VBox;
import javafx.util.Duration;
public class TestController implements Initializable {
#FXML private VBox statusContainer;
private TranslateTransition showStatus;
private TranslateTransition hideStatus;
boolean showsStatus = false;
public void toggleStatus() {
if( showsStatus ) {
showStatus.stop();
hideStatus.play();
}
else {
hideStatus.stop();
showStatus.play();
}
}
#Override
public void initialize(URL url, ResourceBundle rb) {
showStatus = new TranslateTransition(Duration.millis(250), statusContainer);
showStatus.setByY(-1080.0);
showStatus.setOnFinished(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent event) {
showsStatus = true;
}
});
hideStatus = new TranslateTransition(Duration.millis(250), statusContainer);
hideStatus.setByY(1080.0);
hideStatus.setOnFinished(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent event) {
showsStatus = false;
}
});
}
}
for Test.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.web.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.image.*?>
<?import java.lang.*?>
<?import java.net.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="testinggg.TestController">
<children>
<AnchorPane id="AnchorPane" maxHeight="-Infinity" prefHeight="1080.0" prefWidth="1920.0" StackPane.alignment="TOP_LEFT">
<children>
<Button mnemonicParsing="false" onAction="#toggleStatus" prefHeight="1080.0" prefWidth="1920.0" text="Button" />
</children>
</AnchorPane>
<VBox fx:id="statusContainer" maxHeight="1080.0" prefHeight="1080.0" translateY="1080.0" StackPane.alignment="BOTTOM_LEFT">
<children>
<AnchorPane prefHeight="668.0" prefWidth="1266.0">
<VBox.margin>
<Insets bottom="50.0" left="50.0" right="50.0" top="50.0" />
</VBox.margin>
<children>
<ImageView fitHeight="540.0" fitWidth="1820.0" layoutY="43.0" pickOnBounds="true">
<image>
<Image url="#../Rainbow%20Poro.png" />
</image>
</ImageView>
<ImageView fitHeight="44.0" fitWidth="153.0" layoutX="857.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="#../logo.png" />
</image>
</ImageView>
<ScrollPane layoutY="582.0" prefHeight="391.0" prefViewportHeight="208.0" prefViewportWidth="1266.0" prefWidth="1820.0">
<content>
<TextArea editable="false" layoutY="460.0" prefHeight="515.0" prefWidth="1804.0" text="Some paragraph here" />
</content>
</ScrollPane>
<Button layoutX="1775.0" mnemonicParsing="false" onAction="#toggleStatus" text="Close" />
</children>
</AnchorPane>
</children>
</VBox>
</children>
<stylesheets>
<URL value="#test1.css" />
</stylesheets>
</StackPane>
Here is an mcve version of the code you are following, to get you started :
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Test1 extends Application {
#Override
public void start(Stage primaryStage) {
try {
StackPane page = (StackPane) FXMLLoader.load(this.getClass().getResource("test1.fxml"));
Scene scene = new Scene(page);
primaryStage.setScene(scene);
primaryStage.show();
}
catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
test1.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<StackPane id="StackPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
minWidth="-Infinity" prefHeight="300.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/10.0.1"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="Test1Controller">
<children>
<AnchorPane prefHeight="200.0" prefWidth="200.0" style="-fx-background-color: blue;">
<children>
<Button mnemonicParsing="false" onAction="#toggleStatus" text="Status" AnchorPane.leftAnchor="50.0" AnchorPane.topAnchor="100.0" />
</children>
</AnchorPane>
<VBox fx:id="statusContainer" maxHeight="100.0" prefHeight="100.0" style="-fx-background-color: yellow;" translateY="100.0" StackPane.alignment="BOTTOM_LEFT" />
</children>
</StackPane>
and its controller:
import javafx.animation.TranslateTransition;
import javafx.fxml.FXML;
import javafx.scene.layout.VBox;
import javafx.util.Duration;
public class Test1Controller {
#FXML private VBox statusContainer;
private TranslateTransition showStatus;
private TranslateTransition hideStatus;
private boolean showsStatus = false;
#FXML void initialize() {
showStatus = new TranslateTransition(Duration.millis(250), statusContainer);
showStatus.setByY(-100.0);
showStatus.setOnFinished(event -> showsStatus = true);
hideStatus = new TranslateTransition(Duration.millis(250), statusContainer);
hideStatus.setByY(100.0);
hideStatus.setOnFinished(event -> showsStatus = false);
}
public void toggleStatus() {
if( showsStatus ) {
showStatus.stop();
hideStatus.play();
}
else {
hideStatus.stop();
showStatus.play();
}
}
}
Edit: you can add auto-hide of the sliding node to the controller:
public class Test1Controller {
#FXML private VBox statusContainer;
private TranslateTransition showStatus;
private TranslateTransition hideStatus;
private boolean showsStatus = false;
private static final int AUTO_HIDE_DEALY = 5;
#FXML void initialize() {
showStatus = new TranslateTransition(Duration.millis(250), statusContainer);
showStatus.setByY(-100.0);
showStatus.setOnFinished(event -> {
showsStatus = true;
autoHide();
});
hideStatus = new TranslateTransition(Duration.millis(250), statusContainer);
hideStatus.setByY(100.0);
hideStatus.setOnFinished(event -> showsStatus = false);
}
public void toggleStatus() {
if( showsStatus ) {
hide();
}
else {
show();
}
}
private void show(){
hideStatus.stop();
showStatus.play();
}
private void hide(){
showStatus.stop();
hideStatus.play();
}
private void autoHide() {
Duration duration = Duration.seconds(AUTO_HIDE_DEALY);
PauseTransition transition = new PauseTransition(duration);
transition.setOnFinished(evt ->{
if( showsStatus ) {
hide();
}
});
transition.play();
}
}
TableView multi selection returns one of the selected objects as null. This doesn't happen every time but happens most of the times when i try to select two rows in the table.similar to the issue defined in this question
Best way of reproducing the issue is , try selecting two sequential rows .
FXML :
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<AnchorPane xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.test.controller.TestController">
<children>
<TableView fx:id="personsTable" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="35.0">
<placeholder>
<Label text="" />
</placeholder>
<columns>
<TableColumn text="Name">
<cellValueFactory>
<PropertyValueFactory property="name" />
</cellValueFactory>
</TableColumn>
<TableColumn text="Address">
<cellValueFactory>
<PropertyValueFactory property="address" />
</cellValueFactory>
</TableColumn>
<TableColumn text="Course">
<cellValueFactory>
<PropertyValueFactory property="course" />
</cellValueFactory>
</TableColumn>
<TableColumn text="Country">
<cellValueFactory>
<PropertyValueFactory property="country" />
</cellValueFactory>
</TableColumn>
</columns>
</TableView>
</children>
</AnchorPane>
Controller :
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import com.test.model.Person;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableView;
public class TestController implements Initializable {
#FXML
TableView<Person> personsTable = new TableView<Person>();
#Override
public void initialize(URL location, ResourceBundle resources) {
// TODO Auto-generated method stub
MenuItem combine = new MenuItem("Select");
combine.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
List<Person> selectedPersons = new ArrayList<Person>(
personsTable.getSelectionModel().getSelectedItems());
for (Person person : selectedPersons) {
System.out.println(" the object is " + person);
}
}
});
ContextMenu contextMenu = new ContextMenu();
contextMenu.getItems().add(combine);
personsTable.setContextMenu(contextMenu);
personsTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
personsTable.setItems(getPendingOrders());
}
public ObservableList<Person> getPendingOrders() {
ObservableList<Person> persons = FXCollections.observableArrayList();
for (int i = 0; i < 100; i++) {
Person person = new Person();
person.setName("Name" + i);
person.setAddress("Address" + i);
person.setCountry("Country" + i);
person.setCourse("Course" + i);
persons.add(person);
}
return persons;
}
}
Model :
public class Person {
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getCourse() {
return course;
}
public void setCourse(String course) {
this.course = course;
}
private String name;
private String address;
private String country;
private String course;
}
1: add a default cell factory to your TableView
2: override either updateItem or updateIndex
hope it helpls
I am new to JavaFx and I am trying to create a tableview and fxml is created using scene builder. If I run the program, the table is not getting the values. I found that this question is somehow matching with my requirement (javaFX 2.2 - Not able to populate table from Controller), but my code is matching with that too. But still I am not able to get the values in the table.
I have referenced the following video : http://www.youtube.com/watch?v=HiZ-glk9_LE
My code is as follows,
MainView.java
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package controller;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class MainView extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
try {
AnchorPane page = FXMLLoader.load(MainView.class.getResource("MainView.fxml"));
Scene scene = new Scene(page);
primaryStage.setScene(scene);
primaryStage.setTitle("Sample Window");
primaryStage.setResizable(false);
primaryStage.show();
} catch (Exception e) {
}
}
}
MainView.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" fx:controller="controller.MainViewController">
<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" />
<TableColumn prefWidth="100.0" text="Date" fx:id="tDate" />
<TableColumn prefWidth="200.0" text="Name" fx:id="tName" />
<TableColumn prefWidth="75.0" text="Price" fx:id="tPrice" />
</columns>
</TableView>
</children>
</AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0" />
</items>
</SplitPane>
</children>
</AnchorPane>
MainViewController.java
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package controller;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import model.Table;
/**
* FXML Controller class
*
*/
public class MainViewController 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++, "Dinesh", "12/02/2013", "20"),
new Table(iNumber++, "Vignesh", "2/02/2013", "40"),
new Table(iNumber++, "Satheesh", "1/02/2013", "100"),
new Table(iNumber++, "Dinesh", "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);
}
}
Table.java
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package model;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
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);
}
}
Thanks in advance.
In a Table.java change all getters and setters names,
change getr* to getR*
change setr* to setR*
Thanks to #Uluk Biy. Whatever he mentioned, that needs to be changed and I found that I missed the <cellValueFactory> and <PropertyValueFactory> tags under the <TableColumn> tag for each columns in the FXML file which is as follows,
<TableColumn minWidth="20.0" prefWidth="40.0" text="ID" fx:id="tID" >
<cellValueFactory>
<PropertyValueFactory property="tID" />
</cellValueFactory>
</TableColumn>
Similar to this, all columns need to be updated like this.
After this change, the code is working fine. I am able to add the values into the row. :)
I use javafx2 control TableView to dispay records in the database(I have 20 records now).
And when display the record,I want to add button in each row.
so I search lots of article use google,and write some code below.
the key code is:
protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty);
if(empty) {
setText(null);
setGraphic(null);
} else {
final Button button = new Button("modifty");
setGraphic(button);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
}
I debug this code,the param "empty" would never be "false",
so the button would never be display in the table view,
can anyone help me? thanks
below is the complete code(java and fxml):
java:
package com.turbooo.restaurant.interfaces.javafx;
import java.io.IOException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.ResourceBundle;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.util.Callback;
import com.turbooo.restaurant.domain.Customer;
import com.turbooo.restaurant.domain.CustomerRepository;
import com.turbooo.restaurant.interfaces.facade.dto.CustomerDto;
import com.turbooo.restaurant.interfaces.facade.dto.assembler.CustomerDtoAssembler;
import com.turbooo.restaurant.util.ContextHolder;
import com.turbooo.restaurant.util.UTF8Control;
public class CustomerController implements Initializable {
#FXML
private TableView<CustomerDto> customerTableView;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
TableColumn<CustomerDto, Long> idCol = new TableColumn<CustomerDto, Long>("id");
idCol.setCellValueFactory(
new PropertyValueFactory<CustomerDto,Long>("id")
);
TableColumn<CustomerDto, String> nameCol = new TableColumn<CustomerDto, String>("name");
nameCol.setMinWidth(100);
nameCol.setCellValueFactory(
new PropertyValueFactory<CustomerDto,String>("name")
);
TableColumn<CustomerDto, String> birthdayCol = new TableColumn<CustomerDto, String>("birthday");
birthdayCol.setCellValueFactory(
new Callback<TableColumn.CellDataFeatures<CustomerDto, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<CustomerDto, String> customer) {
if (customer.getValue() != null) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return new SimpleStringProperty(sdf.format(customer.getValue().getBirthday()));
} else {
return new SimpleStringProperty("");
}
}
});
TableColumn<CustomerDto, Long> actionCol = new TableColumn<CustomerDto, Long>("action");
actionCol.setCellFactory(
new Callback<TableColumn<CustomerDto,Long>, TableCell<CustomerDto,Long>>() {
#Override
public TableCell<CustomerDto, Long> call(TableColumn<CustomerDto, Long> arg0) {
final TableCell<CustomerDto, Long> cell = new TableCell<CustomerDto, Long>() {
#Override
protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty);
if(empty) {
setText(null);
setGraphic(null);
} else {
final Button button = new Button("modifty");
setGraphic(button);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
}
};
return cell;
}
});
//TODO add button column
customerTableView.getColumns().addAll(idCol, nameCol, birthdayCol, actionCol);
loadData();
}
public void loadData() {
CustomerRepository cusRepo = (CustomerRepository)ContextHolder.getContext().getBean("customerRepository");
List<Customer> customers = cusRepo.findAll();
CustomerDtoAssembler customerDTOAssembler = new CustomerDtoAssembler();
List<CustomerDto> customerDtos = customerDTOAssembler.toDTOList(customers);
ObservableList<CustomerDto> data = FXCollections.observableArrayList(customerDtos);
customerTableView.setItems(data);
}
public void showNewDialog(ActionEvent event) {
ResourceBundle resourceBundle = ResourceBundle.getBundle(
"com/turbooo/restaurant/interfaces/javafx/customer_holder",
new UTF8Control());
Parent container = null;
try {
container = FXMLLoader.load(
getClass().getResource("customer_holder.fxml"),
resourceBundle);
} catch (IOException e) {
e.printStackTrace();
}
container.setUserData(this); //pass the controller, so can access LoadData function in other place later
Scene scene = new Scene(container, 400, 300);
Stage stage = new Stage();
stage.setScene(scene);
stage.initModality(Modality.APPLICATION_MODAL);
stage.show();
}
}
fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.paint.*?>
<BorderPane prefHeight="345.0" prefWidth="350.0" xmlns:fx="http://javafx.com/fxml" fx:controller="com.turbooo.restaurant.interfaces.javafx.CustomerController">
<center>
<TableView fx:id="customerTableView" prefHeight="200.0" prefWidth="200.0" />
</center>
<top>
<HBox alignment="CENTER_LEFT" prefHeight="42.0" prefWidth="350.0">
<children>
<Button text="new" onAction="#showNewDialog"/>
<Separator prefHeight="25.0" prefWidth="13.0" visible="false" />
<Button text="modify" />
<Separator prefHeight="25.0" prefWidth="15.0" visible="false" />
<Button text="delete" />
</children>
</HBox>
</top>
</BorderPane>
You haven't set a cellValueFactory for your action column, so it is always empty.
A simple way to do this is just to set the cell value to the id of the record.
actionCol.setCellValueFactory(
new PropertyValueFactory<CustomerDto,Long>("id")
);
Additionally, I created a simple example for creating a table with Add buttons in it, which you could reference if needed.