在运行时组合DataTemplates [英] Combining DataTemplates at runtime

查看:60
本文介绍了在运行时组合DataTemplates的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

< ;!-这是每个模板的唯一自定义部分->
< StackPanel Margin = 0,10,5,0 Orientation = Horizo​​ntal>
<标签内容=类型: />
< ComboBox Height = 22 Horizo​​ntalAlignment = Left SelectedItem = {Binding Path = CollectibleType,Mode = TwoWay}
ItemsSource = {Binding Source = {StaticResource collectibleTypeFromEnum}} /> ;
< / StackPanel>
< ;!-结束自定义部分->

< StackPanel Margin = 0,0,0,5>
< Label Content =可用的操作:>
< 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,尽可能地做到无懈可击。当然,没有复制带有中间添加的自定义 stuff的整个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 />

< ;!-这是每个模板的唯一自定义部分->
[...]
<!-结束自定义部分->

< ContentPresenter Content = {StaticResource AvailableActions} />

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

< StackPanel Margin = 0,0,0,5 x:Key = AvailableActions x:Shared = false>
< Label Content =可用的操作:>
< Label.Style>
< ;!-
[第一个示例中共享XAML的下半部分,在此处卸载]
->
< / StackPanel>

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



不确定,这里有些困惑,因为是否有一种模式可以让我优雅地解决这个问题。 / p>

而且,仅供参考:我当前的DataTemplateSelector非常简单。我希望在这里构造最终的DataTemplate,而不是简单地返回用XAML硬编码的数据模板。

 公共类NodeComponentDataTemplateSelector: DataTemplateSelector 
{
公共重写DataTemplate SelectTemplate(对象项,DependencyObject容器)
{
FrameworkElement元素=容器作为FrameworkElement;

if(element!= null&&&&&item!= null)
{
if(item is CollectibleComponent)
返回元素。FindResource( collectibleTemplate )作为DataTemplate;

// [...]
}
}
}


解决方案

您可以使用DataTemplate .microsoft.com / en-us / library / cc663033(v = vs.110).aspx rel = nofollow noreferrer> XamlReader.Parse 或 XamlReader.Load 方法,例如:

 字符串模板=< 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], ...自定义代码...); 
将System.Windows.Markup.XamlReader.Parse(template)返回为DataTemplate;

自定义部分可以定义为 UserControls



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


I have a ListBox that presents a databound list of objects via its ItemSource. Because each object has special display needs I’m defining an ItemTemplateSelector that returns the appropriate DataTemplate depending on the object. That all works without a hitch.

The DataTemplates for each object follow a common formula, but contains custom elements in the middle. For example:

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

As you can see there’s lots of shared XAML, wrapping a small custom section in the middle.

Additional data templates will be written by other engineers (they’ll want to create one for each new object type that they add), so I’m interested in making the creation of a new DataTemplate as fool-proof and painless as possible. No copying of the entire DataTemplate with the custom "stuff" added in the middle, of course – but I’m also not partial to extracting parts of the template as reusable parts and referencing them in because it still leads to lots of duplicate code in each new DataTemplate, and that means possible errors and hard maintainability. I.e., this right here is a more maintainable approach but still feels suboptimal:

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

So: what is my best strategy to solve this? AFAIK I’m stuck with using DataTemplates because that’s the only element that a ListBox ItemTemplateSelector accepts. Is there a way to create a compound DataTemplate in the DataTemplateSelector? I'd provide the stock DataTemplate that is shared by all objects, and the DataTemplateSelector references in the bit of custom XAML needed for each object type. Other engineers would hook into that generalized code behavior.

Not sure, fumbling a bit in the dark here as whether there is a pattern that allows me to solve this elegantly.

And, just for reference: my current DataTemplateSelector is super straightforward. This is where I would expect to construct the final DataTemplate, rather than simply returning one that's hardcoded in 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;

            // [...]
        }
    }
}

解决方案

You could create the DataTemplate dynamically using the XamlReader.Parse or XamlReader.Load method, e.g.:

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;

The custom parts could be defined as UserControls.

I am afraid there is no way to base a DataTemplate on another one in pure XAML though.

这篇关于在运行时组合DataTemplates的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