JavaFX TableView 过滤鼠标右键选择
JavaFX TableView Filter Right Mouse Button Selection
当我在 JavaFX TreeTableView 上按下鼠标右键时,它 select 就是我单击的节点。我能理解为什么在大多数情况下这可能是可取的,但并非在所有情况下都如此。
我正在开发一个应用程序,其中我在树 table 中有多个列,其中一列使用 canvas 自定义图形(波形)小部件。由于各种原因(设置标记、缩放等),图形列需要能够与鼠标进行交互。因此,我不希望鼠标按钮 select table 中的行(或与 table 交互)。
我可以通过为 MouseEvent.MOUSE_CLICKED
和 event.consume()
放置 addEventFilter()
来过滤掉第一个鼠标按钮的点击。然后我可以按我想要的方式处理鼠标单击。
然而,当我右键单击单元格时,table 中的 selection 更改为该行。我已经尝试在单元格、table、行、列上放置事件过滤器,但似乎无法过滤右键单击 selection 更改。请注意 canvas 小部件设置为鼠标透明。
这是一个使用标准地址簿示例的示例,只是我用 canvas 小部件替换了电子邮件列。
package sample;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Main extends Application {
private TableView<Person> table = new TableView<Person>();
private ContextMenu menu = new ContextMenu();
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")
);
class MyTableCell extends TableCell<Person, String> {
public MyTableCell(ContextMenu menu) {
super();
setContextMenu(menu);
}
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(item);
setGraphic(null);
}
}
class MySpecialCell extends MyTableCell {
Canvas canvas = new Canvas(200.0, 12.0);
public MySpecialCell() {
super(null);
canvas.setMouseTransparent(true);
addEventFilter(MouseEvent.ANY, e -> e.consume());
}
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(null);
if (! empty) {
canvas.getGraphicsContext2D().strokeText(item, 5.0, 10.0);
setGraphic(canvas);
} else {
setGraphic(null);
}
}
}
@Override
public void start(Stage stage) throws Exception{
Scene scene = new Scene(new Group());
stage.setTitle("Table View Sample");
stage.setWidth(450);
stage.setHeight(500);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
menu.getItems().add(new MenuItem("Hello World"));
Callback cellFactory = param -> new MyTableCell(menu);
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName"));
firstNameCol.setCellFactory(cellFactory);
TableColumn lastNameCol = new TableColumn("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("lastName"));
lastNameCol.setCellFactory(cellFactory);
TableColumn emailCol = new TableColumn("Email");
emailCol.setMinWidth(200);
emailCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("email"));
emailCol.setCellFactory(param -> new MySpecialCell());
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
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);
}
}
}
这是使用 Java 8u20。在此示例中,MyTableCell
class 用于前两列中的单元格。它向这些列添加了一个上下文菜单。 MySpecialCell
class 用于最后一列,它在 table 中放置一个 canvas,其中包含电子邮件文本。它还会过分过滤 ANY
鼠标事件。
如果您 运行 这样做,您将看到如果您在前两列中使用鼠标按钮 1,则可以更改 table 中的 selection。第三列中的鼠标按钮 1 不会更改 selection。然而,鼠标按钮 2(鼠标右键)确实 改变了第三列中的select离子。
我希望它不改变 select离子。有人可以提示我在列中使用鼠标右键时如何防止 selection 发生变化吗?
注意:我已经尝试让 canvas 处理鼠标输入(实际上并没有让它对鼠标透明并让它过滤所有鼠标事件)并且 table 仍然改变 select右键单击离子。由于各种原因(数据在哪里,canvas 实际上是一堆 canvas 小部件,在 table 中很难弄清楚鼠标正在与哪个节点交互,等)在我的实际应用程序中,我希望单元格处理鼠标输入。
谢谢!
重复我的评论:我认为这种行为可能是错误而不是功能 - 我希望使用任何 mouseEvent 都会像左(主)按钮一样阻止右(辅助)按钮的正常操作.
是evaluated as a feature:缺少的部分是除了使用 mouseEvents 外还使用 contextMenuEvents,例如
public MySpecialCell() {
super(null);
canvas.setMouseTransparent(true);
addEventFilter(MouseEvent.ANY, e -> e.consume());
// move Jonathan's code from table to cell level:
// need to consume contextMenuEvents as well
addEventFilter(ContextMenuEvent.ANY, e -> e.consume());
}
返回不同意评价:
- 所有鼠标按钮应该一致地消耗所有 mouseEvents
- contextMenuEvent 基本上与选择无关,因此使用它对选择应该没有影响(无论哪种方式)
如果需要在特殊列上通过键盘触发上下文菜单,则可能仍然需要下面的 hack,但没有进一步挖掘。
无论如何,一个快速的 hack-around 是用自定义实现替换默认行为。合作者
- 覆盖 doSelect 的自定义 TableCellBehaviour 在接收到辅助按钮事件时不执行任何操作
- 自定义 TableCellSkin:仅需插入自定义行为 - 需要扩展 TableCellSkinBase(并从 TableCellSkin 中 c&p 实现其抽象方法),因为只有这样我们才有一个采用自定义行为的构造函数
- 让自定义 TableCell 创建自定义外观而不是默认外观
类似的东西(注意:在工作时,它没有经过适当的测试):
class MySpecialCellBehavior extends TableCellBehavior {
public MySpecialCellBehavior(TableCell control) {
super(control);
}
@Override
protected void doSelect(double x, double y, MouseButton button,
int clickCount, boolean shiftDown, boolean shortcutDown) {
// do nothing on secondary button
if (button == MouseButton.SECONDARY) return;
super.doSelect(x, y, button, clickCount, shiftDown, shortcutDown);
}
}
class MySpecialCellSkin extends TableCellSkinBase {
private final TableColumn tableColumn;
public MySpecialCellSkin(TableCell tableCell) {
super(tableCell, new MySpecialCellBehavior(tableCell));
// doesn't make a difference
//consumeMouseEvents(true);
this.tableColumn = tableCell.getTableColumn();
super.init(tableCell);
}
@Override protected BooleanProperty columnVisibleProperty() {
return tableColumn.visibleProperty();
}
@Override protected ReadOnlyDoubleProperty columnWidthProperty() {
return tableColumn.widthProperty();
}
}
class MySpecialCell extends MyTableCell {
Canvas canvas = new Canvas(200.0, 12.0);
public MySpecialCell() {
super(null);
canvas.setMouseTransparent(true);
addEventFilter(MouseEvent.ANY, e -> e.consume());
}
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(null);
if (! empty) {
canvas.getGraphicsContext2D().strokeText(item, 5.0, 10.0);
setGraphic(canvas);
} else {
setGraphic(null);
}
}
// create and return the custom skin
@Override
protected Skin<?> createDefaultSkin() {
return new MySpecialCellSkin(this);
}
}
当我在 JavaFX TreeTableView 上按下鼠标右键时,它 select 就是我单击的节点。我能理解为什么在大多数情况下这可能是可取的,但并非在所有情况下都如此。
我正在开发一个应用程序,其中我在树 table 中有多个列,其中一列使用 canvas 自定义图形(波形)小部件。由于各种原因(设置标记、缩放等),图形列需要能够与鼠标进行交互。因此,我不希望鼠标按钮 select table 中的行(或与 table 交互)。
我可以通过为 MouseEvent.MOUSE_CLICKED
和 event.consume()
放置 addEventFilter()
来过滤掉第一个鼠标按钮的点击。然后我可以按我想要的方式处理鼠标单击。
然而,当我右键单击单元格时,table 中的 selection 更改为该行。我已经尝试在单元格、table、行、列上放置事件过滤器,但似乎无法过滤右键单击 selection 更改。请注意 canvas 小部件设置为鼠标透明。
这是一个使用标准地址簿示例的示例,只是我用 canvas 小部件替换了电子邮件列。
package sample;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Main extends Application {
private TableView<Person> table = new TableView<Person>();
private ContextMenu menu = new ContextMenu();
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")
);
class MyTableCell extends TableCell<Person, String> {
public MyTableCell(ContextMenu menu) {
super();
setContextMenu(menu);
}
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(item);
setGraphic(null);
}
}
class MySpecialCell extends MyTableCell {
Canvas canvas = new Canvas(200.0, 12.0);
public MySpecialCell() {
super(null);
canvas.setMouseTransparent(true);
addEventFilter(MouseEvent.ANY, e -> e.consume());
}
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(null);
if (! empty) {
canvas.getGraphicsContext2D().strokeText(item, 5.0, 10.0);
setGraphic(canvas);
} else {
setGraphic(null);
}
}
}
@Override
public void start(Stage stage) throws Exception{
Scene scene = new Scene(new Group());
stage.setTitle("Table View Sample");
stage.setWidth(450);
stage.setHeight(500);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
menu.getItems().add(new MenuItem("Hello World"));
Callback cellFactory = param -> new MyTableCell(menu);
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName"));
firstNameCol.setCellFactory(cellFactory);
TableColumn lastNameCol = new TableColumn("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("lastName"));
lastNameCol.setCellFactory(cellFactory);
TableColumn emailCol = new TableColumn("Email");
emailCol.setMinWidth(200);
emailCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("email"));
emailCol.setCellFactory(param -> new MySpecialCell());
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
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);
}
}
}
这是使用 Java 8u20。在此示例中,MyTableCell
class 用于前两列中的单元格。它向这些列添加了一个上下文菜单。 MySpecialCell
class 用于最后一列,它在 table 中放置一个 canvas,其中包含电子邮件文本。它还会过分过滤 ANY
鼠标事件。
如果您 运行 这样做,您将看到如果您在前两列中使用鼠标按钮 1,则可以更改 table 中的 selection。第三列中的鼠标按钮 1 不会更改 selection。然而,鼠标按钮 2(鼠标右键)确实 改变了第三列中的select离子。
我希望它不改变 select离子。有人可以提示我在列中使用鼠标右键时如何防止 selection 发生变化吗?
注意:我已经尝试让 canvas 处理鼠标输入(实际上并没有让它对鼠标透明并让它过滤所有鼠标事件)并且 table 仍然改变 select右键单击离子。由于各种原因(数据在哪里,canvas 实际上是一堆 canvas 小部件,在 table 中很难弄清楚鼠标正在与哪个节点交互,等)在我的实际应用程序中,我希望单元格处理鼠标输入。
谢谢!
重复我的评论:我认为这种行为可能是错误而不是功能 - 我希望使用任何 mouseEvent 都会像左(主)按钮一样阻止右(辅助)按钮的正常操作.
是evaluated as a feature:缺少的部分是除了使用 mouseEvents 外还使用 contextMenuEvents,例如
public MySpecialCell() {
super(null);
canvas.setMouseTransparent(true);
addEventFilter(MouseEvent.ANY, e -> e.consume());
// move Jonathan's code from table to cell level:
// need to consume contextMenuEvents as well
addEventFilter(ContextMenuEvent.ANY, e -> e.consume());
}
返回不同意评价:
- 所有鼠标按钮应该一致地消耗所有 mouseEvents
- contextMenuEvent 基本上与选择无关,因此使用它对选择应该没有影响(无论哪种方式)
如果需要在特殊列上通过键盘触发上下文菜单,则可能仍然需要下面的 hack,但没有进一步挖掘。
无论如何,一个快速的 hack-around 是用自定义实现替换默认行为。合作者
- 覆盖 doSelect 的自定义 TableCellBehaviour 在接收到辅助按钮事件时不执行任何操作
- 自定义 TableCellSkin:仅需插入自定义行为 - 需要扩展 TableCellSkinBase(并从 TableCellSkin 中 c&p 实现其抽象方法),因为只有这样我们才有一个采用自定义行为的构造函数
- 让自定义 TableCell 创建自定义外观而不是默认外观
类似的东西(注意:在工作时,它没有经过适当的测试):
class MySpecialCellBehavior extends TableCellBehavior {
public MySpecialCellBehavior(TableCell control) {
super(control);
}
@Override
protected void doSelect(double x, double y, MouseButton button,
int clickCount, boolean shiftDown, boolean shortcutDown) {
// do nothing on secondary button
if (button == MouseButton.SECONDARY) return;
super.doSelect(x, y, button, clickCount, shiftDown, shortcutDown);
}
}
class MySpecialCellSkin extends TableCellSkinBase {
private final TableColumn tableColumn;
public MySpecialCellSkin(TableCell tableCell) {
super(tableCell, new MySpecialCellBehavior(tableCell));
// doesn't make a difference
//consumeMouseEvents(true);
this.tableColumn = tableCell.getTableColumn();
super.init(tableCell);
}
@Override protected BooleanProperty columnVisibleProperty() {
return tableColumn.visibleProperty();
}
@Override protected ReadOnlyDoubleProperty columnWidthProperty() {
return tableColumn.widthProperty();
}
}
class MySpecialCell extends MyTableCell {
Canvas canvas = new Canvas(200.0, 12.0);
public MySpecialCell() {
super(null);
canvas.setMouseTransparent(true);
addEventFilter(MouseEvent.ANY, e -> e.consume());
}
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(null);
if (! empty) {
canvas.getGraphicsContext2D().strokeText(item, 5.0, 10.0);
setGraphic(canvas);
} else {
setGraphic(null);
}
}
// create and return the custom skin
@Override
protected Skin<?> createDefaultSkin() {
return new MySpecialCellSkin(this);
}
}