Reputation: 191
Attempting to write a custom control which permits a collection of children content. The end goal is to have some functional filtering of the child elements based on custom attached dependency properties; however, before getting that far, any attempts I've made to collect and re-display child UIElements have ended in exceptions during XAML parsing/display.
I have a class CustomFilter : Control
which has an associated default style in a nearby xaml file. It has a dependency property for Children, which currently collect the child elements in a UIElementCollection.
I believe my problem may be how I am attempting to render the collection of children. The only way I know of looping through content in XAML involves using an ItemsControl, to which I am passing my Children collection as the ItemsSource, which feels rather backwards (using UIElements as data models?)
Afterwards, we attempt to render the elements via a content presenter inside of the ItemTemplate. I've seen in many other examples which render control children using content presenters, so I expect this is probably at least partially correct (though all only using single elements).
The sample class
[ContentProperty(nameof(Children))]
public class CustomFilter : Control
{
static CustomFilter()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomFilter), new FrameworkPropertyMetadata(typeof(CustomFilter)));
}
public CustomFilter()
{
Children = new UIElementCollection(this, this);
}
public static readonly DependencyProperty ChildrenProperty = DependencyProperty.Register(nameof(Children), typeof(UIElementCollection), typeof(CustomFilter), new FrameworkPropertyMetadata(RefreshFilter));
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public UIElementCollection Children
{
get { return (UIElementCollection)GetValue(ChildrenProperty); }
private set { SetValue(ChildrenProperty, value); }
}
}
The sample template
<Style TargetType="{x:Type local:CustomFilter}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomFilter}">
<StackPanel>
<ItemsControl ItemsSource="{TemplateBinding Children}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<ContentPresenter Content="{Binding}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The use case
<local:CustomFilter>
<TextBlock Text="X"/>
<TextBlock Text="Y"/>
<TextBlock Text="Z"/>
</local:CustomFilter>
Attempting to run use this control whatsoever causes it to fail when it reaches the ItemsControl, without reaching the ItemTemplate, with the following exception:
ArgumentException: Specified Visual is already a child of another Visual or the root of a CompositionTarget.
I've attempted to create a wrapper class for the Children, passing one child to each instance of the wrapper, and binding to that as an ItemsSource - it successfully loops through the ItemTemplate, but attempting to use the ContentPresenter there on the wrapped child element provides me with the following exception instead:
ArgumentException: Must disconnect specified child from current parent Visual before attaching to new parent Visual.
Upvotes: 2
Views: 1059
Reputation: 169380
Why are you using an UIElementCollection
instead of a List<UIElement>
? You are not supposed to bind to an UIElementCollection
in a template.
Your example should work if you define a List<UIElement>
dependency property:
[ContentProperty(nameof(Children))]
public class CustomFilter : Control
{
static CustomFilter()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomFilter), new FrameworkPropertyMetadata(typeof(CustomFilter)));
}
public CustomFilter()
{
SetValue(ChildrenPropertyKey, new List<UIElement>());
}
private static readonly DependencyPropertyKey ChildrenPropertyKey =
DependencyProperty.RegisterReadOnly(
nameof(Children),
typeof(List<UIElement>),
typeof(CustomFilter),
new FrameworkPropertyMetadata(new List<UIElement>())
);
public static readonly DependencyProperty ChildrenProperty =
ChildrenPropertyKey.DependencyProperty;
public List<UIElement> Children
{
get { return (List<UIElement>)GetValue(ChildrenProperty); }
}
}
Upvotes: 1