Отключение элемента от любого / неопределенного родительского контейнера в WPF

У меня есть элемент управления, который является дочерним по отношению к другому элементу управления (так как все элементы без полномочий root /elemts находятся в WPF). Если я хочу переместить элемент управления в другой контейнер, я должен сначала отключить его от текущего контейнера (в противном случае выдается исключение).

Если я знаю, кто является родителем, тогда я могу просто удалить его из коллекции Children, Content или чего-либо еще. Но что, если я не знаю тип родительского контейнера - как тогда удалить дочерний элемент управления?

В приведенном ниже примере кода: Как я могу переместить "sp1" в другой контейнер, не зная тип родительского (Panel, GroupBox...)?

// Add the child object "sp1" to a container (of any type).
StackPanel sp1 = new StackPanel();
SomeParentControl.Children.Add(sp1);

// Somewhere else in the code. I still have a reference to "sp1" but now I don't know what container it is in. I just want to move the "sp1" object to another parent container.
AnotherParentControl.Content = sp1; // Generates exception: "Must disconnect specified child from current parent Visual before attaching to new parent Visual."

В идеале я хотел бы написать что-то вроде:

sp1.Parent.RemoveChild(sp1);

Но я не нашел ничего подобного.

2 ответа

Решение

Вы можете написать вспомогательный класс с методом расширения:

public static class RemoveChildHelper
{
    public static void RemoveChild(this DependencyObject parent, UIElement child)
    {
        var panel = parent as Panel;
        if (panel != null)
        {
            panel.Children.Remove(child);
            return;
        }

        var decorator = parent as Decorator;
        if (decorator != null)
        {
            if (decorator.Child == child)
            {
                decorator.Child = null;
            }
            return;
        }

        var contentPresenter = parent as ContentPresenter;
        if (contentPresenter != null)
        {
            if (contentPresenter.Content == child)
            {
                contentPresenter.Content = null;
            }
            return;
        }

        var contentControl = parent as ContentControl;
        if (contentControl != null)
        {
            if (contentControl.Content == child)
            {
                contentControl.Content = null;
            }
            return;
        }

        // maybe more
    }
}

NEW:

Я предлагаю использовать базовые классы вместо всех других перечисленных. Попробуйте этот код, эти 3 класса являются наиболее подходящими для ваших нужд. Как я понимаю, это почти так же, как предыдущие ^)

  var parent = VisualTreeHelper.GetParent(child);
  var parentAsPanel = parent as Panel;
  if (parentAsPanel != null)
  {
      parentAsPanel.Children.Remove(child);
  }
  var parentAsContentControl = parent as ContentControl;
  if (parentAsContentControl != null)
  {
      parentAsContentControl.Content = null;
  }
  var parentAsDecorator = parent as Decorator;
  if (parentAsDecorator != null)
  {
      parentAsDecorator.Child = null;
  }

СТАРЫЙ: Насколько я помню, вы можете использовать тип Visual в качестве родительского типа и попытаться вызвать метод RemoveVisualChild.

Для полноты я добавил проверку ItemsControl и метод Add, который вернет дочерний элемент. Дочерний или родительский элемент может еще не быть в визуальном дереве, поэтому вам нужно проверить как визуальное, так и логическое дерево:

    /// <summary>
    /// Adds or inserts a child back into its parent
    /// </summary>
    /// <param name="child"></param>
    /// <param name="index"></param>
    public static void AddToParent(this UIElement child, DependencyObject parent, int? index = null)
    {
        if (parent == null)
            return;

        if (parent is ItemsControl itemsControl)
            if (index == null)
                itemsControl.Items.Add(child);
            else
                itemsControl.Items.Insert(index.Value, child);
        else if (parent is Panel panel)
            if (index == null)
                panel.Children.Add(child);
            else
                panel.Children.Insert(index.Value, child);
        else if (parent is Decorator decorator)
            decorator.Child = child;
        else if (parent is ContentPresenter contentPresenter)
            contentPresenter.Content = child;
        else if (parent is ContentControl contentControl)
            contentControl.Content = child;
    }

    /// <summary>
    /// Removes the child from its parent collection or its content.
    /// </summary>
    /// <param name="child"></param>
    /// <param name="parent"></param>
    /// <returns></returns>
    public static bool RemoveFromParent(this UIElement child, out DependencyObject parent, out int? index)
    {
        parent = child.GetParent(true);
        if (parent == null)
            parent = child.GetParent(false);

        index = null;

        if (parent == null)
            return false;

        if (parent is ItemsControl itemsControl)
        {
            if (itemsControl.Items.Contains(child))
            {
                index = itemsControl.Items.IndexOf(child);
                itemsControl.Items.Remove(child);
                return true;
            }
        }
        else if (parent is Panel panel)
        {
            if (panel.Children.Contains(child))
            {
                index = panel.Children.IndexOf(child);
                panel.Children.Remove(child);
                return true;
            }
        }
        else if (parent is Decorator decorator)
        {
            if (decorator.Child == child)
            {
                decorator.Child = null;
                return true;
            }
        }
        else if (parent is ContentPresenter contentPresenter)
        {
            if (contentPresenter.Content == child)
            {
                contentPresenter.Content = null;
                return true;
            }
        }
        else if (parent is ContentControl contentControl)
        {
            if (contentControl.Content == child)
            {
                contentControl.Content = null;
                return true;
            }
        }

        return false;
    }

    public static DependencyObject GetParent(this DependencyObject depObj, bool isVisualTree)
    {
        if (isVisualTree)
        {
            if(depObj is Visual || depObj is Visual3D)
                return VisualTreeHelper.GetParent(depObj);
            return null;
        }
        else
            return LogicalTreeHelper.GetParent(depObj);
    }

Моя версия для решения @Clemens:

    /// <summary>
    /// Disconnects <paramref name="child"/> from it's parent if any.
    /// </summary>
    /// <param name="child"></param>
    public static void DisconnectIt(this FrameworkElement child)
    {
        var parent = child.Parent;
        if (parent == null)
            return;

        if (parent is Panel panel)
        {
            panel.Children.Remove(child);
            return;
        }

        if (parent is Decorator decorator)
        {
            if (decorator.Child == child)
                decorator.Child = null;

            return;
        }

        if (parent is ContentPresenter contentPresenter)
        {
            if (contentPresenter.Content == child)
                contentPresenter.Content = null;
            return;
        }

        if (parent is ContentControl contentControl)
        {
            if (contentControl.Content == child)
                contentControl.Content = null;
            return;
        }

        //if (parent is ItemsControl itemsControl)
        //{
        //  itemsControl.Items.Remove(child);
        //  return;
        //}
    }
Другие вопросы по тегам