如何从 DataTemplate 创建实例
How to create an instance from a DataTemplate
我已经实现了一个自定义控件,它在控件模板中内部使用了一个 ListView。 FindParts 可以访问此 ListView。我需要将 ListViews View 属性 设置为一个实例,我必须在我的自定义控件中以编程方式实例化该实例。我希望能够从可以绑定到自定义控件的 属性 的 DataTemplate 创建此实例。
问题是我不知道如何从 DataTemplate 创建实例。
请注意,我不想将 GridView 直接绑定到视图 属性(例如,从 DataTemplates x:Shared 属性设置为 false 的 ResourceDictionary),因为这会导致问题XAML 设计师(View 不能被多个 ListView 共享)。
编辑: 在我与 grek40 的讨论中,很明显无法在 DataTemplate 中提供 GridView。因此,标记为解决方案的答案不处理如何从我的问题标题暗示的 DataTemplate 创建实例的问题。
您可以实现自己的 DataTemplateSelector,然后动态创建一次性使用的 DataTemplate,大致如下:
public DataTemplate GenerateTemplate() {
var template = new DataTemplate();
var p = new FrameworkElementFactory(typeof(Grid));
p.SetValue(FrameworkElement.HorizontalAlignmentProperty, HorizontalAlignment.Left);
p.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Center);
...
template.VisualTree = p;
return template;
}
由于你分享的代码不多,所以我凭空做了一些东西。想法:MyCustomControl
托管项目,为此,在 ControlTemplate
中使用 ListView
。默认 GridView
直接包含在 ControlTemplate
中,但可以通过 MyCustomControl.MyView
更改它
自定义控件:
public class MyCustomControl : ItemsControl
{
static MyCustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl), new FrameworkPropertyMetadata(typeof(MyCustomControl)));
}
public ViewBase MyView
{
get { return (ViewBase)GetValue(MyViewProperty); }
set { SetValue(MyViewProperty, value); }
}
public static readonly DependencyProperty MyViewProperty =
DependencyProperty.Register("MyView", typeof(ViewBase), typeof(MyCustomControl), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnMyViewChanged)));
private static void OnMyViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyCustomControl)d).HasCustomView = e.NewValue != null;
}
// readonly property to support the Trigger in the ControlTemplate in order to exchange the ListView.View
public bool HasCustomView
{
get { return (bool)GetValue(HasCustomViewProperty); }
private set { SetValue(HasCustomViewPropertyKey, value); }
}
private static readonly DependencyPropertyKey HasCustomViewPropertyKey =
DependencyProperty.RegisterReadOnly("HasCustomView", typeof(bool), typeof(MyCustomControl), new PropertyMetadata(false));
public static readonly DependencyProperty HasCustomViewProperty = HasCustomViewPropertyKey.DependencyProperty;
}
控件模板(注意替换的默认GridView和触发器):
<Style TargetType="{x:Type local:MyCustomControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyCustomControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ListView x:Name="PART_ListView"
ItemsSource="{Binding Items,RelativeSource={RelativeSource TemplatedParent}}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="DEF"/>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="HasCustomView" Value="True">
<Setter TargetName="PART_ListView" Property="View" Value="{Binding MyView,RelativeSource={RelativeSource TemplatedParent}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
用法:
<local:MyCustomControl>
<!--uses default view-->
<TextBlock Text="Item 1"/>
<TextBlock Text="Item 2"/>
</local:MyCustomControl>
<local:MyCustomControl>
<!--uses custom view-->
<local:MyCustomControl.MyView>
<GridView>
<GridView.Columns>
<GridViewColumn Header="ABC"/>
</GridView.Columns>
</GridView>
</local:MyCustomControl.MyView>
<TextBlock Text="Item 1"/>
<TextBlock Text="Item 2"/>
</local:MyCustomControl>
如有任何不明之处,请随时询问。如评论所述,我不知道如何将您的 DataTemplate
想法应用到所有这些东西中。
编辑:
以下是一个资源部分的示例,用于定义一些数据和 DataTemplate
及其可能的用法。
<StackPanel>
<StackPanel.Resources>
<x:Array x:Key="Items1" Type="{x:Type sys:Int32}">
<sys:Int32>1</sys:Int32>
<sys:Int32>2</sys:Int32>
<sys:Int32>3</sys:Int32>
<sys:Int32>4</sys:Int32>
</x:Array>
<x:Array x:Key="Items2" Type="{x:Type sys:Int32}">
<sys:Int32>5</sys:Int32>
<sys:Int32>4</sys:Int32>
<sys:Int32>6</sys:Int32>
<sys:Int32>7</sys:Int32>
</x:Array>
<CollectionViewSource x:Key="ItemsSource1" Source="{StaticResource Items1}"/>
<CollectionViewSource x:Key="ItemsSource2" Source="{StaticResource Items2}"/>
<DataTemplate x:Key="IntegerListTemplate1">
<local:MyCustomControl ItemsSource="{Binding}">
<!--uses default view-->
</local:MyCustomControl>
</DataTemplate>
<DataTemplate x:Key="IntegerListTemplate2">
<local:MyCustomControl ItemsSource="{Binding}">
<!--uses custom view-->
<local:MyCustomControl.MyView>
<GridView>
<GridView.Columns>
<GridViewColumn Header="ABC"/>
</GridView.Columns>
</GridView>
</local:MyCustomControl.MyView>
</local:MyCustomControl>
</DataTemplate>
</StackPanel.Resources>
<!--the default view-->
<ContentControl ContentTemplate="{StaticResource IntegerListTemplate1}" Content="{Binding Source={StaticResource ItemsSource1}}"/>
<Separator Margin="3"/>
<!--same items other view-->
<ContentControl ContentTemplate="{StaticResource IntegerListTemplate2}" Content="{Binding Source={StaticResource ItemsSource1}}"/>
<Separator Margin="3"/>
<!--different items same view as second one, no complaints about reusing-->
<ContentControl ContentTemplate="{StaticResource IntegerListTemplate2}" Content="{Binding Source={StaticResource ItemsSource2}}"/>
</StackPanel>
编辑 2,针对评论的 MCVE:
我会完全按照我在第一次编辑中解释的那样解决这个问题。将您的控件变成 DataTemplate
,而不是将视图变成样式。
在MainWindow.xaml中替换为:
<!-- old -->
<myctrls:MyControl Grid.Row="0" Grid.Column="0" Style="{StaticResource AddressStyle}" ItemsSource="{Binding Addresses}"/>
<!-- new -->
<ContentControl Grid.Row="0" Grid.Column="0" ContentTemplate="{StaticResource AddressTemplate}" Content="{Binding Addresses}"/>
与其他事件相同。
在MyControlStyles.xaml(或指示DataTemplate
用法的重命名文件)中,将GridView
资源和Style
与myctrls:MyControl
合并到一个DataTemplate
.
旧:
<GridView x:Key="AddressGridView" x:Shared="False">
<GridViewColumn Header="City" Width="Auto" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding City}" HorizontalAlignment="Left"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Country" Width="Auto" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Country}" HorizontalAlignment="Left"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
<Style x:Key="AddressStyle" TargetType="{x:Type myctrls:MyControl}">
<Setter Property="SuggestionsView" Value="{DynamicResource AddressGridView}"/>
</Style>
新:
<DataTemplate x:Key="AddressTemplate">
<!-- the base control -->
<myctrls:MyControl ItemsSource="{Binding}">
<!-- this was previously assigned by AddressStyle -->
<myctrls:MyControl.SuggestionsView>
<!-- this was previously the AddressGridView -->
<!-- Same as before, only removed the x:Key and x:Shared -->
<GridView>
<GridViewColumn Header="City" Width="Auto" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding City}" HorizontalAlignment="Left"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Country" Width="Auto" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Country}" HorizontalAlignment="Left"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</myctrls:MyControl.SuggestionsView>
</myctrls:MyControl>
</DataTemplate>
ProductsStyle
的相同步骤。
如果您不喜欢 ContentControl
连接方法,请考虑创建额外的视图模型来托管集合,例如 AddressListViewModel
。这将允许您通过 DataType
来处理 DataTemplate
而不是 ContentControl
.
中的资源键和显式用法
我已经实现了一个自定义控件,它在控件模板中内部使用了一个 ListView。 FindParts 可以访问此 ListView。我需要将 ListViews View 属性 设置为一个实例,我必须在我的自定义控件中以编程方式实例化该实例。我希望能够从可以绑定到自定义控件的 属性 的 DataTemplate 创建此实例。
问题是我不知道如何从 DataTemplate 创建实例。
请注意,我不想将 GridView 直接绑定到视图 属性(例如,从 DataTemplates x:Shared 属性设置为 false 的 ResourceDictionary),因为这会导致问题XAML 设计师(View 不能被多个 ListView 共享)。
编辑: 在我与 grek40 的讨论中,很明显无法在 DataTemplate 中提供 GridView。因此,标记为解决方案的答案不处理如何从我的问题标题暗示的 DataTemplate 创建实例的问题。
您可以实现自己的 DataTemplateSelector,然后动态创建一次性使用的 DataTemplate,大致如下:
public DataTemplate GenerateTemplate() {
var template = new DataTemplate();
var p = new FrameworkElementFactory(typeof(Grid));
p.SetValue(FrameworkElement.HorizontalAlignmentProperty, HorizontalAlignment.Left);
p.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Center);
...
template.VisualTree = p;
return template;
}
由于你分享的代码不多,所以我凭空做了一些东西。想法:MyCustomControl
托管项目,为此,在 ControlTemplate
中使用 ListView
。默认 GridView
直接包含在 ControlTemplate
中,但可以通过 MyCustomControl.MyView
自定义控件:
public class MyCustomControl : ItemsControl
{
static MyCustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl), new FrameworkPropertyMetadata(typeof(MyCustomControl)));
}
public ViewBase MyView
{
get { return (ViewBase)GetValue(MyViewProperty); }
set { SetValue(MyViewProperty, value); }
}
public static readonly DependencyProperty MyViewProperty =
DependencyProperty.Register("MyView", typeof(ViewBase), typeof(MyCustomControl), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnMyViewChanged)));
private static void OnMyViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyCustomControl)d).HasCustomView = e.NewValue != null;
}
// readonly property to support the Trigger in the ControlTemplate in order to exchange the ListView.View
public bool HasCustomView
{
get { return (bool)GetValue(HasCustomViewProperty); }
private set { SetValue(HasCustomViewPropertyKey, value); }
}
private static readonly DependencyPropertyKey HasCustomViewPropertyKey =
DependencyProperty.RegisterReadOnly("HasCustomView", typeof(bool), typeof(MyCustomControl), new PropertyMetadata(false));
public static readonly DependencyProperty HasCustomViewProperty = HasCustomViewPropertyKey.DependencyProperty;
}
控件模板(注意替换的默认GridView和触发器):
<Style TargetType="{x:Type local:MyCustomControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyCustomControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ListView x:Name="PART_ListView"
ItemsSource="{Binding Items,RelativeSource={RelativeSource TemplatedParent}}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="DEF"/>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="HasCustomView" Value="True">
<Setter TargetName="PART_ListView" Property="View" Value="{Binding MyView,RelativeSource={RelativeSource TemplatedParent}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
用法:
<local:MyCustomControl>
<!--uses default view-->
<TextBlock Text="Item 1"/>
<TextBlock Text="Item 2"/>
</local:MyCustomControl>
<local:MyCustomControl>
<!--uses custom view-->
<local:MyCustomControl.MyView>
<GridView>
<GridView.Columns>
<GridViewColumn Header="ABC"/>
</GridView.Columns>
</GridView>
</local:MyCustomControl.MyView>
<TextBlock Text="Item 1"/>
<TextBlock Text="Item 2"/>
</local:MyCustomControl>
如有任何不明之处,请随时询问。如评论所述,我不知道如何将您的 DataTemplate
想法应用到所有这些东西中。
编辑:
以下是一个资源部分的示例,用于定义一些数据和 DataTemplate
及其可能的用法。
<StackPanel>
<StackPanel.Resources>
<x:Array x:Key="Items1" Type="{x:Type sys:Int32}">
<sys:Int32>1</sys:Int32>
<sys:Int32>2</sys:Int32>
<sys:Int32>3</sys:Int32>
<sys:Int32>4</sys:Int32>
</x:Array>
<x:Array x:Key="Items2" Type="{x:Type sys:Int32}">
<sys:Int32>5</sys:Int32>
<sys:Int32>4</sys:Int32>
<sys:Int32>6</sys:Int32>
<sys:Int32>7</sys:Int32>
</x:Array>
<CollectionViewSource x:Key="ItemsSource1" Source="{StaticResource Items1}"/>
<CollectionViewSource x:Key="ItemsSource2" Source="{StaticResource Items2}"/>
<DataTemplate x:Key="IntegerListTemplate1">
<local:MyCustomControl ItemsSource="{Binding}">
<!--uses default view-->
</local:MyCustomControl>
</DataTemplate>
<DataTemplate x:Key="IntegerListTemplate2">
<local:MyCustomControl ItemsSource="{Binding}">
<!--uses custom view-->
<local:MyCustomControl.MyView>
<GridView>
<GridView.Columns>
<GridViewColumn Header="ABC"/>
</GridView.Columns>
</GridView>
</local:MyCustomControl.MyView>
</local:MyCustomControl>
</DataTemplate>
</StackPanel.Resources>
<!--the default view-->
<ContentControl ContentTemplate="{StaticResource IntegerListTemplate1}" Content="{Binding Source={StaticResource ItemsSource1}}"/>
<Separator Margin="3"/>
<!--same items other view-->
<ContentControl ContentTemplate="{StaticResource IntegerListTemplate2}" Content="{Binding Source={StaticResource ItemsSource1}}"/>
<Separator Margin="3"/>
<!--different items same view as second one, no complaints about reusing-->
<ContentControl ContentTemplate="{StaticResource IntegerListTemplate2}" Content="{Binding Source={StaticResource ItemsSource2}}"/>
</StackPanel>
编辑 2,针对评论的 MCVE:
我会完全按照我在第一次编辑中解释的那样解决这个问题。将您的控件变成 DataTemplate
,而不是将视图变成样式。
在MainWindow.xaml中替换为:
<!-- old -->
<myctrls:MyControl Grid.Row="0" Grid.Column="0" Style="{StaticResource AddressStyle}" ItemsSource="{Binding Addresses}"/>
<!-- new -->
<ContentControl Grid.Row="0" Grid.Column="0" ContentTemplate="{StaticResource AddressTemplate}" Content="{Binding Addresses}"/>
与其他事件相同。
在MyControlStyles.xaml(或指示DataTemplate
用法的重命名文件)中,将GridView
资源和Style
与myctrls:MyControl
合并到一个DataTemplate
.
旧:
<GridView x:Key="AddressGridView" x:Shared="False">
<GridViewColumn Header="City" Width="Auto" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding City}" HorizontalAlignment="Left"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Country" Width="Auto" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Country}" HorizontalAlignment="Left"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
<Style x:Key="AddressStyle" TargetType="{x:Type myctrls:MyControl}">
<Setter Property="SuggestionsView" Value="{DynamicResource AddressGridView}"/>
</Style>
新:
<DataTemplate x:Key="AddressTemplate">
<!-- the base control -->
<myctrls:MyControl ItemsSource="{Binding}">
<!-- this was previously assigned by AddressStyle -->
<myctrls:MyControl.SuggestionsView>
<!-- this was previously the AddressGridView -->
<!-- Same as before, only removed the x:Key and x:Shared -->
<GridView>
<GridViewColumn Header="City" Width="Auto" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding City}" HorizontalAlignment="Left"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Country" Width="Auto" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Country}" HorizontalAlignment="Left"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</myctrls:MyControl.SuggestionsView>
</myctrls:MyControl>
</DataTemplate>
ProductsStyle
的相同步骤。
如果您不喜欢 ContentControl
连接方法,请考虑创建额外的视图模型来托管集合,例如 AddressListViewModel
。这将允许您通过 DataType
来处理 DataTemplate
而不是 ContentControl
.