javafx Tableview单元索引/编辑带有重复条目的错误

时间:2016-01-27 20:30:14

标签: listview indexing javafx-8

我一直在搞乱javafx。我遇到了一些我无法解释的事情。我正在尝试使用字符串值创建可编辑的Tableview。一切顺利,直到我开始进行biger测试。在我使用小列表进行测试之前,它没有超出列表的可见限制。虽然经过了大考验,但这个名单远远超过它在窗口中显示的范围。

问题是我在Tableview的开头给了一定数量的单元格另一种颜色,它完美地运行,直到你开始向下滚动。似乎重复使用的单元格没有足够快地更新,因为有些单元格被赋予了错误的颜色。当向下滚动,从而更新更多单元格时,“错误的彩色单元格”的模式也会发生变化。有趣的是,这些“失败”是一致的!

(顺便说一句,我使用 this.getIndex()检查单元格索引,然后用正确的颜色语法调用this.setStyle()。)

我注意到的第二件事是,当我开始编辑单元格时,虽然不要停止编辑并向下滚动。有多个单元格同时被编辑。 (也是一致的模式)并且它不是一个视觉故障!我调试了它,似乎editing属性也没有很好地更新。

最后一点,我在Cell#updateItem()方法中更新了TableCells的图形。 (以及Cell#startEdit()Cell#cancelEdit()中的编辑部分。)

我的问题是,这是一个错误吗?如果是这样,有没有解决方法? 如果您有任何想法/建议/解决方案,请发表评论。

提前致谢!

编辑01:

首先我需要合适,因为问题是使用tableView,而不是listView。话虽这么说,我做了一个示例类,它完全显示了我试图描述的问题。如果有任何建议/解决方案,请告诉我们!

package testpacket;

import java.util.Arrays;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;


public class TableViewTest extends Application
{
    private static ObservableList<String> exampleList = FXCollections.observableArrayList();
    private static String[] testArray = new String[50];

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

    @Override
    public void start(Stage primaryStage) throws Exception
    {
        // basic ui setup
        AnchorPane parent = new AnchorPane();
        Scene scene = new Scene(parent);
        primaryStage.setScene(scene);

        //fill backinglist with data
        Arrays.fill(testArray, 0, testArray.length, "Test String");
        exampleList.setAll(Arrays.asList(testArray));

        //create a basic tableView
        TableView<String> listView = new TableView<String>();
        TableColumn<String, String> column = new TableColumn<String, String>();
        column.setCellFactory(E -> new TableCellTest<String, String>());
        column.setCellValueFactory(E -> new SimpleStringProperty(E.getValue()));

        // set listViews' backing list
        listView.getItems().addAll(exampleList);
        // listView.setItems(exampleList); it doesnt rely on the backing list, either way it is showing this bug.


        listView.getColumns().clear();
        listView.getColumns().add(column);
        parent.getChildren().add(listView);

        primaryStage.show();
    }

    public static class TableCellTest<S, T> extends TableCell<S, T>
    {

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

            // dipslays cells' value
            this.setText((String)this.getItem());

            // checks cells index and set its color.
            if(this.getIndex() < 12)
                this.setStyle("-fx-background-color: rgba(253, 255, 150, 0.4);");
            else this.setStyle("");
        }
    }
}

编辑02:

@James_D

非常感谢您的回答,实际上重复的项目是问题所在。不知何故,javafx正在检查内容是否已更改。不幸的是解决方案:
testArray[i] = new String("Test String");
不起作用,这是不幸的,因为我的系统严重依赖于重复的条目。试图使它们成为独特的几乎不可能完美地处理。

所以我真的希望有可能以某种方式覆盖'等于检查'。我一直在挖掘,似乎所有这些方法/字段都是私有的。所以改变这种做法是不可能的,除非我重新创建了一堆javafx类。

所以,如果仍然有关于这个的建议/解决方案,请随时发布!

在下一个问题上,编辑属性也没有足够好地更新。在onUpdateItem()方法中,我检查当前单元格是否正在编辑并继续更新该单元格以使其保持编辑模式,即使向下滚动(从而将编辑索引滚出窗口)也是如此。我做了这个例子,你可以自己看看。

请注意,当您删除编辑系统的onUpdateItem()部分时,单元格将在viewWindow外滚动后退出编辑模式。

package testpacket;

import java.util.Arrays;

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;


public class TableViewTest extends Application
{
    private static ObservableList<String> exampleList = FXCollections.observableArrayList();
    private static String[] testArray = new String[50];

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

