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

重新排列集合而不添加/删除项目?

  •  1
  • Rachel  · 技术社区  · 14 年前

    我有一个TabControl,可以通过拖放tab来重新排列。当前进程从列表中删除一个项目并将其添加到新位置。我在切换选项卡时遇到了一些性能问题,因为选项卡有多复杂,所以找到了一个替代方法来存储呈现的选项卡,并在请求时重新加载它们。我唯一的问题是,当拖放选项卡时,它会重新呈现每个选项卡并导致相同的延迟。有没有办法简单地移动集合中的项而不是添加/删除它?

    否则,有没有办法取消 OnItemsChanged 拖放操作期间的事件?这个过程会影响控件的视觉效果,因此如果是由拖放操作引起的,我需要实际取消add/remove事件(用户也可以正常地添加/删除选项卡)。

    2 回复  |  直到 14 年前
        1
  •  2
  •   Tim Cooper    13 年前

    你试过绑这条绳子吗 TabControl.ItemsSource 到集合视图,然后根据索引对集合视图进行排序?然后,移动逻辑只需更改索引,选项卡项就会相应地排序。

        2
  •  0
  •   Rachel    14 年前

    OnItemsChanged 事件以低于Add代码的调度程序优先级运行Remove代码,因此Add操作有机会取消Remove操作,并重用TabItem的ContentPresenter,而不是呈现新的ContentPresenter。

    我最初的代码是从 here

    它基本上存储TabItem ContentPresenters,因此在切换选项卡时,它使用存储的ContentPresenter,而不是重新绘制新的ContentPresenter。以下是我对OnItemChanged所做的修改,以使拖放操作可以重用旧项,而不是重新绘制新项

    case NotifyCollectionChangedAction.Add:
    case NotifyCollectionChangedAction.Remove:
    
        // Search for recently deleted items caused by a Drag/Drop operation
        if (e.NewItems != null && _deletedObject != null)
        {
            foreach (var item in e.NewItems)
            {
                if (_deletedObject == item)
                {
                    // If the new item is the same as the recently deleted one (i.e. a drag/drop event)
                    // then cancel the deletion and reuse the ContentPresenter so it doesn't have to be 
                    // redrawn. We do need to link the presenter to the new item though (using the Tag)
                    ContentPresenter cp = FindChildContentPresenter(_deletedObject);
                    if (cp != null)
                    {
                        int index = _itemsHolder.Children.IndexOf(cp);
    
                        (_itemsHolder.Children[index] as ContentPresenter).Tag =
                            (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item));
                    }
                    _deletedObject = null;
                }
            }
        }
    
        if (e.OldItems != null)
        {
            foreach (var item in e.OldItems)
            {
    
                _deletedObject = item;
    
                // We want to run this at a slightly later priority in case this
                // is a drag/drop operation so that we can reuse the template
                // Render is good since a normal Removal of an item will run prior to adding a new one
                this.Dispatcher.BeginInvoke(DispatcherPriority.Render,
                    new Action(delegate()
                {
                    if (_deletedObject != null)
                    {
                        ContentPresenter cp = FindChildContentPresenter(_deletedObject);
                        if (cp != null)
                        {
                            this._itemsHolder.Children.Remove(cp);
                        }
                    }
                }
                ));
            }
        }