Code example is reduced to the minimum amount reproducing the issue. Given a custom WPF user control with a list of children defined as such:
[ContentProperty(nameof(FormItems))]
public class FormContainer : ContentControl
{
static FormContainer()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(FormContainer),
new FrameworkPropertyMetadata(typeof(FormContainer))
);
}
#region FormItems Dependency Property
public static readonly DependencyProperty FormItemsProperty = DependencyProperty.Register(
"FormItems", typeof(ObservableCollection<UIElement>), typeof(FormContainer),
new PropertyMetadata(new ObservableCollection<UIElement>()));
public ObservableCollection<UIElement> FormItems
{
get => (ObservableCollection<UIElement>)GetValue(FormItemsProperty);
set => SetValue(FormItemsProperty, value);
}
#endregion
// ... extra boilerplate removed
}
Which is being rendered in a simple Stack Panel with an ItemsControl help using the following style:
<ControlTemplate x:Key="FormContainerControlTemplate" TargetType="{x:Type uc:FormContainer}">
<ItemsControl ItemsSource="{TemplateBinding FormItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ControlTemplate>
<Style TargetType="{x:Type uc:FormContainer}">
<Setter Property="Template" Value="{StaticResource FormContainerControlTemplate}"/>
</Style>
This usage of the control works perfectly:
<controls:FormContainer>
<Label Content="Something" />
</controls:FormContainer>
This usage crashed with a stackoverflow:
<controls:FormContainer>
<Label Content="Something" />
<controls:FormContainer>
<Label Content="Something else" />
</controls:FormContainer>
</controls:FormContainer>
I would expected to just get two containers nested with their own controls.
Why does it crashed? What am I missing? And more importantly how can I enable nesting functionality for my custom controls?
CodePudding user response:
DP metadata declaration is incorrect. DP object (FormItemsProperty
) is a singletone, so its PropertyMetadata and default value is shared by all intances of FormContainer. Which in case of reference type DP can cause issues.
fix them by assigning a different collection for each instance:
public class FormContainer : ContentControl
{
static FormContainer()
{
DefaultStyleKeyProperty.OverrideMetadata
(
typeof(FormContainer),
new FrameworkPropertyMetadata(typeof(FormContainer))
);
}
public FormContainer()
{
SetCurrentValue(FormItemsProperty, new ObservableCollection<UIElement>());
}
#region FormItems Dependency Property
public static readonly DependencyProperty FormItemsProperty = DependencyProperty.Register
(
nameof(FormItems),
typeof(ObservableCollection<UIElement>),
typeof(FormContainer),
new PropertyMetadata(null)
);
public ObservableCollection<UIElement> FormItems
{
get => (ObservableCollection<UIElement>)GetValue(FormItemsProperty);
set => SetValue(FormItemsProperty, value);
}
#endregion
}
CodePudding user response:
Even though @ASh suggested a working solution, I want to show a more typical way for WPF to implement a collection property of child elements.
public FormContainer()
{
SetValue(FormItemsPropertyKey, new UIElementCollection (this, this));
}
private static readonly DependencyPropertyKey FormItemsPropertyKey = DependencyProperty.RegisterReadOnly(
nameof(FormItems),
typeof(UIElementCollection),
typeof(FormContainer));
public static readonly DependencyProperty FormItemsProperty = FormItemsPropertyKey.DependencyProperty;
public UIElementCollection FormItems => (UIElementCollection)GetValue(FormItemsProperty);
See more complete example: public UIElementCollection Children