JavaFX TableView 过滤鼠标右键选择

JavaFX TableView Filter Right Mouse Button Selection

当我在 JavaFX TreeTableView 上按下鼠标右键时,它 select 就是我单击的节点。我能理解为什么在大多数情况下这可能是可取的,但并非在所有情况下都如此。

我正在开发一个应用程序,其中我在树 table 中有多个列,其中一列使用 canvas 自定义图形(波形)小部件。由于各种原因(设置标记、缩放等),图形列需要能够与鼠标进行交互。因此,我不希望鼠标按钮 select table 中的行(或与 table 交互)。

我可以通过为 MouseEvent.MOUSE_CLICKEDevent.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);
    }

}