I have a own component (extended TextField). When I display the component in an AnchorPane the layout from the component is correctly displayed. But when I display the component in a TabPane then the first time when the component is shown the layout isn't correct rendered.
Screenshot:
Own component in an AnchorPane
Screenshot:
Own component in a TabPane
Here a MCVE:
import static javafx.scene.layout.Region.USE_PREF_SIZE;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.event.ActionEvent;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
*
* #author Naoghuman
*/
public class ExtendedComponentsMCVE extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
ExtendedTextField extendedTextField = new ExtendedTextField();
// OnlyAnchorPane oap = new OnlyAnchorPane(extendedTextField);
// Scene scene = new Scene(oap, 800, 600);
TabAnchorPane tap = new TabAnchorPane(extendedTextField);
Scene scene = new Scene(tap, 800, 600);
primaryStage.setTitle("Demo Extended Components");
primaryStage.setScene(scene);
primaryStage.show();
}
// ExtendedTextField #######################################################
class ExtendedTextField extends HBox {
private BooleanProperty configureCheckBoxProperty;
private BooleanProperty configureLeftLabelProperty;
private BooleanProperty configureTopLabelProperty;
private CheckBox cbReadOnly;
private Label lLeft;
private Label lTop;
private TextField tfText;
private VBox vBox;
private String lastUserInput = ""; // NOI18N
ExtendedTextField() {
super();
this.init();
}
private void init() {
// vbox
super.setAlignment(Pos.BOTTOM_LEFT);
super.setStyle("-fx-background-color: lightgreen;");
// left label
configureLeftLabelProperty = new SimpleBooleanProperty(Boolean.TRUE);
lLeft = new Label("<left>"); // NOI18N
lLeft.setMaxHeight(USE_PREF_SIZE);
lLeft.setMinHeight(USE_PREF_SIZE);
lLeft.visibleProperty().bind(configureLeftLabelProperty);
lLeft.managedProperty().bind(configureLeftLabelProperty);
super.getChildren().add(lLeft);
// checkbox
configureCheckBoxProperty = new SimpleBooleanProperty(Boolean.TRUE);
cbReadOnly = new CheckBox();
cbReadOnly.setMaxHeight(USE_PREF_SIZE);
cbReadOnly.setMinHeight(USE_PREF_SIZE);
cbReadOnly.visibleProperty().bind(configureCheckBoxProperty);
cbReadOnly.managedProperty().bind(configureCheckBoxProperty);
super.getChildren().add(cbReadOnly);
// vbox
vBox = new VBox();
HBox.setHgrow(vBox, Priority.ALWAYS);
// top label
configureTopLabelProperty = new SimpleBooleanProperty(Boolean.TRUE);
lTop = new Label("<top>"); // NOI18N
lTop.visibleProperty().bind(configureTopLabelProperty);
lTop.managedProperty().bind(configureTopLabelProperty);
vBox.getChildren().add(lTop);
// textfield
tfText = new TextField(lastUserInput);
tfText.disableProperty().bind(cbReadOnly.selectedProperty().not());
lLeft.prefHeightProperty().bind(tfText.heightProperty());
cbReadOnly.prefHeightProperty().bind(tfText.heightProperty());
vBox.getChildren().add(tfText);
super.getChildren().add(vBox);
}
public void setCheckBoxSelected(Boolean selected) {
cbReadOnly.setSelected(selected);
if (selected) {
tfText.setText(lastUserInput);
}
else {
lastUserInput = tfText.getText();
tfText.setText(null);
}
}
public void setCheckBoxVisibleManaged(Boolean visible) {
configureCheckBoxProperty.setValue(visible);
}
public void setLeftLabelVisibleManaged(boolean selected) {
configureLeftLabelProperty.setValue(selected);
}
public void setTopLabelVisibleManaged(boolean selected) {
configureTopLabelProperty.setValue(selected);
}
}
// ExtendedTextField #######################################################
// OnlyAnchorPane ##########################################################
class OnlyAnchorPane extends AnchorPane {
OnlyAnchorPane(ExtendedTextField extendedTextField) {
super();
super.setStyle("-fx-background-color: BLANCHEDALMOND;");
// hbox
HBox hbox = new HBox();
hbox.setStyle("-fx-background-color: KHAKI;");
hbox.setSpacing(7.0d);
hbox.setPrefWidth(Double.MAX_VALUE);
// extendedTextField
VBox vbox = new VBox();
vbox.getChildren().add(extendedTextField);
HBox.setHgrow(vbox, Priority.ALWAYS);
hbox.getChildren().add(vbox);
// menu
MenuVBox menu = new MenuVBox(extendedTextField);
hbox.getChildren().add(menu);
AnchorPane.setBottomAnchor(hbox, 14d);
AnchorPane.setLeftAnchor(hbox, 14d);
AnchorPane.setTopAnchor(hbox, 14d);
AnchorPane.setRightAnchor(hbox, 14d);
super.getChildren().add(hbox);
}
}
// OnlyAnchorPane ##########################################################
// TabAnchorPane ###########################################################
class TabAnchorPane extends AnchorPane {
TabAnchorPane(ExtendedTextField extendedTextField) {
super();
super.setStyle("-fx-background-color: BLANCHEDALMOND;");
// tabpane
TabPane tp = new TabPane();
// tab
Tab t = new Tab("TextField");
t.setClosable(false);
tp.getTabs().add(t);
// hbox
HBox hbox = new HBox();
hbox.setStyle("-fx-background-color: KHAKI;");
hbox.setSpacing(7.0d);
hbox.setPrefWidth(Double.MAX_VALUE);
// extendedTextField
VBox vbox = new VBox();
vbox.getChildren().add(extendedTextField);
HBox.setHgrow(vbox, Priority.ALWAYS);
hbox.getChildren().add(vbox);
// menu
MenuVBox menu = new MenuVBox(extendedTextField);
hbox.getChildren().add(menu);
t.setContent(hbox);
AnchorPane.setBottomAnchor(tp, 14d);
AnchorPane.setLeftAnchor(tp, 14d);
AnchorPane.setTopAnchor(tp, 14d);
AnchorPane.setRightAnchor(tp, 14d);
super.getChildren().add(tp);
}
}
// TabAnchorPane ###########################################################
// MenuVBox ################################################################
class MenuVBox extends VBox {
MenuVBox(ExtendedTextField extendedTextField) {
super();
super.setStyle("-fx-background-color: HONEYDEW;");
super.setSpacing(7.0d);
super.setMaxWidth(200.0d);
super.setMinWidth(200.0d);
super.setPrefWidth(200.0d);
// show top label
CheckBox cb1 = new CheckBox("Show top label");
cb1.setSelected(true);
cb1.setOnAction((ActionEvent event) -> {
extendedTextField.setTopLabelVisibleManaged(cb1.isSelected());
});
super.getChildren().add(cb1);
// show left label
CheckBox cb2 = new CheckBox("Show left label");
cb2.setSelected(true);
cb2.setOnAction((ActionEvent event) -> {
extendedTextField.setLeftLabelVisibleManaged(cb2.isSelected());
});
super.getChildren().add(cb2);
// seperator
super.getChildren().add(new Separator());
// select checkbox
CheckBox cb3 = new CheckBox("Select checkbox");
cb3.setSelected(false);
cb3.setOnAction((ActionEvent event) -> {
extendedTextField.setCheckBoxSelected(cb3.isSelected());
});
super.getChildren().add(cb3);
// show checkbox
CheckBox cb4 = new CheckBox("Show checkbox");
cb4.setSelected(true);
cb4.setOnAction((ActionEvent event) -> {
extendedTextField.setCheckBoxVisibleManaged(cb4.isSelected());
});
super.getChildren().add(cb4);
}
}
// MenuVBox ################################################################
}
That is the problem of laying out maybe due to the fixed values you tried to give for related nodes:
lLeft.setMaxHeight(USE_PREF_SIZE);
lLeft.setMinHeight(USE_PREF_SIZE);
and
cbReadOnly.setMaxHeight(USE_PREF_SIZE);
cbReadOnly.setMinHeight(USE_PREF_SIZE);
This just a guess, but anyway you can manually request the renderer to layout the components just at the end of constructing of them via runLater():
class ExtendedTextField extends HBox {
...
...
private void init() {
...
Platform.runLater( ()->{
requestLayout();
});
}
...
}
BTW, I could not see any control that extends TextField ;)
I am trying Show a Button on TextField look like Windows 8 Metro theme in javafx.
If TextField is empty button is invisible otherwise button show.
In this stage i'm little close to success. i use this code to make it.
#FXML
private TextField tfMyName;//fx:id="tfMyName"
#FXML
private Button btnClear;//fx:id="btnClear"
#Override
public void initialize(URL url, ResourceBundle rb) {
clearTextFieldByButton(tfMyName, btnClear);
}
public void clearTextFieldByButton(TextField value, Button btn){
btn.setVisible(false);
value.setOnKeyTyped(new EventHandler<KeyEvent>(){
#Override
public void handle(KeyEvent event) {
if ((value.textProperty().get().length() < 0) || (value.textProperty().get().equals(""))) {
btn.setVisible(false);
} else if (value.textProperty().get().length() > -1 || (!value.textProperty().get().equals(""))) {
btn.setVisible(true);
}
}
});
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
tfMyName.clear();
btn.setVisible(false);
tfMyName.requestFocus();
}
});
Using this code by default button is invisible but the button is only visible when i type more then one Characters.
But i need if anything input into the TextField to Button show.
But when i remove the condition under KeyEvent replace by
value.setOnKeyTyped(new EventHandler<KeyEvent>(){
#Override
public void handle(KeyEvent event) {
btn.setVisible(true);
}
});
Then btn show if any character input into the TextField
You may also prefer to use JavaFX binding mechanism:
#Override
public void start( final Stage primaryStage )
{
TextField textfield = new TextField();
Button button = new Button( "my button" );
button.visibleProperty().bind( textfield.textProperty().isEmpty().not() );
final Scene scene = new Scene( new HBox( button, textfield ), 800, 600 );
primaryStage.setScene( scene );
primaryStage.show();
}
The actual problem in your code:
You have attached a listener to field when "OnKeyTyped", at this stage the newly typed text is not appended to the textfield's text value so your if-else condition will not see it. Instead, the correct way should be attaching the listener on "OnKeyReleased".
Add a listener to the textProperty() of the TextField. Check if the value is empty, hide the button else show it. It will be called whenever a character is added or removed from the textfield.
Here is a MCVE, you can just add the listener to the initialize method of the controller.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class HideButtonOnTextEntered extends Application {
#Override
public void start(Stage stage) {
TextField textField = new TextField();
Button button = new Button("Button");
button.setVisible(false);
VBox root = new VBox(20, textField, button);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 200, 200);
stage.setScene(scene);
stage.show();
textField.textProperty().addListener((ov, oldValue, newValue) -> {
if (newValue.isEmpty()) {
button.setVisible(false);
} else {
button.setVisible(true);
}
});
}
public static void main(String[] args) {
launch(args);
}
}
Is there a way to add MODAL capability to a context menu?
My code is below:
package snippet;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
public class ContextMenuSample extends Application
{
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage stage)
{
stage.setTitle("ContextMenuSample");
Scene scene = new Scene(new Group(), 450, 250);
Label toLabel = new Label("To: ");
TextField notification = new TextField();
final ContextMenu contextMenu = new ContextMenu();
contextMenu.setAutoHide(false);
contextMenu.setOnShowing(new EventHandler<WindowEvent>()
{
public void handle(WindowEvent e)
{
System.out.println("showing the context menu");
}
});
contextMenu.setOnShown(new EventHandler<WindowEvent>()
{
public void handle(WindowEvent e)
{
System.out.println("context menu has been shown");
}
});
MenuItem closeItem = new MenuItem("Close");
closeItem.setOnAction(new EventHandler<ActionEvent>()
{
public void handle(ActionEvent e)
{
contextMenu.hide();
}
});
MenuItem colorItem = new MenuItem("Choose", new ColorPicker());
colorItem.setOnAction(new EventHandler<ActionEvent>()
{
public void handle(ActionEvent e)
{
System.out.println("Preferences");
}
});
GridPane contextGridPane = new GridPane();
Pane pane = new Pane();
pane.getChildren().add(contextGridPane);
contextMenu.getItems().addAll(colorItem, deleteItem// , subsystem1,
// radioItem
);
toLabel.setContextMenu(contextMenu);
GridPane grid = new GridPane();
grid.setVgap(4);
grid.setHgap(10);
grid.setPadding(new Insets(5, 5, 5, 5));
grid.add(toLabel, 0, 0);
grid.add(notification, 1, 0);
grid.add(new ColorPicker(), 2, 0);
Group root = (Group) scene.getRoot();
root.getChildren().add(grid);
stage.setScene(scene);
stage.show();
}
}
When the user clicks on the label "To", a context menu appears. I wish to have modal capability for this context menu such that the user is not able to do anything else on the application unless some operation is performed on the context menu. Also, when the context menu is active, the user should not be able to click anywhere else on the application.
Regards,
The easiest solution would be to call another Stage and set its modality with initModality before you show the stage. You probably want to use Modality.APPLICATION_MODEL as far as I understood you.
Here is a small example derived from yours (btw your code was not even runnable, it had errors)
public class ContextMenuSample extends Application
{
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(final Stage stageOne)
{
final Stage stageTwo = new Stage();
stageTwo.initModality(Modality.APPLICATION_MODAL);
final Pane layoutOne = new HBox(10);
Pane layoutTwo = new HBox(10);
Label labelOne = new Label("click");
Label labelTwo = new Label("other click");
labelOne.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
stageTwo.show();
}
});
labelTwo.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
stageTwo.close();
}
});
Scene sceneOne = new Scene(layoutOne);
Scene sceneTwo = new Scene(layoutTwo);
layoutOne.getChildren().add(labelOne);
layoutTwo.getChildren().add(labelTwo);
stageOne.setScene(sceneOne);
stageTwo.setScene(sceneTwo);
stageOne.show();
}
}
I found this example of Internal Frames
http://docs.oracle.com/javase/tutorial/uiswing/components/internalframe.html
Is it possible to make the same internal Frames in JavaFX?
With JFXtras there is a Window control, where you can add content and handle the internal window behavior.
First you will need to put in your classpath the jfxtras library. They have some instructions where you can get the library. If you are using maven, just need to add:
<dependency>
<groupId>org.jfxtras</groupId>
<artifactId>jfxtras-labs</artifactId>
<version>2.2-r5</version>
</dependency>
Or download the library and put it into your project classpath, whatever.
Now I put a sample of the demo of the Window with a little difference, allowing generation of several windows.
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.stage.Stage;
import jfxtras.labs.scene.control.window.CloseIcon;
import jfxtras.labs.scene.control.window.MinimizeIcon;
import jfxtras.labs.scene.control.window.Window;
public class WindowTests extends Application {
private static int counter = 1;
private void init(Stage primaryStage) {
final Group root = new Group();
Button button = new Button("Add more windows");
root.getChildren().addAll(button);
primaryStage.setResizable(false);
primaryStage.setScene(new Scene(root, 600, 500));
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
// create a window with title "My Window"
Window w = new Window("My Window#"+counter);
// set the window position to 10,10 (coordinates inside canvas)
w.setLayoutX(10);
w.setLayoutY(10);
// define the initial window size
w.setPrefSize(300, 200);
// either to the left
w.getLeftIcons().add(new CloseIcon(w));
// .. or to the right
w.getRightIcons().add(new MinimizeIcon(w));
// add some content
w.getContentPane().getChildren().add(new Label("Content... \nof the window#"+counter++));
// add the window to the canvas
root.getChildren().add(w);
}
});
}
public double getSampleWidth() {return 600;}
public double getSampleHeight() {return 500;}
#Override
public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) {launch(args);}
}
In the original demo, the event code was in the init method, and no button was included. I add the button to create dynamically windows and adding them to the screen.
Here is a snapshot of the result of the application:
I totally recommend you try the demo of jfxtras. They have really great stuff. Hope it helps.
You can implement simple internal window themselves. Main idea, that InternalWindow class just skeleton, that has internal frame like functionality. You can apply any content to it.
1) Declare class
public class InternalWindow extends Region
2) You should be able to set content in window
public void setRoot(Node node) {
getChildren().add(node);
}
3) You should be able to bring window to front if many window exist
public void makeFocusable() {
this.setOnMouseClicked(mouseEvent -> {
toFront();
});
}
4) Now we need dragging functionality
//just for encapsulation
private static class Delta {
double x, y;
}
//we can select nodes that react drag event
public void makeDragable(Node what) {
final Delta dragDelta = new Delta();
what.setOnMousePressed(mouseEvent -> {
dragDelta.x = getLayoutX() - mouseEvent.getScreenX();
dragDelta.y = getLayoutY() - mouseEvent.getScreenY();
//also bring to front when moving
toFront();
});
what.setOnMouseDragged(mouseEvent -> {
setLayoutX(mouseEvent.getScreenX() + dragDelta.x);
setLayoutY(mouseEvent.getScreenY() + dragDelta.y);
});
}
5) Also we want able to resize window (I show only simple right-bottom resizing)
//current state
private boolean RESIZE_BOTTOM;
private boolean RESIZE_RIGHT;
public void makeResizable(double mouseBorderWidth) {
this.setOnMouseMoved(mouseEvent -> {
//local window's coordiantes
double mouseX = mouseEvent.getX();
double mouseY = mouseEvent.getY();
//window size
double width = this.boundsInLocalProperty().get().getWidth();
double height = this.boundsInLocalProperty().get().getHeight();
//if we on the edge, change state and cursor
if (Math.abs(mouseX - width) < mouseBorderWidth
&& Math.abs(mouseY - height) < mouseBorderWidth) {
RESIZE_RIGHT = true;
RESIZE_BOTTOM = true;
this.setCursor(Cursor.NW_RESIZE);
} else {
RESIZE_BOTTOM = false;
RESIZE_RIGHT = false;
this.setCursor(Cursor.DEFAULT);
}
});
this.setOnMouseDragged(mouseEvent -> {
//resize root
Region region = (Region) getChildren().get(0);
//resize logic depends on state
if (RESIZE_BOTTOM && RESIZE_RIGHT) {
region.setPrefSize(mouseEvent.getX(), mouseEvent.getY());
} else if (RESIZE_RIGHT) {
region.setPrefWidth(mouseEvent.getX());
} else if (RESIZE_BOTTOM) {
region.setPrefHeight(mouseEvent.getY());
}
});
}
6) Usage. First we construct all layout. Then apply it to InternalWindow.
private InternalWindow constructWindow() {
// content
ImageView imageView = new ImageView("https://upload.wikimedia.org/wikipedia/commons/thumb/a/a9/Cheetah4.jpg/250px-Cheetah4.jpg");
// title bar
BorderPane titleBar = new BorderPane();
titleBar.setStyle("-fx-background-color: green; -fx-padding: 3");
Label label = new Label("header");
titleBar.setLeft(label);
Button closeButton = new Button("x");
titleBar.setRight(closeButton);
// title bat + content
BorderPane windowPane = new BorderPane();
windowPane.setStyle("-fx-border-width: 1; -fx-border-color: black");
windowPane.setTop(titleBar);
windowPane.setCenter(imageView);
//apply layout to InternalWindow
InternalWindow interalWindow = new InternalWindow();
interalWindow.setRoot(windowPane);
//drag only by title
interalWindow.makeDragable(titleBar);
interalWindow.makeDragable(label);
interalWindow.makeResizable(20);
interalWindow.makeFocusable();
return interalWindow;
}
7) And how add window to layout
#Override
public void start(Stage primaryStage) throws Exception {
Pane root = new Pane();
root.getChildren().add(constructWindow());
root.getChildren().add(constructWindow());
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
Result
Full code: gist
Upd about close button:
You can add method to InternalWindow
public void setCloseButton(Button btn) {
btn.setOnAction(event -> ((Pane) getParent()).getChildren().remove(this));
}
And when construct:
interalWindow.setCloseButton(closeButton);
My application uses JPA read data into TableView then modify and display them. The table refreshed modified record under JavaFx 2.0.3. Under JavaFx 2.1, 2.2, the table wouldn't refresh the update anymore. I found other people have similar issue. My plan was to continue using 2.0.3 until someone fixes the issue under 2.1 and 2.2. Now I know it is not a bug and wouldn't be fixed. Well, I don't know how to deal with this. Following are codes are modified from sample demo to show the issue. If I add a new record or delete a old record from table, table refreshes fine. If I modify a record, the table wouldn't refreshes the change until a add, delete or sort action is taken. If I remove the modified record and add it again, table refreshes. But the modified record is put at button of table. Well, if I remove the modified record, add the same record then move the record to the original spot, the table wouldn't refresh anymore. Below is a completely code, please shine some light on this.
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
private TextField firtNameField = new TextField();
private TextField lastNameField = new TextField();
private TextField emailField = new TextField();
private Stage editView;
private Person fPerson;
public static class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
private Person(String fName, String lName, String email) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String fName) {
firstName.set(fName);
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String fName) {
lastName.set(fName);
}
public String getEmail() {
return email.get();
}
public void setEmail(String fName) {
email.set(fName);
}
}
private TableView<Person> table = new TableView<Person>();
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"),
new Person("Ethan", "Williams", "ethan.williams#example.com"),
new Person("Emma", "Jones", "emma.jones#example.com"),
new Person("Michael", "Brown", "michael.brown#example.com"));
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setTitle("Table View Sample");
stage.setWidth(535);
stage.setHeight(535);
editView = new Stage();
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName"));
firstNameCol.setMinWidth(150);
TableColumn lastNameCol = new TableColumn("Last Name");
lastNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("lastName"));
lastNameCol.setMinWidth(150);
TableColumn emailCol = new TableColumn("Email");
emailCol.setMinWidth(200);
emailCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("email"));
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
//--- create a edit button and a editPane to edit person
Button addButton = new Button("Add");
addButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
fPerson = null;
firtNameField.setText("");
lastNameField.setText("");
emailField.setText("");
editView.show();
}
});
Button editButton = new Button("Edit");
editButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (table.getSelectionModel().getSelectedItem() != null) {
fPerson = table.getSelectionModel().getSelectedItem();
firtNameField.setText(fPerson.getFirstName());
lastNameField.setText(fPerson.getLastName());
emailField.setText(fPerson.getEmail());
editView.show();
}
}
});
Button deleteButton = new Button("Delete");
deleteButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (table.getSelectionModel().getSelectedItem() != null) {
data.remove(table.getSelectionModel().getSelectedItem());
}
}
});
HBox addEditDeleteButtonBox = new HBox();
addEditDeleteButtonBox.getChildren().addAll(addButton, editButton, deleteButton);
addEditDeleteButtonBox.setAlignment(Pos.CENTER_RIGHT);
addEditDeleteButtonBox.setSpacing(3);
GridPane editPane = new GridPane();
editPane.getStyleClass().add("editView");
editPane.setPadding(new Insets(3));
editPane.setHgap(5);
editPane.setVgap(5);
Label personLbl = new Label("Person:");
editPane.add(personLbl, 0, 1);
GridPane.setHalignment(personLbl, HPos.LEFT);
firtNameField.setPrefWidth(250);
lastNameField.setPrefWidth(250);
emailField.setPrefWidth(250);
Label firstNameLabel = new Label("First Name:");
Label lastNameLabel = new Label("Last Name:");
Label emailLabel = new Label("Email:");
editPane.add(firstNameLabel, 0, 3);
editPane.add(firtNameField, 1, 3);
editPane.add(lastNameLabel, 0, 4);
editPane.add(lastNameField, 1, 4);
editPane.add(emailLabel, 0, 5);
editPane.add(emailField, 1, 5);
GridPane.setHalignment(firstNameLabel, HPos.RIGHT);
GridPane.setHalignment(lastNameLabel, HPos.RIGHT);
GridPane.setHalignment(emailLabel, HPos.RIGHT);
Button saveButton = new Button("Save");
saveButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (fPerson == null) {
fPerson = new Person(
firtNameField.getText(),
lastNameField.getText(),
emailField.getText());
data.add(fPerson);
} else {
int k = -1;
if (data.size() > 0) {
for (int i = 0; i < data.size(); i++) {
if (data.get(i) == fPerson) {
k = i;
}
}
}
fPerson.setFirstName(firtNameField.getText());
fPerson.setLastName(lastNameField.getText());
fPerson.setEmail(emailField.getText());
data.set(k, fPerson);
table.setItems(data);
// The following will work, but edited person has to be added to the button
//
// data.remove(fPerson);
// data.add(fPerson);
// add and remove refresh the table, but now move edited person to original spot,
// it failed again with the following code
// while (data.indexOf(fPerson) != k) {
// int i = data.indexOf(fPerson);
// Collections.swap(data, i, i - 1);
// }
}
editView.close();
}
});
Button cancelButton = new Button("Cancel");
cancelButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
editView.close();
}
});
HBox saveCancelButtonBox = new HBox();
saveCancelButtonBox.getChildren().addAll(saveButton, cancelButton);
saveCancelButtonBox.setAlignment(Pos.CENTER_RIGHT);
saveCancelButtonBox.setSpacing(3);
VBox editBox = new VBox();
editBox.getChildren().addAll(editPane, saveCancelButtonBox);
Scene editScene = new Scene(editBox);
editView.setTitle("Person");
editView.initStyle(StageStyle.UTILITY);
editView.initModality(Modality.APPLICATION_MODAL);
editView.setScene(editScene);
editView.close();
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.getChildren().addAll(label, table, addEditDeleteButtonBox);
vbox.setPadding(new Insets(10, 0, 0, 10));
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
}
See the answer to Updating rows in Tableview. Add these getters and it will just work.
Additionally since the data is an ObservableList which is set as items to tableView, any changes to this data list will be reflected to the table.getItems() too. Namely no need to table.setItems(data) again.
I have found a simple workaround for triggering the refresh of the TableView in JavaFX 2.1 TableView refresh items. It solved the issue for me.
Add this to your code:
tableView.getColumns().get(0).setVisible(false);
tableView.getColumns().get(0).setVisible(true);
looking into the TableView.java code, there's private refresh() which just executes
getProperties().put(TableViewSkinBase.REFRESH, Boolean.TRUE);
At last, the code below worked for me(Java8). (be careful, the constant's name is not REFRESH but RECREATE)
tableView.getProperties().put(TableViewSkinBase.RECREATE, Boolean.TRUE);
(reading javafx's code, this will force cell re-creation)
Notification-based updates of JavaFX controls typically require that the properties of the data model object backing your GUI meet the minimum definition for a JavaFX Bean.
The following exemplifies the minimum code needed in order for a JavaFX property to satisfy these requirements:
public class Client extends DB {
private IntegerProperty id = new SimpleIntegerProperty();
private StringProperty lastName = new SimpleStringProperty();
private StringProperty firstName = new SimpleStringProperty();
public final int getID() {return this.id.get(); }
void setID(int id) { this.id.set(id); }
public final IntegerProperty idProperty() { return this.id; }
public final String getLastName() { return this.lastName.get(); }
public final void setLastName(String ln) { this.lastName.set(ln); }
public final StringProperty lastNameProperty() { return this.lastName; }
public final String getFirstName() { return this.firstName.get(); }
public final void setFirstName(String fn) { this.firstName.set(fn); }
public final StringProperty firstNameProperty() { return this.firstName; }
:
:
}
Glancing over your code, it does not appear that your properties satisfy the requirements for a JavFX Bean. As such, automatic notification-based updates will not occur.
I have the same problem, and not being able to add SimpleStringProperty to the POJO's used by JPA makes this a bit problematic. But it seems to me that this should be fixable issue because I have noticed the following behavior:
In my application, clicking on a row in the table populates some text fields on the screen, that the user can then edit.
At that point the user can save the data, or create a new item with the same or changed data. If the user creates a new item, which is then inserted into the observable list that the tableview represents, the change is immediately reflected in the contents of the tableview. However if the user just saves the change the new data is not reflected in the table. To put the new data in the list I'm simply doing
trialList.set(currentIndex, tempTrial);
And here's what I think points to this as a fixable issue: if I scroll the affected row out of view on the table and then scroll it back, the 'new' value(s) are now presented.
Hopefully, this can be fixed. Sorry this isn't an answer, so to speak, but might provide some insight for a fix.
this worked for me
#FXML
private void refreshTableView()
{
firstNameCol.setVisible(false);
lastNameCol.setVisible(false);
emailCol.setVisible(false);
firstNameCol.setVisible(true);
lastNameCol.setVisible(true);
emailCol.setVisible(true);
}
I had the same problem and after some search this is my workaround. I found that if the columns are removed and then re-added the table is updated.
public static <T,U> void refreshTableView(final TableView<T> tableView, final List<TableColumn<T,U>> columns, final List<T> rows) {
if (tableView == null) {
throw new NullPointerException();
}
if (columns == null) {
throw new NullPointerException();
}
if (rows == null) {
throw new NullPointerException();
}
tableView.getColumns().clear();
tableView.getColumns().addAll(columns);
ObservableList<T> list = FXCollections.observableArrayList(rows);
tableView.setItems(list);
}
Example of usage:
refreshTableView(myTableView, Arrays.asList(col1, col2, col3), rows);