JavaFX TableView过滤器鼠标右键选择

时间:2015-02-06 22:42:36

标签: javafx javafx-8

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

我正在开发一个应用程序,其中我在树表中有多个列,其中一列使用画布小部件来显示自定义图形(波形)。图形列需要能够通过鼠标与各种原因(设置标记,缩放等)进行交互。因此,我不希望鼠标按钮选择表中的行(或与表交互)。

我可以使用第一个鼠标按钮过滤掉点击,为addEventFilter()添加MouseEvent.MOUSE_CLICKED event.consume()。然后我可以按照我想要的方式处理鼠标。

然而,当我右键单击单元格时,表格中的选择将更改为该行。我已经尝试在单元格,表格,行,列上放置事件过滤器,似乎没有任何工作来过滤右键单击选择更改。请注意,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类用于前两列中的单元格。它为这些列添加了一个上下文菜单。 MySpecialCell类用于最后一列,它将画布放在表中,其中包含电子邮件文本。它也会过度使用并过滤ANY鼠标事件。

如果您运行此选项,您将看到如果在前两列中使用鼠标按钮1,则可以更改表格中的选择。第三列中的鼠标按钮1不会更改选择。但是,鼠标按钮2(鼠标右键) DOES 会更改第三列中的选择。

我希望它不会改变选择。当有人在列中使用鼠标右键时,有人可以给我一个关于如何防止选择更改的提示吗?

注意:我已尝试使用画布处理鼠标输入(实际上没有使鼠标透明并使其过滤所有鼠标事件),并且表格仍然会在右键单击时更改选择。出于各种原因(数据是这样的,画布实际上是一堆画布小部件的事实,难以确定鼠标在表格中与哪些节点进行交互等)在我的实际应用程序中我希望单元格能够处理鼠标输入。

谢谢!

1 个答案:

答案 0 :(得分:1)

重复我的评论:我认为行为可能是一个错误,而不是一个功能 - 我希望消耗任何mouseEvent会阻止右(辅助)按钮与左(主)按钮正常操作相同。 / p>

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基本上与选择无关,所以消费它应该对选择没有影响(无论哪种方式)

如果需要特殊列上的键盘触发contextMenu,可能仍然需要下面的黑客攻击,但是没有深入挖掘。

无论如何,快速解决方法是通过自定义实现替换默认行为。合作者

  • 自定义TableCellBehaviour,它会覆盖doSelect,以便在接收辅助按钮事件时不执行任何操作
  • 自定义TableCellSkin:只需要插入自定义行为 - 需要扩展TableCellSkinBase(以及从TableCellSkin实现其抽象方法),因为只有这样我们才有了构造函数来实现我们的自定义行为
  • 让自定义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);
    }

}