Почему метод isCelected () TableCell дает разные результаты при перемещении вперед или назад в JavaFX8 TableRow? - PullRequest
0 голосов
/ 22 октября 2018

У меня установлен InvalidationListener на selectedProperty() пользовательского TableCell.Я использую его, чтобы изменить возможность редактирования и цвет фона строк на основе логического значения в модели данных.

Я отлаживал таинственное исчезновение цвета строк, когда щелкаю ячейку и затем нажимаю * 1006.* предыдущая ячейка в той же строке и отслеживание ее до значения isSelected() в точке, в которой установлен цвет строки.

Из того, что я мог видеть, isSelected() меняется с false до true при движении вперед подряд, но при движении назад изменяется с true до false.

Почему это так?Разве это не должно быть согласованным в изменениях, о которых он сообщает?

Я пытался использовать selectedProperty.get() вместо isSelected() и ChangeListener вместо InvalidationListener, но получил тот же результат.И то же самое происходит, если я перемещаюсь с помощью клавиатуры, а не мыши.

Вот MVCE, который демонстрирует проблему.Он основан на ответе пользователя Slaw здесь Как установить цвет фона TableRow в зависимости от того, выбран он или нет и значение в модели данных, в JavaFX8 TableView? и ответ пользователя kleopatra здесь TreeTableView: установка строки не редактируемой .

Чтобы воспроизвести поведение, щелкните по второй или третьей ячейке строки.Это изменит цвет согласно матрице, показанной ниже.Затем нажмите на предыдущую ячейку в той же строке.Цвет строки должен исчезнуть и вернуться к значению по умолчанию.Значения isSelected() и selectedProperty() будут выводиться на консоль.

enter image description here

Я использую JavaFX8 (JDK1.8.0_181), NetBeans8.2 и Scene Builder 8.3.

Test45_Listeners.java

package test45_listeners;

import java.util.Arrays;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Test45_Listeners extends Application {

    private final ObservableList<TestModel> olTestModel 
            = FXCollections.observableArrayList(testmodel -> new Observable[] {});

    private Parent createContent() {

        //Initialise the TableView and data
        createDummyData(100);

        TableView<TestModel> table = new TableView<>();

        TableColumn<TestModel, String> colField1 = new TableColumn<>("field1");
        colField1.setCellValueFactory(features -> features.getValue().field1Property());
        colField1.setCellFactory(col -> TestTextCell.createStringTextCell(TestModel::lockedProperty));

        TableColumn<TestModel, String> colField2 = new TableColumn<>("field2");
        colField2.setCellValueFactory(features -> features.getValue().field2Property());
        colField2.setCellFactory(col -> TestTextCell.createStringTextCell(TestModel::lockedProperty));

        TableColumn<TestModel, String> colField3 = new TableColumn<>("field3");
        colField3.setCellValueFactory(features -> features.getValue().field3Property());
        colField3.setCellFactory(col -> TestTextCell.createStringTextCell(TestModel::lockedProperty));

        table.setItems(olTestModel);
        table.getColumns().addAll(Arrays.asList(colField1, colField2, colField3));
        table.setEditable(true);
        table.getSelectionModel().setCellSelectionEnabled(true);
        table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);

        //Set a row factory to set the background colour of any LOCKED row to be yellow
        table.setRowFactory(tv -> {
            TableRow<TestModel> row = new TableRow<TestModel>() {
                @Override
                public void updateItem(TestModel item, boolean empty) {
                    super.updateItem(item, empty);
                    boolean locked = false;
                    if ( getItem() != null ) {
                        locked = getItem().lockedProperty().get();
                        setEditable( ! locked );
                    }

                    if ( !isEmpty() && locked ) {
                        setStyle("-fx-background-color: yellow;");
                    } else {
                        setStyle(null);
                    }

                }
            };
            return row;
        });

        BorderPane content = new BorderPane(table);
        return content;
    }

    private void createDummyData(int count) {

        for ( int i=0; i<count; i++ ) {
            boolean locked = Math.random() >= 0.5;
            olTestModel.add(new TestModel(locked, (locked ? "row LOCKED" : "row NOT locked"),
                    Integer.toString(i), "a"+Integer.toString(i)));
        }

    }

    private class TestModel {

        private final BooleanProperty locked;
        private final StringProperty field1;
        private final StringProperty field2;
        private final StringProperty field3;

        public TestModel(

            boolean locked,
            String field1,
            String field2,
            String field3
        ) {
            this.locked = new SimpleBooleanProperty(locked);
            this.field1 = new SimpleStringProperty(field1);
            this.field2 = new SimpleStringProperty(field2);
            this.field3 = new SimpleStringProperty(field3);
        }

        public boolean getLocked() {return locked.get();}
        public void setLocked(boolean locked) {this.locked.set(locked);}
        public BooleanProperty lockedProperty() {return locked;}

        public String getField1() {return field1.get();}
        public void setField1(String field1) {this.field1.set(field1);}
        public StringProperty field1Property() {return field1;}

        public String getField2() {return field2.get();}
        public void setField2(String field2) {this.field2.set(field2);}
        public StringProperty field2Property() {return field2;}

        public String getField3() {return field3.get();}
        public void setField3(String field3) {this.field3.set(field3);}
        public StringProperty field3Property() {return field3;}

    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(createContent()));
        stage.setTitle("Test");
        stage.setWidth(350);
        stage.show();
    }

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

}

