在运行时组合DataTemplates

时间:2017-06-25 23:03:22

标签: c# wpf xaml datatemplateselector

我有一个ListBox,它通过ItemSource显示对象的数据绑定列表。因为每个对象都有特殊的显示需求,所以我定义了一个ItemTemplateSelector,它根据对象返回相应的DataTemplate。这一切都顺利进行。

每个对象的DataTemplates遵循一个通用公式,但在中间包含自定义元素。例如:

    <DataTemplate x:Key="collectibleTemplate">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Border BorderBrush="LightGray" BorderThickness="1">
                <Expander IsExpanded="True" Header="{Binding ComponentName}" Background="WhiteSmoke">
                    <StackPanel>
                        <TextBlock Margin="5,5,5,0" Text="{Binding EditDescription}" TextWrapping="Wrap" />

                        <!-- This is the only custom part of each template -->
                        <StackPanel Margin="0,10,5,0" Orientation="Horizontal">
                            <Label Content="Type:" />
                            <ComboBox Height="22" HorizontalAlignment="Left" SelectedItem="{Binding Path=CollectibleType, Mode=TwoWay}"
                                            ItemsSource="{Binding Source={StaticResource collectibleTypeFromEnum}}" />
                        </StackPanel>
                        <!-- End custom part -->

                        <StackPanel Margin="0,0,0,5">
                            <Label Content="Available Actions:" >
                                <Label.Style>
                                    <Style TargetType="Label">
                                        <Setter Property="Visibility" Value="Visible" />
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding EditActions.Count}" Value="0">
                                                <Setter Property="Visibility" Value="Collapsed" />
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                                </Label.Style>
                            </Label>
                            <ItemsControl ItemsSource="{Binding EditActions}">
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <Button Command="{Binding}" Content="{Binding Title}" ToolTip="{Binding ToolTip}" Margin="5,0,5,0"/>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>
                        </StackPanel>
                    </StackPanel>
                </Expander>
            </Border>
        </Grid>
    </DataTemplate>

正如你所看到的那样,有许多共享的XAML,在中间包含一个小的自定义部分。

其他数据模板将由其他工程师编写(他们希望为他们添加的每个新对象类型创建一个),因此我有兴趣创建一个新的DataTemplate,因为它是万无一失的,无痛苦的可能。当然,没有复制整个DataTemplate与中间添加的自定义“东西” - 但我也不会将模板的一部分提取为可重用的部分并引用它们,因为它仍会导致大量重复的代码每个新的DataTemplate,这意味着可能的错误和难以维护。即,这就是一种更易于维护的方法,但仍然感觉不是最理想的:

<DataTemplate x:Key="collectibleTemplate">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Border BorderBrush="LightGray" BorderThickness="1">
                <Expander IsExpanded="True" Header="{Binding ComponentName}" Background="WhiteSmoke">
                    <StackPanel>
                        <TextBlock Margin="5,5,5,0" Text="{Binding EditDescription}" TextWrapping="Wrap" />

                        <!-- This is the only custom part of each template -->
                        [...]
                        <!-- End custom part -->

                        <ContentPresenter Content="{StaticResource AvailableActions}" />

                    </StackPanel>
                </Expander>
            </Border>
        </Grid>
    </DataTemplate>

    <StackPanel Margin="0,0,0,5" x:Key="AvailableActions" x:Shared="false">
        <Label Content="Available Actions:" >
            <Label.Style>
        <!-- 
        [Bottom half of shared XAML from the first example, offloaded here]
        -->
    </StackPanel>

那么:解决这个问题的最佳策略是什么? AFAIK我坚持使用DataTemplates,因为这是ListBox ItemTemplateSelector接受的唯一元素。有没有办法在DataTemplateSelector中创建复合DataTemplate?我提供了所有对象共享的库存DataTemplate,以及每个对象类型所需的自定义XAML位中的DataTemplateSelector引用。其他工程师会加入这种通用代码行为。

不确定,在黑暗中摸索一下,因为是否有一种模式可以让我优雅地解决这个问题。

而且,仅供参考:我当前的DataTemplateSelector非常简单。这是我期望构建最终DataTemplate的地方,而不是简单地返回在XAML中硬编码的那个。

public class NodeComponentDataTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        FrameworkElement element = container as FrameworkElement;

        if (element != null && item != null)
        {
            if (item is CollectibleComponent)
                return element.FindResource("collectibleTemplate") as DataTemplate;

            // [...]
        }
    }
}

2 个答案:

答案 0 :(得分:1)

您可以创建符合您需求的新CustomControl。它将自己应用样式,您可以提供额外的DepdendencyProperties以使其更方便。最后,您仍然可以将其放在DataTemplate中,以便与DataTemplateSelector一起使用。

答案 1 :(得分:1)

您可以使用XamlReader.ParseXamlReader.Load方法动态创建DataTemplate,例如:

string template = "<DataTemplate xmlns =\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xmlns:x =\"http://schemas.microsoft.com/winfx/2006/xaml\"><StackPanel>[PLACEHOLDER]</StackPanel></DataTemplate>".Replace("[PLACEHOLDER]", "...custom code...");
return System.Windows.Markup.XamlReader.Parse(template) as DataTemplate;

自定义部件可以定义为UserControls

我担心在纯XAML中没有办法将DataTemplate基于另一个。{/ p>