我创建了一个动态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秒......
答案 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));
}
// ...
}