    @Override
    public void start(Stage primaryStage) throws Exception
    {
        // basic ui setup
        AnchorPane parent = new AnchorPane();
        Scene scene = new Scene(parent);
        primaryStage.setScene(scene);

        //fill backinglist with data
        for(int i = 0 ; i < testArray.length; i++)
            testArray[i] = "Test String" + i;

//      Arrays.fill(testArray, 0, testArray.length, new String("Test String"));
        exampleList.setAll(Arrays.asList(testArray));

        //create a basic tableView
        TableView<String> listView = new TableView<String>();
        TableColumn<String, String> column = new TableColumn<String, String>();
        column.setCellFactory(E -> new TableCellTest<String, String>());
        column.setCellValueFactory(E -> new SimpleStringProperty(E.getValue()));
        column.setEditable(true);

        // set listViews' backing list
        listView.getItems().addAll(exampleList);
        listView.setEditable(true);
//      listView.setItems(exampleList); it doesnt rely on the backing list, either way it is showing this bug.


        listView.getColumns().clear();
        listView.getColumns().add(column);
        parent.getChildren().add(listView);

        primaryStage.show();
    }

    public static class TableCellTest<S, T> extends TableCell<S, T>
    {

        protected TextField textField;

        @Override
        public void startEdit()
        {
            super.startEdit();

            if (!isEditable()
                    || (this.getTableView() != null && !this.getTableView().isEditable())
                    || (this.getTableColumn() != null && !this.getTableColumn().isEditable()))
                return;

            if(this.textField == null)
                this.createTextField();

            this.textField.setText((String)this.getItem());
            this.setGraphic(this.textField);
            this.textField.selectAll();
            this.setText(null);
        }

        @Override
        public void cancelEdit()
        {
            super.cancelEdit();
            if (!isEditing())
                return;

            this.setText((String)this.getItem());
            this.setGraphic(null);
        }

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

            if(empty || item == null)
            {
                this.setText(null);
                this.setGraphic(null);
            }
            else
            {
                // I tried this option to check whether a cell represents the correct item, though it doesnt work
                if(this.isEditing())// && this.getTableView().getEditingCell() != null && this.getTableView().getEditingCell().equals(this))
                {
                    if(this.textField != null)
                        this.textField.setText((String)this.getItem());
                    this.setText(null);
                    this.setGraphic(this.textField);
                }
                else
                {
                    this.setText((String)this.getItem());
                    this.setGraphic(null);
                }
            }
        }

        @SuppressWarnings("unchecked")
        public void createTextField()
        {
            this.textField = new TextField();
            this.textField.setOnKeyReleased((EventHandler<? super KeyEvent>)E -> {
                if(E.getCode() == KeyCode.ENTER)
                {
                    this.setItem((T)this.textField.getText());
                    this.commitEdit(this.getItem());
                }
                else if(E.getCode() == KeyCode.ESCAPE)
                    this.cancelEdit();
            });
        }
    }
}

编辑03:

与课堂上提到的不同,这个方法有一个解决方法。请注意,这仅适用于“单细胞编辑模式”! (因为它是TableView支持的唯一模式) 解决方案如下:

if(this.isEditing() && this.getTableView().getEditingCell() != null
     && this.getTableView().getEditingCell().getTableColumn().getCellData(this.getTableView().getEditingCell().getRow()).equals(this.getItem()))
{
    ... do stuff
}

虽然不幸的是它没有解决索引问题。该主题仍然是开放的建议/解决方案。

2 个答案:

答案 0 :(得分:1)

突出显示问题的出现是因为您的表中有相同的元素:即您有Date.prototype.getUTCFullYear()等。如果您更改了表初始化,那么项目不相同(但仍然相等) :

table.getItems().get(0)==table.getItems().get(1)

然后突出显示按预期工作。

要在滚动期间保持编辑状态(包括编辑单元格滚动到视图外的情况),您需要小心区分单元格项目 。您希望保留正在编辑的项目:由于在滚动期间可以重复使用该单元格,这意味着您需要某种方式来跟踪滚动期间正在编辑的项目;然后在// Arrays.fill(testArray, 0, testArray.length, "Test String"); for (int i = 0 ; i < testArray.length; i++) testArray[i] = new String("Test String"); 方法中,您需要检查此项并在必要时“进入编辑模式”。跟踪正在编辑的项目的最简单方法可能是使用行索引。

以下示例显示了执行此操作的方法:

updateItem(...)

答案 1 :(得分:0)

@kleopatra这是您要求的帖子/答案:D。

此时已回答了这两个问题。我将在下面解释这两个解决方案,但首先确认表视图不能处理重复的条目。如果你考虑复制像Windows一样的Windows Excel,这是相当不幸的。例如,在TableView中的行为。

无论如何,这里是索引更新故障的解决方案:如果要复制条目,永远不要使用原始数据类型作为基本类型!使用可观察的值代替!在底部有一个示例类。该类将observableString包含在ObservableProperty内。这将解决索引问题!

