Reputation: 132618
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).
Upvotes: 1
Views: 347
Reputation: 178780
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.
Upvotes: 2
Reputation: 132618
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);
}
}
}
));
}
}
Upvotes: 0