我正在创建一个自定义ItemsControl,其中包含一个用于显示的网格。我希望用户能够使用datatemplates显示任何内容,但我该怎么做呢?
我知道如何创建模板,但我不知道如何应用模板以使项目在网格中正确定位(我的代码),并且每个项目都按用户需要显示(通过DataTemplate中)。
- 编辑 -
对于我的要求,似乎有点混乱。想象一下,我想从头开始创建自己的ListView,使用Grid进行布局(这不是我实际做的,但作为一个例子......)。给定用户的DataTemplate,如何使用它来确保根据模板显示每个网格单元格中的元素?
答案 0 :(得分:2)
您的控件可以公开自己的属性/属性,您在代码隐藏中声明了这些属性/属性。
如果您需要单个DataTemplate
,则可以公开类型为DataTemplate
的属性。当用户在XAML中声明您的控件类型时,她可以提供模板:
<ns:YourControl>
<ns:YourControl.DataTemplate>
<DataTemplate>
…
</DataTemplate>
</ns:YourControl.DataTemplate>
</ns:YourControl>
在您自己的控件中,您通过绑定到DataTemplate
属性来使用它。请务必在Binding
而不是DataContext
中引用控件本身。如果用户未指定DataTemplate
,您可能需要默认Exception
或抛出有用的DataTemplate
。
如果您公开类型为DataTemplateSelector
的属性,然后将其应用于您的项目,如果数据类型不同或用户可能在不同情况下需要不同的模板,则可以为用户提供额外的灵活性。
MyControl.xaml
<UserControl x:Class="MyNamespace.MyControl"
x:Name="ThisControl">
<ItemsControl ItemTemplate="{Binding ItemTemplate, ElementName=ThisControl}" />
</UserControl>
MyControl.xaml.cs
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
}
public static readonly DependencyProperty ItemTemplateProperty
= DependencyProperty.Register("ItemTemplate", typeof (DataTemplate),
typeof (MyControl), new PropertyMetadata(default(DataTemplate)));
public DataTemplate ItemTemplate
{
get { return (DataTemplate) GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
// Other dependency properties (ItemsSource, SelectedItem, etc.)
}
消费者:
<Grid>
<ns:MyControl ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}">
<ns:MyControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="2"
BorderBrush="Black">
<TextBlock Foreground="DarkGray"
Text="{Binding Name}"
Margin="4" />
</Border>
</DataTemplate>
</ns:MyControl.ItemTemplate>
</ns:MyControl>
</Grid>
好的,这是一个填充Grid
并使用DataTemplate
的工作示例。
MyControl
公开了一个属性ItemsSource
,它允许使用者绑定到她的视图模型中的集合。 MyControl
还公开了一个属性ItemTemplate
,允许用户指定如何显示这些项目(同样,您也可以允许用户指定DataTemplateSelector
)。
在代码隐藏中,当源集合发生变化时,我们
ColumnDefinition
Row
和Column
属性的类中,并首先,XAML:
<UserControl x:Class="WpfApplication1.MyControl"
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"
x:Name="ThisControl"
d:DesignHeight="300" d:DesignWidth="300">
<ItemsControl x:Name="ItemsControl"
ItemsSource="{Binding BindableItems, ElementName=ThisControl, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Grid.Row" Value="{Binding Row}" />
<Setter Property="Grid.Column" Value="{Binding Column}" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ContentPresenter Content="{Binding Content}"
ContentTemplate="{Binding ItemTemplate, ElementName=ThisControl}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</UserControl>
代码隐藏:
using System.Collections;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
"ItemsSource", typeof (IEnumerable), typeof (MyControl),
new PropertyMetadata(default(IEnumerable), OnItemsSourceChanged));
public IEnumerable ItemsSource
{
get { return (IEnumerable) GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
// This is the DataTemplate that the consumer of your control specifies
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register(
"ItemTemplate", typeof (DataTemplate), typeof (MyControl), new PropertyMetadata(default(DataTemplate)));
public DataTemplate ItemTemplate
{
get { return (DataTemplate) GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
// This is declared private, because it is only to be consumed by this control
private static readonly DependencyProperty BindableItemsProperty = DependencyProperty.Register(
"BindableItems", typeof (ObservableCollection<object>), typeof (MyControl), new PropertyMetadata(new ObservableCollection<object>()));
private ObservableCollection<object> BindableItems
{
get { return (ObservableCollection<object>) GetValue(BindableItemsProperty); }
set { SetValue(BindableItemsProperty, value); }
}
private static void OnItemsSourceChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var myControl = dependencyObject as MyControl;
if (myControl == null)
{
return;
}
// Get reference to the Grid using reflection. You could also walk the tree.
var grid = (Grid) typeof (ItemsControl).InvokeMember("ItemsHost",
BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance,
null, myControl.ItemsControl, null);
var columns = grid.ColumnDefinitions;
columns.Clear();
myControl.BindableItems.Clear();
var items = args.NewValue as IEnumerable;
if (items != null)
{
var columnIndex = 0;
foreach (var item in items)
{
columns.Add(new ColumnDefinition{ Width = GridLength.Auto });
var container = new MyItem
{
Row = columnIndex,
Column = columnIndex++,
Content = item
};
myControl.BindableItems.Add(container);
}
}
}
}
public class MyItem
{
public object Content { get; set; }
public int Row { get; set; }
public int Column { get; set; }
}
}