Silverlight Nested Custom Controls results in StackOverflowException

73 views Asked by At

iam writing a reusable Controllibrary for my Silverlight Projects. At the moment i get an StackOverflowException each Time i startup my test Application wich is using the Controllibrary. I was able to reproduce the Exception in a small sample Projekt.

I have two simple Custom Controls:

//Control1.cs
public class Control1:Control
{
    public Control1()
    {
        this.DefaultStyleKey = typeof(Control1);
    }
}

//Control2.cs
public class Control2:Control
{
    public static DependencyProperty TestProperty =  
            DependencyProperty.Register("Test",typeof(Control1),typeof(Control2),null);

    public Control1 Test
    {
        get {return (Control1)GetValue(TestProperty);}
        set {SetValue(TestProperty,value);}
    }

    public Control2()
    {
        this.DefaultStyleKey = typeof(Control2);
    }
}

Constrol2 has a DependencyProperty of Type Control1. In my Controllibrary Control1 is something simelar to a MenuItem which is passed to the Menu when Control2 is pressed. Now in my Themes/generic.xaml iam defining the two default Styles for the Controls:

//generic.xaml
<Style TargetType="local:Control1">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:Control1">
                <Rectangle Width="20" Height="20" Fill="Blue"/>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style TargetType="local:Control2">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:Control2">
                <Rectangle Width="20" Height="20" Fill="Red"/>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="Test">
        <Setter.Value>
            <local:Control1/>
        </Setter.Value>
    </Setter>
</Style>

Now when i use Control2 in a Test Application iam getting a StackOverflowException. When setting up Breakpoints the Constructor of Control2 is called once an then the Constructor of Control1 is called continuesly until the StackOverflowException.

When i change Control1s constructor to load its style directly from a ResourceDictionary with a resourceKey everything loads fine. But that is more like a Workaround than a Solution.

When i remove the Setter for my TestProperty in the generic.xaml everthing works fine as well. Then iam able to override the Style in my App.xaml of the Test Application and define the Setter for the TestProperty there. But thats also not what i want to do.

Does annyone have a Solution for this Problem? Or maybe an someone can explain me why it behaves that way.

Thanks in advance

1

There are 1 answers

1
Martin On BEST ANSWER

I stumbled upon that splendid effect myself, and as it turned out:

You can't instantiate an object derived from UIElement (your line <local:Control1/> tries exactly this) in a ResourceDictionary (and generic.xaml is one), because all objects in said dictionary must be shareable.

Documented here. Relevant section:

Shareable Types and UIElement Types

A resource dictionary is a technique for defining shareable types and values of these types in XAML. Not all types or values are suitable for usage from a ResourceDictionary. For more information on which types are considered shareable in Silverlight, see Resource Dictionaries.

In particular, all UIElement derived types are not shareable unless they come from templates and application of a template on a specific control instance. Excluding the template case, a UIElement is expected to only exist in one place in an object tree once instantiated, and having a UIElement be shareable would potentially violate this principle.

But there is a cure

Wrap your control1 in a DataTemplate, causing your control1 to not be instantiated in the ResourceDictionary but rather at the point in time when a control2 is actually instantiated.

<Style TargetType="OuterControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="OuterControl">
                <StackPanel>
                    <TextBlock Text="Outer Hello World"/>
                    <ContentPresenter
                        Content="{TemplateBinding Content}"
                        ContentTemplate="{TemplateBinding ContentTemplate}"/>
                </StackPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="ContentTemplate">
        <Setter.Value>
            <DataTemplate>
                <InnerControl/>
            </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>

and code

public class OuterControl : Control
{
    public OuterControl()
    {
        DefaultStyleKey = typeof( OuterControl );
    }

    public InnerControl Content
    {
        get { return (InnerControl) GetValue( ContentProperty ); }
        set { SetValue( ContentProperty, value ); }
    }

    public static readonly DependencyProperty ContentProperty =
        DependencyProperty.Register( "Content", typeof( InnerControl ), typeof( OuterControl ), new PropertyMetadata( null ) );

    public DataTemplate ContentTemplate
    {
        get { return (DataTemplate) GetValue( ContentTemplateProperty ); }
        set { SetValue( ContentTemplateProperty, value ); }
    }

    public static readonly DependencyProperty ContentTemplateProperty =
        DependencyProperty.Register( "ContentTemplate", typeof( DataTemplate ), typeof( OuterControl ), new PropertyMetadata( null ) );
}