ComboBox

时间:2016-08-09 11:49:59

标签: performance javafx combobox tableview

我创建了一个动态ComboBox(在我的TableView' CellFactory类中初始化)来显示调用列表。它需要从ID TableColumn中读取ID号,然后从DB表中获取与该ID匹配的调用。一切顺利,但由于TableView没有完全初始化,而我为我的表设置了CellFactory,我无法在运行时读取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代码段:

    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秒......

1 个答案:

答案 0 :(得分:0)

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

这里棘手的部分是单元格可以经常重复使用,如果在后台线程中启动数据库调用,则可以在前面的数据库调用完成之前使用新项目再次调用updateItem(...)。所以你需要能够取消现有的电话。 javafx.concurrent.Service类具有此功能,但我从未在此上下文中使用它。

这只会产生很小的差异,但每次调用ImageView时都无需创建新的updateItem()。创建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时设置文本,而不是在所有情况下更新标签,我在代码中的注释中指出了这一点。最好每个单元格创建一次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));
    }

    // ...
}