【问题标题】:JavaFX TableView updateItem slow for ComboBoxComboBox 的 JavaFX TableView updateItem 慢
【发布时间】:2016-12-15 10:49:29
【问题描述】:

我创建了一个动态组合框(在我的 TableView 的 CellFactory 类中初始化)来显示调用列表。它需要从 ID TableColumn 中读取 ID 号,然后从 DB 表中获取与该 ID 匹配的调用。一切顺利,但由于在我为表设置 CellFactory 时 TableView 没有完全初始化,我无法在运行时读取 ID,因此我将此代码移至 updateItem() 方法,设置调用列表单元工厂。

在 updateItem() (MyCellFactory) 中:

 listCallCombo.setCellFactory(listview -> new ImageCallListCell(id_num));

 listCallCombo.setButtonCell(new ImageCallListCell(id_num));

ImageCallListCell:

public class ImageCallListCell extends ListCell<String> {

private Label label = null;
private int id;

ImageCallListCell(int id) {
    setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
    this.id = id;
}

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

    if (item == null || empty) {

        setItem(null);
        setGraphic(null);

    } else {
        setText(item);

        ImageView image = Utils.getImageViewByName("call");


        if (image != null && !item.equals("")) {
            image.setFitHeight(20);
            image.setFitWidth(22);
            String call = "Error";
            if (item.equals("0")) {
                call = "Add Call...";
            } else if (id != 0 && !item.equals("-1")) {

                call = ListCallHandler.getCallFromIDandIndex(id, Integer.parseInt(item));
            } 
            label = new Label(call, image);


        }
        setGraphic(label);
    }
}

}

ListCallHandler 返回一个调用数组,对数据库进行一次性提取。 这样做使我的代码正常工作,但当然每次为每一行设置 ButtonCell 和 CellFactory 都会使我的表格滚动缓慢。

我怎样才能更好地处理这个问题,以获得更好的性能?

提前致谢

编辑

这里是 ListCallHandler 代码 sn-p:

    public static List<Map> getCalls() {
        ResultSet list = DbUpdate.run_query("select * from memo", Utils.DBName);
        List mCalls = new LinkedList();
        try {
            while (list.next()) {
                Map call = new HashMap();
                String callstring = list.getString("Lista Chiamate");
                call.put("id", list.getInt("id"));
                call.put("chiamate", callstring);
                mCalls.add(call);
             }
           } catch (SQLException ex) {
                ...
            }

  public static String getCallFromIDandIndex(int id, int index) {
        List<String> c = ListCallHandler.getCallsFromID(id, true);
        String result = "(No Calls)";
        if (index < c.size()) {
            result = c.get(index);
        }

        return result;
    }

getCallsFromID() 只需运行 getCalls,然后将数据重新组织到一个数组中。

再次感谢您!

编辑 2

getImageViewByName():

 public static ImageView getImageViewByName(String name) {
        return Utils.initImageView("raw/" + name + ".png");
    }

    public static ImageView initImageView(String path) {
        BufferedImage bf = null;
        WritableImage wr = null;

        try {
            bf = ImageIO.read(Utils.class.getResourceAsStream(path));
            if (bf != null) {
                wr = new WritableImage(bf.getWidth(), bf.getHeight());
                PixelWriter pw = wr.getPixelWriter();
                for (int x = 0; x < bf.getWidth(); x++) {
                    for (int y = 0; y < bf.getHeight(); y++) {
                        pw.setArgb(x, y, bf.getRGB(x, y));
                    }
                }
            }
        } catch (IOException ex) {
            System.err.println("ERROR LOADING PICTURE " + ex.getLocalizedMessage());
        }
        return new ImageView(wr);

    }

我设法用一个小技巧让滚动变得不那么痛苦,但它的体验仍然很差,这就是我所做的:

tableview.addEventFilter(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {
            @Override
            public void handle(ScrollEvent scrollEvent) {
                if (scrollEvent.getEventType() == ScrollEvent.SCROLL_FINISHED) {
                    ListCallHandler.canUpdateCalls = true;
                } else if (scrollEvent.getEventType() == ScrollEvent.SCROLL_STARTED) {
                    ListCallHandler.canUpdateCalls = false;
                } else {
                    ListCallHandler.canUpdateCalls = true;
                }
            }
        });

然后我在 ImageCallListCell 中绘制单元格之前检查了 canUpdateCalls 变量。慢慢滚动时,这很好。如果你跳到中间列表,它会挂断 4-5 秒...

【问题讨论】:

  • 图片是不是一直都是一样的(从硬编码的参数到你的Utils方法,好像是这样)。
  • 是的,它是一个电话图标

标签: performance javafx combobox tableview


【解决方案1】:

这有点棘手。如果您进行可能需要时间执行的数据库调用,您应该在后台线程上执行此操作。特别是单元实现中的updateItem(...) 方法被频繁调用,因此您应该尽量减少在该方法中完成的工作量。

这里棘手的部分是单元格可能会被频繁地重用,如果您在后台线程中启动数据库调用,则可以在前面的数据库调用完成之前使用新项目再次调用 updateItem(...)。因此,您需要能够取消现有呼叫。 javafx.concurrent.Service 类具有此功能,尽管我从未在此上下文中使用过它。

这只有很小的区别,但不需要每次调用updateItem() 时都创建一个新的ImageView。创建一次ImageView。从您的代码来看,图像看起来总是相同的,但如果需要,您可以使用setImage(...) 更新图像视图。 (标签也是如此。)

这样的单元实现应该会更好:

public class ImageCallListCell extends ListCell<String> {

    private Label label ;
    private int id;

    private Service<String> dbService ;

    ImageCallListCell(int id) {
        setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
        this.id = id;
        label = new Label();
        ImageView image = Utils.getImageViewByName("call");
        image.setFitHeight(20);
        image.setFitWidth(22);
        label.setGraphic(image);

        dbService = new Service<String>() {
            @Override
            protected Task<String> createTask() {
                return new Task<String>() {
                    @Override
                    protected String call() throws Exception {
                        return ListCallHandler.getCallFromIDandIndex(id, Integer.parseInt(getItem()));
                    }
                };
            }
        };

        dbService.setOnSucceeded(e -> label.setText(dbService.getValue()));
        dbService.setOnFailed(e -> {
            Throwable exc = dbService.getException();
            // log exception, etc
        });
    }

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

        // note this won't do anything as you have ContentDisplay.GRAPHIC_ONLY:
        setText(item);

        // cancel any running database task:
        dbService.cancel();

        if (item == null || empty) {

            setGraphic(null);

        } else {


            if (!item.equals("")) {
                if (item.equals("0")) {
                    label.setText("Add Call...");
                } else if (id != 0 && !item.equals("-1")) {
                    label.setText("Loading from database...");
                    // run new database task in background:
                    dbService.restart();
                } 

            } else {
                // should set label's text to something here...
            }
            setGraphic(label);
        }
    }

}