TestTextCell.java

package test45_listeners;

import java.util.function.Function;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WeakChangeListener;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;
import javafx.util.StringConverter;
import javafx.util.converter.DefaultStringConverter;

public class TestTextCell<S, T> extends TableCell<S, T> {

    public final TextField textField = new TextField();
    public final StringConverter<T> converter;

    private BooleanProperty isLockedProperty;

    private final InvalidationListener strongListener = (Observable observable) -> {
        updateStyle();
    };
    private final WeakInvalidationListener weakListener = new WeakInvalidationListener(strongListener);
/*
    public ChangeListener<Boolean> strongListener = (ObservableValue<? extends Boolean> observable, Boolean wasFocused, Boolean isNowFocused) -> {
        updateStyle();
    };
    public final WeakChangeListener<Boolean> weakListener = new WeakChangeListener<Boolean>(strongListener);
*/
    //********************************************************************************************************************* 
    public TestTextCell(StringConverter<T> converter, Function<S, BooleanProperty> methodGetLockedProperty) {

        this.converter = converter;
        setGraphic(textField);
        setContentDisplay(ContentDisplay.TEXT_ONLY);

        itemProperty().addListener((obx, oldItem, newItem) -> {
            if (newItem == null) {
                setText(null);
            } else {
                setText(converter.toString(newItem));
                if ( methodGetLockedProperty != null ) {
                    S datamodel = getTableView().getItems().get(getIndex());
                    isLockedProperty = methodGetLockedProperty.apply(datamodel);
                } else {
                    isLockedProperty = new SimpleBooleanProperty(false);
                }
            }
        });

        //Add the invalidation listener
        selectedProperty().addListener(strongListener);

    }

    //*******************************************************************************************************************    
    public static <S> TestTextCell<S, String> createStringTextCell(Function<S, BooleanProperty> methodGetLockedProperty) {
        return new TestTextCell<S, String>(new DefaultStringConverter(), methodGetLockedProperty);
    }

    //*******************************************************************************************************************    
    @Override
    protected void updateItem(T item, boolean empty) {

        T oldItem = (T) getItem();
        if (oldItem != null) {
            selectedProperty().removeListener(weakListener);
        }

        super.updateItem(item, empty);

        if (item != null) {
            selectedProperty().addListener(weakListener);

            if ( getTableRow() != null ) {
                if (getGraphic() != null) {
                    getGraphic().disableProperty().bind(
                        Bindings.not(getTableRow().editableProperty())
                    );
                }
            }
        }

    }

