Deadzone
Deadzone

Reputation: 814

Removing child from panel, bound to an ItemsSource

I'm trying to create a drag and drop functionality for a couple of buttons which are contained in an ObservableCollection<> in the view model, the collection is later used as an ItemsSource for a StackPanel:

This is the xaml structure:

<ItemsControl x:Name="RingHolder" Grid.Column="0" Grid.ColumnSpan="3" ItemsSource="{Binding Rings}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <customControls:RingsStackpanel Orientation="Vertical" VerticalAlignment="Bottom"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <customControls:RingControl VerticalAlignment="Stretch" Height="50"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Where RingsStackPanel inherits from StackPanel and RingControl inherits from Button.

In order to allow the button to be dragged around freely I want to detach it from it's parent like so:

if (VisualTreeHelper.GetParent(this) is ContentPresenter contentPresenter)
{
    if (VisualTreeHelper.GetParent(contentPresenter) is RingsStackpanel ringStackPanel)
    {
        ringStackPanel.Children.Remove(contentPresenter);
    }
}

However I get the following exception:

InvalidOperationException:

Cannot explicitly modify Children collection of Panel used as ItemsPanel for ItemsControl. ItemsControl generates child elements for Panel

Which makes sense, I can only modify it by modifying the ItemsSource that is bound to it, however in this scenario, removing an item from the ObservableCollection<> causes the selected Button to disappear as it's being destroyed. Is there anyway I can allow my Button to move around freely, without creating a clone of it (this would be nigh impossible as the project currently is).

Upvotes: 0

Views: 531

Answers (1)

mm8
mm8

Reputation: 169420

You could remove the data item from the source collection and add a public method to your RingsStackpanel class that removes the visual from the logical tree:

public class RingsStackpanel : StackPanel
{
    ...
    public void RemoveElement(Visual visual) => RemoveVisualChild(visual);
}

You should then be able to handle the Unloaded event of the ContentPresenter and remove its parent-child with the StackPanel. Something like this:

if (VisualTreeHelper.GetParent(this) is ContentPresenter contentPresenter)
{
    if (VisualTreeHelper.GetParent(contentPresenter) is RingsStackpanel ringStackPanel
        && RingHolder.ItemsSource is ObservableCollection<YourItemType> dataItems
        && contentPresenter.DataContext is YourItemType dataItem)
    {
        //wait for the ContentPresenter to get unloaded
        RoutedEventHandler handler = null;
        handler = (ss, ee) =>
        {
            //remove the parent-child relationship:
            ringStackPanel.RemoveElement(contentPresenter);

            contentPresenter.Unloaded -= handler;
        };
        //remove the data object
        dataItems.Remove(dataItem);
    }
}

Upvotes: 1

Related Questions