Взаимодействие с пользовательским узлом CellFactory добавляет строку в список выбора TableView? - PullRequest
2 голосов
/ 09 апреля 2019

У меня есть TableView с CellFactory, который помещает ComboBox в один из столбцов. TableView имеет включенный SelectionMode.MULTIPLE, но он действует странно с ячейкой ComboBox.

Когда пользователи нажимают ComboBox, чтобы выбрать значение, эта строка добавляется в список выбранных строк. Вместо этого нажатие на ComboBox должно либо выбрать эту строку и отменить выбор всех остальных (если не удерживать клавишу CTRL), либо вообще не выбирать строку, а только разрешить взаимодействие с ComboBox.

Я не уверен, как этого добиться.

Вот полный пример, демонстрирующий проблему:

import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;

enum Manufacturer {
    HP, DELL, LENOVO, ASUS, ACER;
}

public class TableViewSelectionIssue extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

        // Simple Interface
        VBox root = new VBox(10);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10));

        // Simple TableView
        TableView<ComputerPart> tableView = new TableView<>();
        TableColumn<ComputerPart, Manufacturer> colManufacturer = new TableColumn<>("Manufacturer");
        TableColumn<ComputerPart, String> colItem = new TableColumn<>("Item");
        tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

        colManufacturer.setCellValueFactory(t -> t.getValue().manufacturerProperty());
        colItem.setCellValueFactory(t -> t.getValue().itemNameProperty());

        tableView.getColumns().addAll(colManufacturer, colItem);

        // CellFactory to display ComboBox in colManufacturer
        colManufacturer.setCellFactory(param -> new ManufacturerTableCell(colManufacturer, FXCollections.observableArrayList(Manufacturer.values())));

        // Add sample items
        tableView.getItems().addAll(
                new ComputerPart("Keyboard"),
                new ComputerPart("Mouse"),
                new ComputerPart("Monitor"),
                new ComputerPart("Motherboard"),
                new ComputerPart("Hard Drive")
        );

        root.getChildren().add(tableView);

        // Show the stage
        primaryStage.setScene(new Scene(root));
        primaryStage.setTitle("Sample");
        primaryStage.show();
    }
}

class ComputerPart {

    private final ObjectProperty<Manufacturer> manufacturer = new SimpleObjectProperty<>();
    private final StringProperty itemName = new SimpleStringProperty();

    public ComputerPart(String itemName) {
        this.itemName.set(itemName);
    }

    public Manufacturer getManufacturer() {
        return manufacturer.get();
    }

    public void setManufacturer(Manufacturer manufacturer) {
        this.manufacturer.set(manufacturer);
    }

    public ObjectProperty<Manufacturer> manufacturerProperty() {
        return manufacturer;
    }

    public String getItemName() {
        return itemName.get();
    }

    public void setItemName(String itemName) {
        this.itemName.set(itemName);
    }

    public StringProperty itemNameProperty() {
        return itemName;
    }
}

class ManufacturerTableCell extends TableCell<ComputerPart, Manufacturer> {
    private final ComboBox<Manufacturer> cboStatus;

    ManufacturerTableCell(TableColumn<ComputerPart, Manufacturer> column, ObservableList<Manufacturer> items) {
        this.cboStatus = new ComboBox<>();
        this.cboStatus.setItems(items);
        this.cboStatus.setConverter(new StringConverter<Manufacturer>() {
            @Override
            public String toString(Manufacturer object) {
                return object.name();
            }

            @Override
            public Manufacturer fromString(String string) {
                return null;
            }
        });

        this.cboStatus.disableProperty().bind(column.editableProperty().not());
        this.cboStatus.setOnShowing(event -> {
            final TableView<ComputerPart> tableView = getTableView();
            tableView.getSelectionModel().select(getTableRow().getIndex());
            tableView.edit(tableView.getSelectionModel().getSelectedIndex(), column);
        });

        this.cboStatus.valueProperty().addListener((observable, oldValue, newValue) -> {
            if (isEditing()) {
                commitEdit(newValue);
                column.getTableView().refresh();

            }
        });
        setContentDisplay(ContentDisplay.GRAPHIC_ONLY);

    }