    @Override
    public void startEdit() {
        if ( ! isLockedProperty.get() ) {
            super.startEdit();
            if (getGraphic() != null) {
                textField.setText(converter.toString(getItem()));
                setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
                getGraphic().requestFocus();
            }
        }
    }

    @Override
    public void cancelEdit() {
        super.cancelEdit();
        setContentDisplay(ContentDisplay.TEXT_ONLY);
    }

    //*******************************************************************************************************************    
    private void updateStyle() {

        System.out.println("in updateStyle(), isLockedProperty = " + isLockedProperty.get() 
                + ", isSelected() = " + isSelected() + ", selectedProperty.get() = " + selectedProperty().get());
        if ( getTableRow() != null ) {
            if ( isLockedProperty.get() && isSelected() ) {
//            if ( isLockedProperty.get() && selectedProperty().get() ) {
                getTableRow().setStyle("-fx-background-color: pink;");
            } else if ( isLockedProperty.get() && ! isSelected()) {
//            } else if ( isLockedProperty.get() && ! selectedProperty().get() ) {
                getTableRow().setStyle("-fx-background-color: yellow;");
            } else if ( ! isLockedProperty.get() && isSelected() ) {
//            } else if ( ! isLockedProperty.get() && selectedProperty().get() ) {
                getTableRow().setStyle("-fx-background-color: #b6e1fc;");
            } else if ( ! isLockedProperty.get() && ! isSelected() ) {
//            } else if ( ! isLockedProperty.get() && ! selectedProperty().get() ) {
                getTableRow().setStyle(null);
            } else {
                throw new AssertionError("how did I get here?");
            }
        }

    }

}

1 Ответ

0 голосов
/ 23 октября 2018

Помимо всех хороших и ценных предложений в комментариях, чтобы ответить на актуальный вопрос «почему» :: метод isCelected () TableCell действительно работает нормально , и в вашем коде есть проблемакоторый неправильно вычисляет требуемую логику.

Чтобы оправдать это, я бы хотел, чтобы вы обновили оператор print в методе updateStyle () до значения ниже

System.out.println(getItem() + " :: in updateStyle(), isLockedProperty = " + isLockedProperty.get() + ", isSelected() = " + isSelected());

Давайте рассмотрим первую строку:

enter image description here

Если я выбираю ячейки слева направо, вывод будет таким, как показано ниже: *

// When field1 is selected
row LOCKED :: in updateStyle(), isLockedProperty = true, isSelected() = true
row LOCKED :: in updateStyle(), isLockedProperty = true, isSelected() = true

// When field2 is selected
row LOCKED :: in updateStyle(), isLockedProperty = true, isSelected() = false
row LOCKED :: in updateStyle(), isLockedProperty = true, isSelected() = false
0 :: in updateStyle(), isLockedProperty = true, isSelected() = true
0 :: in updateStyle(), isLockedProperty = true, isSelected() = true

// When field3 is selected
0 :: in updateStyle(), isLockedProperty = true, isSelected() = false
0 :: in updateStyle(), isLockedProperty = true, isSelected() = false
a0 :: in updateStyle(), isLockedProperty = true, isSelected() = true
a0 :: in updateStyle(), isLockedProperty = true, isSelected() = true

В настоящее время, не беспокойтесь о двойной печати каждого выбора (потому что вы устанавливаете и сильный, и слабый слушатели).Но из вывода мы понимаем, что каждый раз, когда мы выбираем ячейку, для выбранной ранее ячейки устанавливается значение false, что является правильным.Это хорошо работает для обновления вашего стиля, так как «true» всегда идет после «false».

Теперь попробуйте выбрать ячейки справа налево, вывод будет таким, как показано ниже: *

// When field3 is selected
a0 :: in updateStyle(), isLockedProperty = false, isSelected() = true
a0 :: in updateStyle(), isLockedProperty = false, isSelected() = true

