模板初始化导致堆栈溢出

时间:2014-04-13 12:44:06

标签: c# wpf stack-overflow datatemplate contentpresenter

我有一个像这样的DataModel:

public class Node
    {
        public List<Node> Children { get; private set; }
        public string Name { get; private set; }

        public Node(string _name, params Node[] _children)
        {
            Name = _name;
            Children = new List<Node>(_children);
        }
    }

我现在想要为这个模型定义一个视图(我没有使用TreeView,原因超出了这个问题的范围),这允许人们以两种方式之一使用它。

示例1:默认布局,内容应自动展开

<NodeView DataContext="{Binding Root}"/>

上面应该以与树视图相同的方式扩展节点树,即递归地沿着节点及其子节点创建每个节点的新视图。

示例2:允许其他人手动设置内容

<NodeView DataContext="{Binding Root}">
    <StackPanel>
       <TextBlock Text="{Binding Children[0].Name"/>
       <TextBlock Text="{Binding Children[1].Name"/>
       <TextBlock Text="{Binding Children[2].Name"/>
    </StackPanel>
</NodeView>

上面的内容现在不会扩展,但只显示前三个子节点。

我以为我可以使用以下自定义控件执行此操作,但是我收到了stackoverflow异常,我做错了什么?

<Style TargetType="{x:Type l:NodeView}">
        <Setter Property="Content">
            <Setter.Value>
                <GroupBox>
                    <ItemsControl ItemsSource="{Binding Children}">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <l:NodeView />
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </GroupBox>

            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type l:NodeView}">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="18" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>

                        <TextBlock Text="{Binding Name}"
                                   Grid.Column="1" />
                        <ContentPresenter Grid.Row="1"
                                          Grid.Column="1" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

如果有人发现更容易使用,这是项目的链接 https://www.dropbox.com/s/j32mm7gave17v7j/NodeView.zip

2 个答案:

答案 0 :(得分:0)

问题主要来自你的风格。您设置内容。但是,您应该指定Template和ItemTemplate。

第一个将描述NodeView的视觉效果:带有子列表的文本块。 第二个将描述您的子节点是如何可视化的:NodeView控件。

<Style TargetType="{x:Type l:NodeView}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type l:NodeView}">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="18" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>

                    <TextBlock Text="{Binding Name}"
                               Grid.Column="1" />
                    <ItemsControl Grid.Row="1" Grid.Column="1"
                                  ItemsSource="{Binding Children}">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <l:NodeView Content="{Binding}"/>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

你会注意到没有ContentPresenter它看起来有点奇怪。但这是NodeView不合适的基类的结果。内容控件通常用于单个内容,但在这里您有一个包含子项的内容。

顺便说一下,这不是你写它的唯一方法。可以保留模板的默认模板,并将整个内容放入NodeView的ContentTemplate中。

<Style TargetType="{x:Type l:NodeView}">
    <Setter Property="ContentTemplate">
        <Setter.Value>
            <DataTemplate TargetType="{x:Type l:NodeView}">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="18" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>

                    <TextBlock Text="{Binding Name}"
                               Grid.Column="1" />
                    <ItemsControl Grid.Row="1" Grid.Column="1"
                                  ItemsSource="{Binding Children}">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <l:NodeView Content="{Binding}"/>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </Grid>
            </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>

在主窗口中,您应该使用Content属性而不是DataContext。

PS:我在没有测试的情况下编写了代码,因此不确定它的开箱即用。

答案 1 :(得分:0)

问题似乎是,你以控件本身的风格引用你正在模板化的控件。 所以当WPF尝试创建样式时,它会尝试初始化它,因此它必须创建datatemplate,因此它必须初始化样式......并且你有循环。

我发现,解决此问题的唯一方法是按代码设置默认内容,如下所示:

public NodeView()
    {
            var dt = FindResource("DefaultNodeContent") as DataTemplate;
            var lb = new ItemsControl();
            lb.ItemTemplate = dt;
            var binding = new Binding("Children");
            lb.SetBinding(ItemsControl.ItemsSourceProperty, binding);

            var gb = new GroupBox();
            gb.Content = lb;
            this.Content = gb;

    }

为此,您必须向您的应用添加资源字典

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:l="clr-namespace:WpfApplication5">
    <DataTemplate x:Key="DefaultNodeContent">
        <l:NodeView DataContext="{Binding}" />
    </DataTemplate>

</ResourceDictionary>

然后你的风格将改为:

<Style TargetType="{x:Type l:NodeView}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type l:NodeView}">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="18" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>

                        <TextBlock Text="{Binding Name}"
                                   Grid.Column="1" />
                        <ContentPresenter Grid.Row="1"
                                          Grid.Column="1" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

希望这有帮助。