代码之家  ›  专栏  ›  技术社区  ›  Andreas

JavaFX ListView单元格工厂项呈现完成事件

  •  0
  • Andreas  · 技术社区  · 7 年前

    任务: 检查项目是否在ListView的可见区域中。

    解决方案: 我有JavaFX ListView,其中包含要渲染的项。为了找出哪些项目在ListView的可见区域中,我实现了计算显示给用户的项目数量的单元工厂。


    1、增加项目
    2.检查是否在ListView中可见。

    问题是: 为了计算项目,项目添加线程(调用线程)必须等待单元工厂完成项目添加操作和渲染。然而,我不知道如何实现它,因为调用线程不知道JavaFX UI线程何时使用内部量子工具包机制完成渲染。 单元格工厂项在JavaFX内的独立线程中呈现,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;                                               
                 }
             }
         }
    
    1 回复  |  直到 7 年前
        1
  •  0
  •   Andreas    7 年前

    经过几天的跨线程管理,我得出结论,解决这个问题的最佳方法确实是将计算逻辑移动到cell factory本身,从而在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;
    

    IMHO,这是比使用项目索引等更干净和优雅的解决方案。 如果未从另一个线程读取可见项,则可以删除同步块。这将提高工厂性能。