【问题标题】:wpf applying datatemplates in code-behindwpf 在代码隐藏中应用数据模板
【发布时间】:2014-06-04 20:40:25
【问题描述】:

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

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

-- 编辑--

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

【问题讨论】:

  • 使用 DataTemplateSelector 选择模板。
  • 不要做代码背后的东西。正确学习 WPF 并使用正确的 XAML 和 DataBinding
  • @HighCore 请提供更有用的评论。我已经编辑了我的问题,以更清楚地说明我在做什么。如果你能告诉我如何通过 XAML 做到这一点,那就太好了。如果我已经知道该怎么做,我就不会在这里问这个问题了。
  • @ryan0270 因为我现在很忙,所以我无法提供完整的答案。我有数百个答案来解释如何正确使用 WPF,而不是代码背后的东西(我可以用它来将您的问题标记为重复并关闭它)。搜索我的回答历史或谷歌“stackoverflow highcore delete all your code”,你会得到它。
  • 让我们首先努力成为一个乐于助人的社区,其次才是坚持者。众所周知,向自定义控件添加属性意味着在代码隐藏中工作; OP 试图了解所有部分是如何连接在一起的。如果您发现一个真正相关且答案不错的重复问题,请发布链接。

标签: wpf datatemplate


【解决方案1】:

您的控件可以公开自己的属性,您可以在代码隐藏中声明这些属性。 如果您需要单个DataTemplate,则可以公开DataTemplate 类型的属性。当用户在 XAML 中声明您的控件类型时,她可以提供模板:

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

在您自己的控件中,您可以通过绑定到DataTemplate 属性来使用它。请务必在您的Binding 中引用控件本身,而不是DataContext。如果用户未指定DataTemplate,您可能需要默认的DataTemplate 或抛出有用的Exception。

如果您公开 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. 将每个项目包装在另一个公开 Row 和 Column 属性的类中,并且
  3. 将每个包装的项目添加到私有集合中,这是我们在控件中实际绑定的。

首先,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; }
  }
}

【讨论】:

  • 对不起,我还是很困惑。首先,绑定将在一端具有 DataTemplate,但另一端是什么?其次,如果控件的ItemsSource绑定到一个字符串列表,例如,如何将每个字符串通过模板传递,然后在相应的网格单元格中显示?
  • @ryan0270 我添加了一个示例供参考。让我知道这是否有助于解决问题。
  • -1。您不会将常规 CLR 属性放在 UI 元素中。你的代码是错误的。请改用正确的 DependencyProperties。顺便说一句,看起来你正在重新发明ItemsControl。这种事没必要。
  • 这被明确标记为 example,因为我不知道 OP 的确切用例。
  • 我仍然对如何进行布局感到困惑。如何指定将数据模板应用到第一个源项的结果应该放在网格的单元格 (0,0) 中,第二次的结果应该放在单元格 (0,1) 中,等等。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多