views:

33

answers:

2

I have a TabControl that can be rearranged by dragging/dropping the tabs. The current process removes an item from the list and adds it to a new location. I had some performance issues switching tabs because of how complex the tabs are, so found an alternative which stores the rendered tabs and reloads them when requested. My only problem with it is that when dragging/dropping tabs, it re-renders each tab and causes the same delay. Is there a way to simply Move the item in the collection instead of Adding/Removing it?

Or failing that, is there a way to cancel the addition/removal in the OnItemsChanged event during a drag/drop operation? The process affects the visual of the control, so I need to actually cancel the add/remove event if it was caused by a drag/drop operation (users can also add/remove tabs normally).

+2  A: 

Have you tried binding the TabControl.ItemsSource to a collection view, and then sorting the collection view according to an index? Then your move logic would simply change the indexes and the tab items would order accordingly.

HTH,
Kent

Kent Boogaart
That sounds like it might work, although I think I might have just found an alternative that doesn't require modifying my drag/drop code (the code is used in other places as well so I was hoping for something that could be used across all drag/drop operations). +1 for a good idea since if others are looking for something similar this would probably work
Rachel
A: 

I ended up modifying my OnItemsChanged event to run the Remove code at a lower dispatcher priority then the Add code, so it gives the Add operation a chance to cancel the Remove one and reuse the TabItem's ContentPresenter instead of rendering a new one.

Original code I started with was obtained from here

It basically stores the TabItem ContentPresenters so when switching tabs it uses a stored ContentPresenter instead of redrawing a new one. Here's the modifications I made to OnItemsChanged to get the Drag/Drop to reuse an old item instead of redrawing a new one

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);
                    }
                }
            }
            ));
        }
    }
Rachel