admin管理员组文章数量:1355754
I have a table and I need to add a context menu for rows. The problem is that context menu is shown also for empty rows. To fix it I remove and add context menu using listener on empty property. But taking into consideration that virtual rows are updated very often I am not sure that it is a performance-efficient solution.
This is my code:
public class NewMain1 extends Application {
public static class Person {
private final StringProperty firstName;
private final StringProperty lastName;
public Person(String firstName, String lastName) {
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
}
public StringProperty firstNameProperty() { return firstName; }
public StringProperty lastNameProperty() { return lastName; }
}
@Override
public void start(Stage primaryStage) {
TableView<Person> tableView = new TableView<>();
tableView.setEditable(true);
TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
firstNameCol.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
firstNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
lastNameCol.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty());
tableView.getColumns().addAll(firstNameCol, lastNameCol);
ObservableList<Person> data = FXCollections.observableArrayList(
new Person("John", "Smith"),
new Person("Emily", "Johnson"),
new Person("Michael", "Williams"),
new Person("Sarah", "Brown")
);
tableView.setItems(data);
tableView.setRowFactory(tv -> {
TableRow<Person> row = new TableRow<>();
var contextMenu = new ContextMenu(new MenuItem("Show Salary"));
row.emptyProperty().addListener((ov, oldV, newV) -> {
if (newV) {
row.setContextMenu(null);
} else {
row.setContextMenu(contextMenu);
}
});
return row;
});
VBox root = new VBox(tableView);
Scene scene = new Scene(root, 400, 300);
primaryStage.setTitle("Editable Names Table");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Could anyone say if there is a better solution not to show context menu for empty rows?
I have a table and I need to add a context menu for rows. The problem is that context menu is shown also for empty rows. To fix it I remove and add context menu using listener on empty property. But taking into consideration that virtual rows are updated very often I am not sure that it is a performance-efficient solution.
This is my code:
public class NewMain1 extends Application {
public static class Person {
private final StringProperty firstName;
private final StringProperty lastName;
public Person(String firstName, String lastName) {
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
}
public StringProperty firstNameProperty() { return firstName; }
public StringProperty lastNameProperty() { return lastName; }
}
@Override
public void start(Stage primaryStage) {
TableView<Person> tableView = new TableView<>();
tableView.setEditable(true);
TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
firstNameCol.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
firstNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
lastNameCol.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty());
tableView.getColumns().addAll(firstNameCol, lastNameCol);
ObservableList<Person> data = FXCollections.observableArrayList(
new Person("John", "Smith"),
new Person("Emily", "Johnson"),
new Person("Michael", "Williams"),
new Person("Sarah", "Brown")
);
tableView.setItems(data);
tableView.setRowFactory(tv -> {
TableRow<Person> row = new TableRow<>();
var contextMenu = new ContextMenu(new MenuItem("Show Salary"));
row.emptyProperty().addListener((ov, oldV, newV) -> {
if (newV) {
row.setContextMenu(null);
} else {
row.setContextMenu(contextMenu);
}
});
return row;
});
VBox root = new VBox(tableView);
Scene scene = new Scene(root, 400, 300);
primaryStage.setTitle("Editable Names Table");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Could anyone say if there is a better solution not to show context menu for empty rows?
Share Improve this question asked Mar 31 at 12:28 SilverCubeSilverCube 6403 silver badges12 bronze badges 3 |3 Answers
Reset to default 4Your idea is not bad in itself. But there are 2 points where you could optimize:
- Create only one context menu, use it throughout the rows
- Override the row's
updateItem
method instead of using a listener on theempty
property
public class NewMain1 extends Application {
public static class Person {
private final StringProperty firstName;
private final StringProperty lastName;
public Person(String firstName, String lastName) {
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
}
public StringProperty firstNameProperty() { return firstName; }
public StringProperty lastNameProperty() { return lastName; }
}
@Override
public void start(Stage primaryStage) {
TableView<Person> tableView = new TableView<>();
tableView.setEditable(true);
TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
firstNameCol.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
firstNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
lastNameCol.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty());
tableView.getColumns().addAll(firstNameCol, lastNameCol);
ObservableList<Person> data = FXCollections.observableArrayList(
new Person("John", "Smith"),
new Person("Emily", "Johnson"),
new Person("Michael", "Williams"),
new Person("Sarah", "Brown")
);
tableView.setItems(data);
final var contextMenu = new ContextMenu(new MenuItem("Show Salary"));
tableView.setRowFactory(tv -> {
TableRow<Person> row = new TableRow<>() {
@Override
public void updateItem(Person item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setContextMenu(null);
} else {
setContextMenu(contextMenu);
}
}
};
return row;
});
VBox root = new VBox(tableView);
Scene scene = new Scene(root, 400, 300);
primaryStage.setTitle("Editable Names Table");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I think this can be fixed easily. Override the method show() of ContextMenu and decide whether to show or not based on the row empty.
TableRow<Person> row = new TableRow<>();
ContextMenu contextMenu = new ContextMenu(new MenuItem("Show Salary")){
@Override
protected void show() {
if(!row.isEmpty()) {
super.show();
}
}
};
row.setContextMenu(contextMenu);
I'm a firm believer in not optimizing for performance unless there is a demonstrated issue, and trying foremost to write code that is clean and easy to read. The code in the OP is unlikely to cause performance issues, and is "standard" JavaFX code in that it uses standard features of the API (cell factories, bindings) in the way they are intended. Also note that the event handler for the menu item (which isn't shown in the OP) needs to access the item associated with the table row. This is much easier and cleaner if each row has its own context menu; of course this incurs a performance penalty as you create multiple context menus.
That said, here is (yet) another solution. Like julien.giband's solution, this creates a single context menu that is shared by all (non-empty) rows. This avoids changing the row's context menu property entirely; instead of setting that property, a listener for contextMenuRequested
is registered with the row which displays the context menu only if the row is non-empty.
As with any solution using a single context menu for all rows, the code for the handlers for the menu items becomes more complex than using a different context menu for each row. To emphasize, under the principle of not complexifying code for performance reasons unless it's demonstrated that it's necessary, I would probably err on the side of using a different context menu for each row for this reason, unless profiling indicated it was necessary. I show one way to manage the menu item event handler here.
Here is the relevant code:
var contextMenu = new ContextMenu() {
private Person person;
void show(Node node, double x, double y, Person person) {
this.person = person;
show(node, x, y);
}
};
MenuItem salaryItem = new MenuItem("Show Salary");
salaryItem.setOnAction( _ ->
System.out.println("Salary for "+contextMenu.person.firstNameProperty().get())
);
contextMenu.getItems().add(salaryItem);
tableView.setRowFactory(tv -> {
TableRow<Person> row = new TableRow<>();
row.setOnContextMenuRequested(evt -> {
if (! row.isEmpty()) {
contextMenu.show(row, evt.getScreenX(), evt.getScreenY(), row.getItem());
}
});
return row;
});
and the complete example:
public class NewMain1 extends Application {
public static class Person {
private final StringProperty firstName;
private final StringProperty lastName;
public Person(String firstName, String lastName) {
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
}
public StringProperty firstNameProperty() { return firstName; }
public StringProperty lastNameProperty() { return lastName; }
}
@Override
public void start(Stage primaryStage) {
TableView<Person> tableView = new TableView<>();
tableView.setEditable(true);
TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
firstNameCol.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
firstNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
lastNameCol.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty());
tableView.getColumns().addAll(firstNameCol, lastNameCol);
ObservableList<Person> data = FXCollections.observableArrayList(
new Person("John", "Smith"),
new Person("Emily", "Johnson"),
new Person("Michael", "Williams"),
new Person("Sarah", "Brown")
);
tableView.setItems(data);
var contextMenu = new ContextMenu() {
private Person person;
void show(Node node, double x, double y, Person person) {
this.person = person;
show(node, x, y);
}
};
MenuItem salaryItem = new MenuItem("Show Salary");
salaryItem.setOnAction( _ ->
System.out.println("Salary for "+contextMenu.person.firstNameProperty().get())
);
contextMenu.getItems().add(salaryItem);
tableView.setRowFactory(tv -> {
TableRow<Person> row = new TableRow<>();
row.setOnContextMenuRequested(evt -> {
if (! row.isEmpty()) {
contextMenu.show(row, evt.getScreenX(), evt.getScreenY(), row.getItem());
}
});
return row;
});
VBox root = new VBox(tableView);
Scene scene = new Scene(root, 400, 300);
primaryStage.setTitle("Editable Names Table");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
本文标签: javaHow not to show context menu on empty TableRow in JavaFXStack Overflow
版权声明:本文标题:java - How not to show context menu on empty TableRow in JavaFX? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1743948085a2566779.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
TableRow
instances anyway, no matter how many items there are in the table. You could profile this, but I doubt you will see any performance issues at all. – James_D Commented Mar 31 at 13:50