Reputation: 23
I have this UserControl:
[ContentProperty("Items")]
[DefaultProperty("Items")]
public partial class PanelControl : UserControl
{
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation", typeof(Orientation), typeof(PanelControl), new FrameworkPropertyMetadata(Orientation.Horizontal, new PropertyChangedCallback(OnOrientationChanged)));
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<UIElement>), typeof(PanelControl), new FrameworkPropertyMetadata(new ObservableCollection<UIElement>(), new PropertyChangedCallback(OnItemsChanged)));
public static readonly DependencyProperty SizeProperty = DependencyProperty.RegisterAttached("Size", typeof(double), typeof(PanelControl), new FrameworkPropertyMetadata(1.0, new PropertyChangedCallback(OnSizeChanged)), new ValidateValueCallback(IsSizeValid));
public Orientation Orientation
{
get
{
return (Orientation)GetValue(OrientationProperty);
}
set
{
SetValue(OrientationProperty, value);
}
}
public ObservableCollection<UIElement> Items
{
get
{
return (ObservableCollection<UIElement>)GetValue(ItemsProperty);
}
set
{
SetValue(ItemsProperty, value);
}
}
public static void SetSize(UIElement element, double size)
{
element.SetValue(SizeProperty, size);
}
public static double GetSize(UIElement element)
{
return (double)element.GetValue(SizeProperty);
}
private static void OnOrientationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
/*MessageBox.Show("orientation");*/
((PanelControl)dependencyObject).ClearAndBuildGrid();
}
private static void OnItemsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
/*MessageBox.Show("items");*/
((PanelControl)dependencyObject).ClearAndBuildGrid();
if(args.OldValue != null)
((ObservableCollection<UIElement>)args.OldValue).CollectionChanged -= ((PanelControl)dependencyObject).OnItemsCollectionChanged;
if (args.NewValue != null)
((ObservableCollection<UIElement>)args.NewValue).CollectionChanged += ((PanelControl)dependencyObject).OnItemsCollectionChanged;
}
private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
/*MessageBox.Show("collection");*/
ClearAndBuildGrid();
}
private static void OnSizeChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
((PanelControl)dependencyObject).ClearAndBuildGrid();
/*MessageBox.Show("size");*/
}
private static bool IsSizeValid(object value)
{
return (double)value < 0 ? false : true;
}
private void ClearAndBuildGrid()
{
MainGrid.Children.Clear();
MainGrid.RowDefinitions.Clear();
MainGrid.ColumnDefinitions.Clear();
/*MessageBox.Show(MainGrid.Children.Count.ToString());*/
for (int i = 0; i < Items.Count; i++)
{
if (Orientation == Orientation.Horizontal)
{
MainGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = SizeToGridLength(GetSize(Items[i])) });
Grid.SetColumn(Items[i], i * 2);
if (i != Items.Count - 1)
{
MainGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(5) });
GridSplitter splitter = new GridSplitter() { ResizeDirection = GridResizeDirection.Columns, HorizontalAlignment = HorizontalAlignment.Stretch };
Grid.SetColumn(splitter, i * 2 + 1);
MainGrid.Children.Add(splitter);
}
}
else
{
MainGrid.RowDefinitions.Add(new RowDefinition() { Height = SizeToGridLength(GetSize(Items[i])) });
Grid.SetRow(Items[i], i * 2);
if (i != Items.Count - 1)
{
MainGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(5) });
GridSplitter splitter = new GridSplitter() { ResizeDirection = GridResizeDirection.Rows, VerticalAlignment = VerticalAlignment.Stretch };
Grid.SetRow(splitter, i * 2 + 1);
MainGrid.Children.Add(splitter);
}
}
MainGrid.Children.Add(Items[i]);
}
}
private GridLength SizeToGridLength(double size)
{
return new GridLength(size, GridUnitType.Star);
}
public PanelControl()
{
InitializeComponent();
Items.CollectionChanged += OnItemsCollectionChanged;
}
}
And I use it here:
<p:PanelControl>
<Button />
<Button />
<Button />
</p:PanelControl>
When i start application it works good, but in designer I have underlined first button in xaml and error "Specified element is already the logical child of another element. Disconnect it first." Thanks for help, sorry for my bad English.
Upvotes: 2
Views: 2762
Reputation: 7163
Using the code from J.H., I would move it up to the top of your function, so that the state of your grid when you clear it, is "cleared", and all the children have been disconnected from the Visual Tree.
private void ClearAndBuildGrid()
{
foreach (var item in Items)
{
var parent = System.Windows.Media.VisualTreeHelper.GetParent(item) as Grid;
if (parent != null)
parent.Children.Remove(item);
}
MainGrid.Children.Clear();
It would be a style/intention/preference thing, and to be clear, the answer J.H. gives is totally valid.
Consider using foreach
instead of for
and not having to deal with array subscripts.
Upvotes: 0
Reputation: 4322
Not sure what is going on with the designer but this will 'fix' it.
Change the line:
MainGrid.Children.Add(Items[i]);
To:
var parent = VisualTreeHelper.GetParent(Items[i]) as Grid;
if (parent != null)
parent.Children.Remove(Items[i]);
MainGrid.Children.Add(Items[i]);
Upvotes: 2