【问题标题】:JavaFX ListView cell factory item rendering complete eventJavaFX ListView 单元工厂项目渲染完成事件
【发布时间】:2017-11-12 20:26:23
【问题描述】:

任务: 检查项目是否在 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;                                               
             }
         }
     }

【问题讨论】:

  • 看看stackoverflow.com/questions/46474385/…,它对表格视图行有类似的功能。
  • 感谢您的回答。我对您提供的示例进行了快速代码审查,它显示了与 TableView 使用的几乎相同的技术。但是,我的问题在于多线程问题而不是计算逻辑。所以,基本上,在调用线程中调用 getItemVisibility() 工厂方法之前,我需要确保在工厂中添加并渲染了项目。
  • “最小的、完整的、可验证的例子”的哪一部分很难理解,你无法提供;)
  • 我有一个问题作为回报。这个主题的哪一部分对你来说太难理解了,需要我提供更多的“最小的、完整的、可验证的例子”?

标签: java multithreading listview javafx factory


【解决方案1】:

在对跨线程管理进行了几天的处理后,我得出结论,解决该问题的最佳方法确实是将计算逻辑移至单元工厂本身,从而在 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;

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-18
    • 1970-01-01
    • 1970-01-01
    • 2013-11-10
    • 2013-06-30
    • 2017-03-03
    相关资源
    最近更新 更多