在运行时组合 DataTemplates

Combining DataTemplates at runtime

我有一个 ListBox,它通过其 ItemSource 显示对象的数据绑定列表。因为每个对象都有特殊的显示需求,所以我定义了一个 ItemTemplateSelector returns 适当的 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 -->
                        <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>

那么:解决这个问题的最佳策略是什么?据我所知,我一直坚持使用 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;

            // [...]
        }
    }
}

您可以创建一个新的 CustomControl 来满足您的需要。它会自己应用样式,您可以添加额外的 DepdendencyProperties 以使其更方便。最后,您仍然可以将它放在 DataTemplate 中,以便与您的 DataTemplateSelector.

一起使用

您可以使用 XamlReader.Parse or XamlReader.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

恐怕没有办法将 DataTemplate 建立在另一个纯 XAML 的基础上。