Отключение элемента от любого / неопределенного родительского контейнера в 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;
//}
}