JavaFX ListView 单元格工厂项渲染完成事件
JavaFX ListView cell factory item rendering complete event
任务:
检查项目是否在 ListView 的可见区域。
解法:
我有包含要呈现的项目的 JavaFX ListView。为了确定哪些项目在 ListView 的可见区域中,我实现了单元格工厂,它计算向用户显示的项目数。
所以,基本上,我需要做的是:
1. 添加项目
2.检查是否在ListView中可见
问题:
为了计算item,item添加线程(调用线程)必须等待cell factory完成item添加操作和渲染。但是,我不知道如何实现它,因为调用线程不知道 JavaFX UI 线程何时使用内部 Quantum Toolkit 机制完成渲染。
细胞工厂项目在 JavaFX 内的单独线程中呈现,无法与之同步。
添加粗略调用线程延迟解决了一个问题,该问题清楚地表明线程同步问题,但我需要更优雅、更清晰的解决方案。
public class MessengerServiceContext {
@Override
public void messageReceived(final MessageReceivedEvent messageReceivedEvent) {
...
//Calling thread method
messengerServiceControl.receiveMessage(messengerMessageData);
//Main thread is paused for several seconds to wait JavaFX UI threads.
//Ugly and erroneous
//Demonstrates the cause of the problem
try {
Thread.sleep(2000);
} catch (InterruptedException ex) { Logger.getLogger(MessengerServiceControl.class.getName()).log(Level.SEVERE, null, ex);
}
if (!messengerServiceControl.getMessageElementControlVisibility(messengerMessageData)) {
int newMessagesCount = getNewMessagesCount().get();
getNewMessagesCount().set(++newMessagesCount);
}
}
}
public class MessengerServiceControl implements Initializable {
...
private TrackingListCellFactory<MessageElementControl> messengerOutputWindowListViewCellFactory;
...
//Calling (message processing) thread method which inserts ListView item
public void receiveMessage(final MessengerMessageData messengerMessageData) {
//Calling MessengerServiceControl model method to insert items in ListView using JavaFX UI thread
MessageElementControl messageElementControl = model.createMessage(messengerMessageData, false);
//Tried scene and parent property
messageElementControl.sceneProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
if (!getMessageElementControlVisibility(messengerMessageData)) {
int newMessagesCount = getNewMessagesCount().get();
getNewMessagesCount().set(++newMessagesCount);
}
}
}
boolean getMessageElementControlVisibility(final MessengerMessageData messengerMessageData) {
return messengerOutputWindowListViewCellFactory.getItemVisibility(messengerMessageData);
}
//Cell factory class which is responsible for items rendering:
private static class TrackingListCellFactory<T extends MessageElementControl> implements Callback<ListView<T>, ListCell<T>> {
//Items which have cells visible to the user
private final Set<T> visibleItems = new HashSet();
TrackingListCellFactory() {
}
boolean getItemVisibility(final MessengerMessageData messengerMessageData) {
synchronized (this) {
Optional<T> messageElementControlOptional = visibleItems.stream().filter((item) -> {
return item.getMessageData().getMessageCreatedDate().isEqual(messengerMessageData.getMessageCreatedDate());
}).findFirst();
return messageElementControlOptional.isPresent();
}
}
@Override
public ListCell<T> call(ListView<T> param) {
//Create cell that displays content
ListCell<T> cell = new ListCell<T>() {
@Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) {
setGraphic(item);
}
}
};
//Add and remove item when cell is reused for different item
cell.itemProperty().addListener((observable, oldItem, newItem) -> {
synchronized (TrackingListCellFactory.this) {
if (oldItem != null) {
visibleItems.remove(oldItem);
}
if (newItem != null) {
visibleItems.add(newItem);
}
}
});
//Update set when bounds of item change
ChangeListener<Object> boundsChangeHandler = (observable, oldValue, newValue) -> {
synchronized (TrackingListCellFactory.this) {
T item = cell.getItem();
if (item != null) {
visibleItems.add(item);
}
}
});
//Must update either if cell changes bounds, or if cell moves within scene (e.g.by scrolling)
cell.boundsInLocalProperty().addListener(boundsChangeHandler);
cell.localToSceneTransformProperty().addListener(boundsChangeHandler);
return cell;
}
}
}
经过几天的跨线程管理处理后,我得出结论,解决问题的最佳方法确实是将计算逻辑移动到单元工厂本身内部,从而在 UI 线程中完成所有工作。所以,基本上它就像一个魅力:
//Add and remove item when cell is reused for different item
final ChangeListener<T> itemChangedEventHandler = (observable, oldValue, newValue) -> {
synchronized (TrackingListCellFactory.this) {
if (oldValue != null) {
visibleItems.remove(oldValue);
}
if (newValue != null) {
visibleItems.add(newValue);
updateMessengerServiceControlModel(newValue, MessageStatus.MessageStatusEnum.SEEN);
}
}
};
//Update set when bounds of item change
final ChangeListener<Object> boundsChangedHandler = (observable, oldValue, newValue) -> {
synchronized (TrackingListCellFactory.this) {
T item = cell.getItem();
if (item != null) {
visibleItems.add(item);
updateMessengerServiceControlModel(item, MessageStatus.MessageStatusEnum.SEEN);
}
}
};
cell.itemProperty().addListener(itemChangedEventHandler);
//Must update either if cell changes bounds, or if cell moves within scene (e.g. by scrolling):
cell.boundsInLocalProperty().addListener(boundsChangedHandler);
cell.localToSceneTransformProperty().addListener(boundsChangedHandler);
return cell;
恕我直言,这是比使用项目索引等更干净、更优雅的解决方案。
如果不从另一个线程读取可见项,则可以删除同步块。这将提高工厂绩效。
任务: 检查项目是否在 ListView 的可见区域。
解法: 我有包含要呈现的项目的 JavaFX ListView。为了确定哪些项目在 ListView 的可见区域中,我实现了单元格工厂,它计算向用户显示的项目数。
所以,基本上,我需要做的是:
1. 添加项目
2.检查是否在ListView中可见
问题: 为了计算item,item添加线程(调用线程)必须等待cell factory完成item添加操作和渲染。但是,我不知道如何实现它,因为调用线程不知道 JavaFX UI 线程何时使用内部 Quantum Toolkit 机制完成渲染。 细胞工厂项目在 JavaFX 内的单独线程中呈现,无法与之同步。
添加粗略调用线程延迟解决了一个问题,该问题清楚地表明线程同步问题,但我需要更优雅、更清晰的解决方案。
public class MessengerServiceContext {
@Override
public void messageReceived(final MessageReceivedEvent messageReceivedEvent) {
...
//Calling thread method
messengerServiceControl.receiveMessage(messengerMessageData);
//Main thread is paused for several seconds to wait JavaFX UI threads.
//Ugly and erroneous
//Demonstrates the cause of the problem
try {
Thread.sleep(2000);
} catch (InterruptedException ex) { Logger.getLogger(MessengerServiceControl.class.getName()).log(Level.SEVERE, null, ex);
}
if (!messengerServiceControl.getMessageElementControlVisibility(messengerMessageData)) {
int newMessagesCount = getNewMessagesCount().get();
getNewMessagesCount().set(++newMessagesCount);
}
}
}
public class MessengerServiceControl implements Initializable {
...
private TrackingListCellFactory<MessageElementControl> messengerOutputWindowListViewCellFactory;
...
//Calling (message processing) thread method which inserts ListView item
public void receiveMessage(final MessengerMessageData messengerMessageData) {
//Calling MessengerServiceControl model method to insert items in ListView using JavaFX UI thread
MessageElementControl messageElementControl = model.createMessage(messengerMessageData, false);
//Tried scene and parent property
messageElementControl.sceneProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
if (!getMessageElementControlVisibility(messengerMessageData)) {
int newMessagesCount = getNewMessagesCount().get();
getNewMessagesCount().set(++newMessagesCount);
}
}
}
boolean getMessageElementControlVisibility(final MessengerMessageData messengerMessageData) {
return messengerOutputWindowListViewCellFactory.getItemVisibility(messengerMessageData);
}
//Cell factory class which is responsible for items rendering:
private static class TrackingListCellFactory<T extends MessageElementControl> implements Callback<ListView<T>, ListCell<T>> {
//Items which have cells visible to the user
private final Set<T> visibleItems = new HashSet();
TrackingListCellFactory() {
}
boolean getItemVisibility(final MessengerMessageData messengerMessageData) {
synchronized (this) {
Optional<T> messageElementControlOptional = visibleItems.stream().filter((item) -> {
return item.getMessageData().getMessageCreatedDate().isEqual(messengerMessageData.getMessageCreatedDate());
}).findFirst();
return messageElementControlOptional.isPresent();
}
}
@Override
public ListCell<T> call(ListView<T> param) {
//Create cell that displays content
ListCell<T> cell = new ListCell<T>() {
@Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) {
setGraphic(item);
}
}
};
//Add and remove item when cell is reused for different item
cell.itemProperty().addListener((observable, oldItem, newItem) -> {
synchronized (TrackingListCellFactory.this) {
if (oldItem != null) {
visibleItems.remove(oldItem);
}
if (newItem != null) {
visibleItems.add(newItem);
}
}
});
//Update set when bounds of item change
ChangeListener<Object> boundsChangeHandler = (observable, oldValue, newValue) -> {
synchronized (TrackingListCellFactory.this) {
T item = cell.getItem();
if (item != null) {
visibleItems.add(item);
}
}
});
//Must update either if cell changes bounds, or if cell moves within scene (e.g.by scrolling)
cell.boundsInLocalProperty().addListener(boundsChangeHandler);
cell.localToSceneTransformProperty().addListener(boundsChangeHandler);
return cell;
}
}
}
经过几天的跨线程管理处理后,我得出结论,解决问题的最佳方法确实是将计算逻辑移动到单元工厂本身内部,从而在 UI 线程中完成所有工作。所以,基本上它就像一个魅力:
//Add and remove item when cell is reused for different item
final ChangeListener<T> itemChangedEventHandler = (observable, oldValue, newValue) -> {
synchronized (TrackingListCellFactory.this) {
if (oldValue != null) {
visibleItems.remove(oldValue);
}
if (newValue != null) {
visibleItems.add(newValue);
updateMessengerServiceControlModel(newValue, MessageStatus.MessageStatusEnum.SEEN);
}
}
};
//Update set when bounds of item change
final ChangeListener<Object> boundsChangedHandler = (observable, oldValue, newValue) -> {
synchronized (TrackingListCellFactory.this) {
T item = cell.getItem();
if (item != null) {
visibleItems.add(item);
updateMessengerServiceControlModel(item, MessageStatus.MessageStatusEnum.SEEN);
}
}
};
cell.itemProperty().addListener(itemChangedEventHandler);
//Must update either if cell changes bounds, or if cell moves within scene (e.g. by scrolling):
cell.boundsInLocalProperty().addListener(boundsChangedHandler);
cell.localToSceneTransformProperty().addListener(boundsChangedHandler);
return cell;
恕我直言,这是比使用项目索引等更干净、更优雅的解决方案。 如果不从另一个线程读取可见项,则可以删除同步块。这将提高工厂绩效。