Silverlight 嵌套自定义控件导致 StackOverflowException

Silverlight Nested Custom Controls results in StackOverflowException

我正在为我的 Silverlight 项目编写可重用的 Controllibrary。目前我每次启动我的测试应用程序时都会收到一个 WhosebugException,它正在使用 Controllibrary。我能够在一个小样本 Projekt 中重现异常。

我有两个简单的自定义控件:

//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 有一个类型为 Control1 的 DependencyProperty。在我的 Controllibrary 中,Control1 与 MenuItem 类似,当按下 Control2 时,MenuItem 会传递给菜单。 现在在我的 Themes/generic.xaml 中,我正在为控件定义两个默认样式:

//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>

现在,当我在测试应用程序中使用 Control2 时,我收到 WhosebugException。设置断点时,将调用一次 Control2 的构造函数,然后继续调用 Control1 的构造函数,直到出现 WhosebugException。

当我更改 Control1 的构造函数以直接从具有 resourceKey 的 ResourceDictionary 加载其样式时,一切都可以正常加载。但这更像是一种解决方法而不是解决方案。

当我在 generic.xaml 中删除我的 TestProperty 的 Setter 时,一切正常。然后我能够覆盖测试应用程序的 App.xaml 中的样式,并在那里为 TestProperty 定义 Setter。但这也不是我想做的。

有人对此问题有解决方案吗? 或者有人可以向我解释为什么它会这样。

提前致谢

我自己偶然发现了这种出色的效果,结果是:

您不能在 ResourceDictionary(而 generic.xaml 是一个)中实例化从 UIElement 派生的对象(您的行 <local:Control1/> 正是这样做的),因为所述字典中的所有对象都必须是可共享的。

已记录 here。相关栏目:

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.

但是有治愈方法

将您的 control1 包装在 DataTemplate 中,导致您的 control1 不会在 ResourceDictionary 中实例化,而是在实际实例化 control2 的时间点实例化。

<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>

和代码

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 ) );
}