通过这个实现,当单元格需要联系数据库时,它使用Service 在后台线程上这样做。当数据库调用完成时,服务的onSucceeded 处理程序将更新标签的文本。对dbService.cancel() 的调用可确保没有多个数据库调用的结果会竞争同一个单元格。

您的实现还有一些其他怪癖,例如当您将内容显示设置为GRAPHIC_ONLY 时设置文本,并且在所有情况下都不更新标签,我在代码中的 cmets 中指出了这一点。并且最好每个单元格创建一次 UI 元素,并在 updateItem(...) 方法中修改它们,而不是每次都创建新的 UI 控件。


更新

要注意的另一件事是,您从流中重复加载同一个图像,这是很多不必要的工作(并且会消耗大量不必要的内存)。多个图像视图可以共享同一个图像,因此您可以缓存图像。例如;

public class Utils {

    private static Map<String, Image> imageCache = new WeakHashMap<>();

    private static Image getImage(String name) {
        return imageCache.computeIfAbsent(name, this::readImage);
    }

    private static Image readImage(String name) {
        BufferedImage bf = null ;
        Image img = null ;
        try {
            bf = ImageIO.read(Utils.class.getResourceAsStream("raw/+name+".png"));
            img = SwingFXUtils.toFXImage(bf, null);
        } catch (IOException exc) {
            System.err.println("ERROR LOADING PICTURE " + ex.getLocalizedMessage());
        }
        return img ;
    }

    public static ImageView getImageViewByName(String name) {
        return new ImageView(getImage(name));
    }

    // ...
}

【讨论】:

  • 非常感谢您的快速回答。我根据您的建议优化了我的代码,但我意识到在 Windows 上仍然非常慢(在 Mac 上它快 10 倍甚至更多!)......你能帮我找出原因吗?我用从 ListCallHandler 调用的方法更新 OP
  • 所做的更改是否真的有所作为(即以前在 Mac 上是否较慢)。 (我使用的是 Mac,所以问我为什么你的 windows 机器很慢可能不是一个好主意......;)。)
  • 在 Mac 上我没有注意到明显的差异...我在不同的 Windows 机器上尝试过,结果相同。仅为该组合禁用绘图也可以在 Windows 上快速滚动(看起来不可思议啊哈)。
  • Utils.getImageViewByName(...) 在做什么?
  • 更新了 OP,我还做了一些可怕的小技巧来减少绘图。
猜你喜欢
  • 1970-01-01
  • 2016-06-01
  • 2019-11-22
  • 1970-01-01
  • 2017-07-06
  • 1970-01-01
  • 2019-10-16
  • 1970-01-01
  • 2013-01-17
相关资源
最近更新 更多