正在为ItemsControl创建可重复使用的DataTemplate资源

本文关键字:DataTemplate 资源 ItemsControl 创建 | 更新日期: 2023-09-27 17:59:50

我目前拥有的

我当前有一个ItemsControl,用于显示控件列表。由于每个"项"都包含多个控件,我通过指定DataTemplate来设置它。类似这样的东西(我删除了一些元素属性,使代码更容易遵循):

<ItemsControl x:Name="Items" ItemsSource="{Binding Path=MyItems}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="10"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Path Grid.Column="0"/>
                <StackPanel Grid.Column="1" Orientation="Horizontal">
                    <c:MyControl />
                    <c:MyButton />
                </StackPanel>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

我想做什么

上面给出了我想要的功能,但我在一些地方有它,我想尽量减少重复的代码。关于上面的xaml,当重用DataTemplate时,唯一需要不同的是"MyButton"answers"MyControl"的控件。考虑到这一点,我定义上面XAML的理想方式是这样的:

<ItemsControl x:Name="Items" ItemsSource="{Binding Path=MyItems}">
    <c:MyControl />
    <c:MyButton />
</ItemsControl>

当然,我很高兴有一些变化,但希望我正在努力消除重复。

我尝试过的

到目前为止,我已经尝试在我的资源文件中创建一个模板,但效果不太好,我甚至不确定我是否走上了正确的道路。这就是我所拥有的:

<DataTemplate x:Key="MyTemplate">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="10"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Path Grid.Column="0" Fill="..." Data="..." />
        <StackPanel Grid.Column="1" Orientation="Horizontal">
            <ItemsPresenter />
        </StackPanel>
    </Grid>
</DataTemplate>

我试着把它像这样应用到我的XAML:

<ItemsControl x:Name="Items" ItemsSource="{Binding Path=MyItems}" ItemTemplate="{StaticResource MyTemplate}">
    <c:MyControl />
    <c:MyButton />
</ItemsControl>

这一切都构建得很好,但在运行时我遇到了一个错误:"Items collection must be empty before using ItemsSource.",这显然是我错误方法的副作用。

我做错了什么?我如何设置我的模板以使其按我希望的方式工作?

正在为ItemsControl创建可重复使用的DataTemplate资源

您可以创建一个派生的ItemsControl类,该类使用ContentControl(而不是ContentPresenter)作为项目容器类型:

public class MyItemsControl : ItemsControl
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        return new ContentControl();
    }
}

现在,您可以将当前的DataTemplate分为一个"外部"可重用部分和一个"内部"部分,前者驻留在ContentControl的Template中,后者由剩余的DataTemplate:定义

<!-- somewhere in Resources -->
<Style x:Key="ReusableItemContainerStyle" TargetType="ContentControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ContentControl">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="10"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Path Grid.Column="0" ... />
                    <ContentPresenter Grid.Column="1"
                        Content="{TemplateBinding Content}"
                        ContentTemplate="{TemplateBinding ContentTemplate}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<local:MyItemsControl
    ItemsSource="{Binding MyItems}"
    ItemContainerStyle="{StaticResource ReusableItemContainerStyle}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <c:MyControl />
                <c:MyButton />
            </StackPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</local:MyItemsControl>

更新:您还可以为Generic.xaml中的派生ItemsControl设置默认样式的可重用ItemContainerStyle,如下所示:

<Style TargetType="local:MyItemsControl"
       BasedOn="{StaticResource {x:Type ItemsControl}}">
    <Setter Property="ItemContainerStyle">
        <Setter.Value>
            <Style TargetType="ContentControl">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ContentControl">
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="10"/>
                                    <ColumnDefinition Width="*"/>
                                </Grid.ColumnDefinitions>
                                <Path Grid.Column="0" ... />
                                <ContentPresenter Grid.Column="1"
                                    Content="{TemplateBinding Content}"
                                    ContentTemplate="{TemplateBinding ContentTemplate}"/>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </Setter.Value>
    </Setter>
</Style>

然后,您还必须为ItemsControl:设置默认样式键

public class MyItemsControl : ItemsControl
{
    static MyItemsControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(
            typeof(MyItemsControl),
            new FrameworkPropertyMetadata(typeof(MyItemsControl)));
    }
    protected override DependencyObject GetContainerForItemOverride()
    {
        return new ContentControl();
    }
}

将模板作为新控件声明为:

<UserControl x:Class="UI.Views.NewControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300" Name="myNewControl">
    <Grid>
        <ItemsControl x:Name="Items" ItemsSource="{Binding Path=MyItems}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="10"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Path Grid.Column="0"/>
                        <ContentControl Grid.Row="1" Content="{Binding MyCustomControl, ElementName=myNewControl}"/>
                    </Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</UserControl>

MyCustomControl应该是控件的依赖项属性。

如何使用:

<MyNewControl>
   <MyNewControl.MyCustomControl>
        <StackPanel>
             <MyControl/>
             <MyButton/>
        </StackPanel>
   </MyNewControl.MyCustomControl>
</MyNewControl>

希望这能帮助