wpf applying datatemplates in code-behind

…衆ロ難τιáo~ 提交于 2020-01-14 05:35:08

问题


I'm creating a custom ItemsControl that contains a grid for display whatever. I'd like the user to be able to use datatemplates for displaying that whatever, but how do I go about doing that?

I know how to create the templates, but I'm not sure how to go about applying a template such that the items are located properly within the grid (my code) and each item is displayed as the user wants (via the datatemplate).

-- edit --

There appears to be a little confusion as to what I'm asking. Imagine I wanted to create my own ListView from scratch using a Grid for the layout (that's not what I'm actually doing, but as an example ... ). Given a DataTemplate from the user, how do I use that to ensure the elements inside each grid cell are displayed according to the template?


回答1:


Your control can expose its own property/properties, which you declare in your code-behind. If you need a single DataTemplate, then you can expose a property of type DataTemplate. When a user declares your control type in XAML, she can provide the template:

<ns:YourControl>
    <ns:YourControl.DataTemplate>
        <DataTemplate>
            …
        </DataTemplate>
    </ns:YourControl.DataTemplate>
</ns:YourControl>

In your own control, you consume this by binding to the DataTemplate property. Be sure to reference the control itself in your Binding, rather than the DataContext. You'll probably want a default DataTemplate or throw a useful Exception in the event that the user does not specify a DataTemplate.

You can give the user some additional flexibility if you expose a property of type DataTemplateSelector and then apply that to your items, if the data types are disparate or the user is likely to want different templates under different circumstances.

Example

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.)
}

The consumer:

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

Update

Okay, here is a working example of populating the Grid and using the DataTemplate.

MyControl exposes a property, ItemsSource, which allows the consumer to bind to a collection in her view-model. MyControl also exposes a property, ItemTemplate, which allows a consumer to specify how to display those items (again, you could also allow the user to specify a DataTemplateSelector).

In the code-behind, when the source collection changes, we

  1. create a ColumnDefinition for each item,
  2. wrap each item inside another class that exposes Row and Column properties, and
  3. add each wrapped item to a private collection, which is what we actually bind to in our control.

First, the 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>

And the code-behind:

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


来源:https://stackoverflow.com/questions/24021766/wpf-applying-datatemplates-in-code-behind

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!