编辑部分。只要您只支持“单细胞编辑”,此部分就能够将编辑单元格滚动到视图之外,而不会取消该特定单元格的编辑状态。 (这应该在onUpdateItem()方法内)

if(this.isEditing() && this.getTableView().getEditingCell() != null 
                        && this.getTableView().getEditingCell().getTableColumn().getCellData(
                                this.getTableView().getEditingCell().getRow()).equals(this.getItem()))
    {
        //...update this cell to view its editing state
    }

同样,此解决方案仅在dupedObjectA.equals(dupedObjectB);返回false时才有效,如果使用原始数据类型则不会出现这种情况。

这是一个完整的示例类:

package testpacket;

import javafx.application.Application;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;


public class TableViewTest extends Application
{
    //Backing list of this TableView. I personally use a custom made Maps' entrySet, which is capable of handling duplicated entries.
    //So it would look like:
    //private static ObservableList<MapEntry<SimpleStringProperty>> exampleList;
    private static ObservableList<ObservableValue<SimpleStringProperty>> exampleList = FXCollections.observableArrayList();

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

    @Override
    public void start(Stage primaryStage) throws Exception
    {
        // basic ui setup
        AnchorPane parent = new AnchorPane();
        Scene scene = new Scene(parent);
        primaryStage.setScene(scene);

        //fill backinglist with duplicated data
        for(int i = 0 ; i < 50; i++)
            exampleList.add(new SimpleObjectProperty<SimpleStringProperty>(new SimpleStringProperty("Hello Test")));


        //create a basic tableView
        TableView<ObservableValue<SimpleStringProperty>> listView = new TableView<ObservableValue<SimpleStringProperty>>();
        listView.setEditable(true);

        TableColumn<ObservableValue<SimpleStringProperty>, SimpleStringProperty> column = new TableColumn<ObservableValue<SimpleStringProperty>, SimpleStringProperty>();
        column.setCellFactory(E -> new TableCellTest<ObservableValue<SimpleStringProperty>, SimpleStringProperty>());
        column.setCellValueFactory(E -> E.getValue());
        column.setEditable(true);

        // set listViews' backing list
        listView.setItems(exampleList);


        listView.getColumns().clear();
        listView.getColumns().add(column);
        parent.getChildren().add(listView);

        primaryStage.show();
    }

    public static class TableCellTest<S, T> extends TableCell<S, T>
    {
        // The editing textField. This can be static as only one cell can be edited at the same time.
        protected static TextField textField;

        @Override
        public void startEdit()
        {
            super.startEdit();

            // The same check as in the super#startEdit(), so technically you can call super#startEdit() after this check.
            if (!isEditable()
                    || (this.getTableView() != null && !this.getTableView().isEditable())
                    || (this.getTableColumn() != null && !this.getTableColumn().isEditable()))
                return;

            if(textField == null)
                this.createTextField();


            textField.setText(((SimpleStringProperty)this.getItem()).get());
            this.setGraphic(textField);
            textField.selectAll();
            this.setText(null);
        }

        @Override
        public void cancelEdit()
        {
            // The same check as in the super#cancelEdit()
            if (!this.isEditing())
                return;

            super.cancelEdit();

            this.setText(((SimpleStringProperty)this.getItem()).get());
            this.setGraphic(null);
        }

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

            // checks cells index and set its color.
            if(this.getIndex() < 12)
                this.setStyle("-fx-background-color: rgba(253, 255, 150, 0.4);");
            else this.setStyle("");

            // Checks if visuals need an update.
            if(empty || item == null)
            {
                this.setText(null);
                this.setGraphic(null);
            }
            else
            {
                // These checks are needed to make sure this cell is the specific cell that is in editing mode.
                // Technically this#isEditing() can be left out, as it is not accurate enough at this point.
                if(this.isEditing() && this.getTableView().getEditingCell() != null 
                        && this.getTableView().getEditingCell().getTableColumn().getCellData(
                                this.getTableView().getEditingCell().getRow()).equals(this.getItem()))
                {
                    //change to TextField
                    this.setText(null);
                    this.setGraphic(textField);
                }
                else
                {
                    //change to actual value
                    this.setText(((SimpleStringProperty)this.getItem()).get());
                    this.setGraphic(null);
                }
            }
        }

        @SuppressWarnings("unchecked")
        public void createTextField()
        {
            textField = new TextField();
            textField.setOnKeyReleased((EventHandler<? super KeyEvent>)E -> {
                if(E.getCode() == KeyCode.ENTER)
                {
                    this.setItem((T)new SimpleStringProperty(textField.getText()));
                    this.commitEdit(this.getItem());
                }
                else if(E.getCode() == KeyCode.ESCAPE)
                    this.cancelEdit();
            });
        }
    }
}

我感谢大家和我一起思考,幸运的是,这两个问题都有一个相当简单的解决方法。我希望他们将来会支持重复的条目!