    @Override
    protected void updateItem(Manufacturer item, boolean empty) {
        super.updateItem(item, empty);

        setText(null);
        if (empty) {
            setGraphic(null);
        } else {
            this.cboStatus.setValue(item);
            this.setGraphic(this.cboStatus);
        }
    }
}

Пример начинается с предсказуемого пользовательского интерфейса:

screenshot 1

Однако при взаимодействии с ComboBox в столбце Производитель выбирается соответствующая строка. Это ожидается для первого ряда, но не отменяется при взаимодействии с другим ComboBox.

screenshot 2


Как можно предотвратить последующее взаимодействие с ComboBox из , добавляющим к выбранным строкам? Он должен вести себя как любой другой щелчок на TableRow, не так ли?

Я использую JDK 8u161.


Примечание: Я понимаю, что доступен класс ComboBoxTableCell , но я не смог найти примеров того, как правильно его использовать; это не имеет отношения к моему вопросу, хотя, если ComboBoxTableCell ведет себя иначе.

1 Ответ

2 голосов
/ 09 апреля 2019

Поскольку вам нужна ячейка «всегда редактируемая», ваша реализация должна вести себя больше как CheckBoxTableCell, чем ComboBoxTableCell.Первый обходит обычный механизм редактирования TableView.В качестве предположения, я думаю, что вы используете обычный механизм редактирования, который вызывает проблемы с выделением - почему именно, я не уверен.

Изменение вашего ManufactureTableCell, чтобы оно больше походило на CheckBoxTableCell, это 'выглядело бы что-то вроде:

class ManufacturerTableCell extends TableCell<ComputerPart, Manufacturer> {
    private final ComboBox<Manufacturer> cboStatus;
    private final IntFunction<Property<Manufacturer>> extractor;

    private Property<Manufacturer> property;


    ManufacturerTableCell(IntFunction<Property<Manufacturer>> extractor, ObservableList<Manufacturer> items) {
        this.extractor = extractor;

        this.cboStatus = new ComboBox<>();
        this.cboStatus.setItems(items);
        // removed StringConverter for brevity (accidentally)
        setContentDisplay(ContentDisplay.GRAPHIC_ONLY);

        cboStatus.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
            if (event.isShortcutDown()) {
                getTableView().getSelectionModel().select(getIndex(), getTableColumn());
            } else {
                getTableView().getSelectionModel().clearAndSelect(getIndex(), getTableColumn());
            }
            event.consume();
        });
    }

    @Override
    protected void updateItem(Manufacturer item, boolean empty) {
        super.updateItem(item, empty);

        setText(null);
        clearProperty();
        if (empty) {
            setGraphic(null);
        } else {
            property = extractor.apply(getIndex());
            Bindings.bindBidirectional(cboStatus.valueProperty(), property);
            setGraphic(cboStatus);
        }
    }

    private void clearProperty() {
        setGraphic(null);
        if (property != null) {
            Bindings.unbindBidirectional(cboStatus.valueProperty(), property);
        }
    }
}

И вы бы установили его так:

// note you could probably share the same ObservableList between all cells
colManufacturer.setCellFactory(param ->
    new ManufacturerTableCell(i -> tableView.getItems().get(i).manufacturerProperty(),
            FXCollections.observableArrayList(Manufacturer.values())));

Как уже упоминалось, вышеприведенная реализация обходит обычный механизм редактирования;он связывает значение ComboBox непосредственно со свойством элемента модели.Реализация также добавляет обработчик MOUSE_PRESSED к ComboBox, который выбирает строку (или ячейку, если используется выбор ячейки) в зависимости от ситуации.К сожалению, я не совсем понимаю, как реализовать выделение, когда Shift выключен, поэтому обрабатываются только «Нажать» и «Ярлык + Нажать».

Вышеприведенное работает так, как я считаю, вы хотитечтобы, но я мог только проверить это с помощью JavaFX 12.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...