// When field2 is selected
0 :: in updateStyle(), isLockedProperty = false, isSelected() = true
0 :: in updateStyle(), isLockedProperty = false, isSelected() = true
a0 :: in updateStyle(), isLockedProperty = false, isSelected() = false
a0 :: in updateStyle(), isLockedProperty = false, isSelected() = false

// When field1 is selected
row NOT locked :: in updateStyle(), isLockedProperty = false, isSelected() = true
row NOT locked :: in updateStyle(), isLockedProperty = false, isSelected() = true
0 :: in updateStyle(), isLockedProperty = false, isSelected() = false
0 :: in updateStyle(), isLockedProperty = false, isSelected() = false

Из вывода очень ясно, что «истина» предшествует «ложь».Другими словами Возможно, JavaFX внутренне всегда обновляет выбор ячеек последовательно слева направо .

Здесь ваш код не работает.Когда вы изменяете выбор справа налево, сначала вызывается обновление левой ячейки, а затем вызывается обновление правой ячейки.А так как ваша правая ячейка не выбрана, вы никогда не увидите желаемый выбранный стиль строки.

Возможное решение ::

Я сноваупомянув об этом, просим учесть все предложения в комментариях .Поскольку вы планируете отказаться от этой идеи, я просто хочу сообщить вам, что это все еще осуществимая реализация.Могут быть и другие лучшие способы, но это одно из возможных решений.

Из приведенного выше анализа очень ясно, что получить желаемое поведение, полагаясь на выбор ячейки, не является подходящим решением.Я бы порекомендовал выполнить все стили строк в самой фабрике строк.

Конечно, для этого вам понадобится новый BooleanProperty в элементе таблицы, чтобы вы знали, выбрана строка или нет.

Пожалуйста, внесите следующие изменения в текущий код:

1) Закомментируйте метод updateStyle () в TestTextCell.java и избавьтесь от всех слушателей.

2) Добавьте новое свойствов TestModel и соответствующих методах получения и установки.

private final BooleanProperty selected = new SimpleBooleanProperty();

3) Добавьте прослушиватель selectedItem tableView для обновления выбора элементов в модели.

table.getSelectionModel().selectedItemProperty().addListener((obs, oldItem, newItem) -> {
            if (oldItem != null) {
                oldItem.setSelected(false);
            }
            if (newItem != null) {
                newItem.setSelected(true);
            }
        });

4) Обновите реализацию фабрики строкниже:

//Set a row factory to set the background colour of any LOCKED row to be yellow
        table.setRowFactory(tv -> {
            TableRow<TestModel> row = new TableRow<TestModel>() {
                private final ChangeListener<Boolean> listener = (o, v, newValue) -> updateStyle();

                {
                    itemProperty().addListener((obs, oldItem, newItem) -> {
                        if(oldItem!=null) {
                            oldItem.selectedProperty().removeListener(listener);
                        }
                        if(newItem!=null) {
                            newItem.selectedProperty().addListener(listener);
                        }
                    });
                }

                @Override
                public void updateItem(TestModel item, boolean empty) {
                    super.updateItem(item, empty);
                    if (getItem() != null) {
                        setEditable(!getItem().getLocked());
                    } else {
                        setEditable(false);
                    }
                    updateStyle();
                }

                private void updateStyle(){
                    if(getItem()!=null) {
                        boolean isLocked = getItem().getLocked();
                        boolean isSelected = getItem().isSelected();
                        if (isLocked) {
                            if (isSelected) {
                                setStyle("-fx-background-color: pink;");
                            } else {
                                setStyle("-fx-background-color: yellow;");
                            }
                        } else {
                            if (isSelected) {
                                setStyle("-fx-background-color: #b6e1fc;");
                            } else {
                                setStyle("-fx-background-color: transparent;");
                            }
                        }
                    }else{
                        setStyle("-fx-background-color: transparent;");
                    }
                }
            };
            return row;
        });

Буду очень признателен за любые исправления в моем понимании.

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