wpf在代码隐藏中应用datatemplates

时间:2014-06-03 17:48:57

标签: wpf datatemplate

我正在创建一个自定义ItemsControl,其中包含一个用于显示的网格。我希望用户能够使用datatemplates显示任何内容,但我该怎么做呢?

我知道如何创建模板,但我不知道如何应用模板以使项目在网格中正确定位(我的代码),并且每个项目都按用户需要显示(通过DataTemplate中)。

- 编辑 -

对于我的要求,似乎有点混乱。想象一下,我想从头开始创建自己的ListView,使用Grid进行布局(这不是我实际做的,但作为一个例子......)。给定用户的DataTemplate,如何使用它来确保根据模板显示每个网格单元格中的元素?

1 个答案:

答案 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)。

在代码隐藏中,当源集合发生变化时,我们

  1. 为每个项目创建ColumnDefinition
  2. 将每个项目包装在另一个公开RowColumn属性的类中,并
  3. 将每个包装的项目添加到私人集合中,这是我们在控件中实际绑定的内容。
  4. 首先,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; }
      }
    }