指定默认的 ItemTemplate

Specify default ItemTemplate

我有一个 WPF 自定义控件,其中有一个 Listview。该控件具有 ListView 的 ItemSource 和 ItemTemplate 的依赖属性。这一切都很好。我想做的是能够设置一个默认的 ItemTemplate,这样我就不会以 object.ToString() 结束列表视图中的项目。

下面是我的控件的 Xaml 样式。

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:btl="clr-namespace:Btl.Controls"
                mc:Ignorable="d">

<DataTemplate x:Key="DefaultListViewItem" DataType="btl:SelectableItem">
    <StackPanel Orientation="Horizontal">
        <CheckBox Margin="2" IsChecked="{Binding Selected}" />
        <TextBlock Margin="5,2" Text="{Binding Description}" VerticalAlignment="Center"/>
    </StackPanel>
</DataTemplate>
<Style TargetType="{x:Type btl:SelectItemsControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type btl:SelectItemsControl}">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <TextBlock x:Name="PART_Title" Margin="10"
                               Text="{Binding Path=Title,
                                        RelativeSource={RelativeSource TemplatedParent}}"
                               TextWrapping="Wrap" 
                               Visibility="Collapsed"/>
                    <GroupBox Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                        <GroupBox.Header>
                            <StackPanel Orientation="Horizontal">
                                <CheckBox x:Name="PART_EnabledCheck"  Margin="0,5" 
                                          Content="" 
                                          IsChecked="{Binding Path=EnabledCheck, Mode=TwoWay, 
                                           RelativeSource={RelativeSource TemplatedParent}}"/>
                                <TextBlock x:Name="PART_GroupTitle" VerticalAlignment="Center"
                                           Text="{Binding Path=GroupTitle,
                                        RelativeSource={RelativeSource TemplatedParent}}"/>
                            </StackPanel>
                        </GroupBox.Header>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="*"/>
                                <RowDefinition Height="Auto"/>
                            </Grid.RowDefinitions>
                            <DockPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
                                <ListView x:Name="PART_Items" 
                                          ItemsSource="{Binding ItemSourceList, 
                                                RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type btl:SelectItemsControl}}}"
                                          ItemTemplate="{Binding ItemTemplate, 
                                                RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type btl:SelectItemsControl}}}" 
                                          HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2"
                                          >
                                </ListView>
                            </DockPanel>
                            <CheckBox x:Name="PART_SelectAllCheck" Grid.Row="1" Margin="11,5" Content="Select All" 
                                      IsChecked="{Binding Selected}"/>
                        </Grid>
                    </GroupBox>

                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

这是我的控件

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;

namespace Btl.Controls
{
/// <summary>
/// 
/// </summary>
/// 
[TemplatePart(Name = "PART_Title", Type = typeof(TextBlock))]
[TemplatePart(Name = "PART_EnabledCheck", Type = typeof(CheckBox))]
[TemplatePart(Name = "PART_GroupTitle", Type = typeof(TextBlock))]
[TemplatePart(Name = "PART_Items", Type = typeof(ListView))]
[TemplatePart(Name = "PART_SelectAllCheck", Type = typeof(CheckBox))]
public class SelectItemsControl : UserControl
{

    #region DependencyProperties

    #region Title
    public string Title
    {
        get { return (string)GetValue(TitleProperty); }
        set { SetValue(TitleProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Title.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty TitleProperty =
        DependencyProperty.Register("Title", typeof(string), typeof(SelectItemsControl), new PropertyMetadata(string.Empty,
            OnTitleChanged
            ));

    private static void OnTitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {

        var control = d as TextBlock;
        if (control != null)
            if (string.IsNullOrEmpty(e.NewValue.ToString()))
        {
            control.Visibility = string.IsNullOrEmpty(e.NewValue.ToString()) ? Visibility.Collapsed : Visibility.Visible;
        }

    }


    #endregion

    #region HasEnabledCheck
    public bool HasEnabledCheck
    {
        get { return (bool)GetValue(HasEnabledCheckProperty); }
        set { SetValue(HasEnabledCheckProperty, value); }
    }

    // Using a DependencyProperty as the backing store for EnabledCheck.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty HasEnabledCheckProperty =
        DependencyProperty.Register("HasEnabledCheck", typeof(bool), typeof(SelectItemsControl), new UIPropertyMetadata(false));
    #endregion

    #region EnabledCheck
    public bool EnabledCheck
    {
        get { return (bool)GetValue(EnabledCheckProperty); }
        set { SetValue(EnabledCheckProperty, value); }
    }

    // Using a DependencyProperty as the backing store for EnabledCheck.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty EnabledCheckProperty =
        DependencyProperty.Register("EnabledCheck", typeof(bool), typeof(SelectItemsControl), new UIPropertyMetadata(true));
    #endregion

    #region GroupTitle
    public string GroupTitle
    {
        get { return (string)GetValue(GroupTitleProperty); }
        set { SetValue(GroupTitleProperty, value); }
    }

    // Using a DependencyProperty as the backing store for GroupTitle.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty GroupTitleProperty =
        DependencyProperty.Register("GroupTitle", typeof(string), typeof(SelectItemsControl), new UIPropertyMetadata(""));

    #endregion

    #region ItemSourceList
    public IEnumerable<ISelectable> ItemSourceList
    {
        get { return (IEnumerable<ISelectable>)GetValue(ItemSourceListProperty); }
        set { SetValue(ItemSourceListProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Items.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ItemSourceListProperty =
        DependencyProperty.Register("ItemSourceList", typeof(IEnumerable), typeof(SelectItemsControl));


    #endregion

    #region ItemTemplate

    public DataTemplate ItemTemplate
    {
        get { return (DataTemplate)GetValue(ItemTemplateProperty); }
        set { SetValue(ItemTemplateProperty, value); }
    }

    // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ItemTemplateProperty =
        DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(SelectItemsControl),
        new UIPropertyMetadata(default(DataTemplate)));

    #endregion


    #region DescriptionTemplate

    public DataTemplate DescriptionTemplate
    {
        get { return (DataTemplate)GetValue(DescriptionTemplateProperty); }
        set { SetValue(DescriptionTemplateProperty, value); }
    }

    // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DescriptionTemplateProperty =
        DependencyProperty.Register("DescriptionTemplate", typeof(DataTemplate), typeof(SelectItemsControl),
        new UIPropertyMetadata(default(DataTemplate), OnDescriptionTemplateChanged));

    private static void OnDescriptionTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
    }

    #endregion

    #region ItemSelected

    public bool ItemSelected
    {
        get { return (bool)GetValue(ItemSelectedProperty); }
        set { SetValue(ItemSelectedProperty, value); }
    }

    // Using a DependencyProperty as the backing store for EnabledCheck.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ItemSelectedProperty =
        DependencyProperty.Register("ItemSelectedCheck", typeof(bool), typeof(SelectItemsControl), new UIPropertyMetadata(false));

    #endregion

    #region ItemDescription



    public string ItemDescription
    {
        get { return (string)GetValue(ItemDescriptionProperty); }
        set { SetValue(ItemDescriptionProperty, value); }
    }

    // Using a DependencyProperty as the backing store for ItemDescription.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ItemDescriptionProperty =
        DependencyProperty.Register("ItemDescription", typeof(string), typeof(SelectItemsControl), new UIPropertyMetadata(""));


    #endregion

    #region SelectAllCheck
    public bool SelectAll
    {
        get { return (bool)GetValue(SelectAllProperty); }
        set { SetValue(SelectAllProperty, value); }
    }

    // Using a DependencyProperty as the backing store for SelectAll.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectAllProperty =
        DependencyProperty.Register("SelectAll", typeof(bool), typeof(SelectItemsControl), new UIPropertyMetadata(false));


    #endregion

    #endregion

    #region Private Members

    private TextBlock _partTitle;
    private CheckBox _partEnabledCheck;
    private TextBlock _partGroupTitle;
    private ListView _partItemsListView;
    private CheckBox _partSelectAllCheck;

    #endregion

    /// <summary>
    /// 
    /// </summary>
    static SelectItemsControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(SelectItemsControl), 
            new FrameworkPropertyMetadata(typeof(SelectItemsControl)));
    }

    public SelectItemsControl()
    {
        Loaded += OnLoaded;
        Unloaded += OnUnloaded;
    }

    private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
    {
        if (ItemTemplate == null)
        {
            CreateDefaultItemTemplate();
        }
        PresentationSource presentationSource = PresentationSource.FromVisual((Visual)sender);

        // Subscribe to PresentationSource's ContentRendered event
        // ReSharper disable once PossibleNullReferenceException
        presentationSource.ContentRendered += SelectItemsControl_ContentRendered;
    }

    private void SelectItemsControl_ContentRendered(object sender, EventArgs e)
    {
        // Don't forget to unsubscribe from the event
        ((PresentationSource)sender).ContentRendered -= SelectItemsControl_ContentRendered;
        ListenToSelectedCheckBoxClickEvent(_partItemsListView, true);
    }

    private void OnUnloaded(object sender, RoutedEventArgs e)
    {
        ListenToSelectedCheckBoxClickEvent(_partItemsListView, false);
    }

    private void CreateDefaultItemTemplate()
    {
        DataTemplate template = new DataTemplate { DataType = typeof(ListViewItem) };
        FrameworkElementFactory stackPanelFactory = new FrameworkElementFactory(typeof(StackPanel));
        stackPanelFactory.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);

        FrameworkElementFactory selected = new FrameworkElementFactory(typeof(CheckBox));
        selected.SetBinding(TextBlock.TextProperty, new Binding("Selected"));
        stackPanelFactory.AppendChild(selected);

        FrameworkElementFactory title = new FrameworkElementFactory(typeof(TextBlock));
        title.SetBinding(TextBlock.TextProperty, new Binding("Description"));
        stackPanelFactory.AppendChild(title);
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        // Code to get the Template parts as instance member
        _partTitle = GetTemplateChild("PART_Title") as TextBlock;
        _partEnabledCheck = GetTemplateChild("PART_EnabledCheck") as CheckBox;
        _partGroupTitle = GetTemplateChild("PART_GroupTitle") as TextBlock;
        _partItemsListView = GetTemplateChild("PART_Items") as ListView;
        //_partItemSelectedCheck = GetTemplateChild("PART_ItemSelectedCheck") as CheckBox;
        _partSelectAllCheck = GetTemplateChild("PART_SelectAllCheck") as CheckBox;

        if (_partTitle == null || _partEnabledCheck == null || _partGroupTitle == null || _partItemsListView == null ||
            _partSelectAllCheck == null)
        {
            throw new NullReferenceException("Template parts not available");
        }

        // set visibility
        _partEnabledCheck.Visibility = HasEnabledCheck ? Visibility.Visible : Visibility.Collapsed;
        _partEnabledCheck.Click += PartEnabledCheckOnClick;
        _partTitle.Visibility = string.IsNullOrEmpty(_partTitle.Text) ? Visibility.Collapsed : Visibility.Visible;
        _partGroupTitle.Visibility = string.IsNullOrEmpty(_partGroupTitle.Text) ? Visibility.Collapsed : Visibility.Visible;
        _partSelectAllCheck.Click += PartSelectAllCheckOnClick;

    }

    private void PartEnabledCheckOnClick(object sender, RoutedEventArgs routedEventArgs)
    {
        _partItemsListView.IsEnabled = EnabledCheck;
        _partSelectAllCheck.IsEnabled = EnabledCheck;
    }

    private void ListenToSelectedCheckBoxClickEvent(DependencyObject parent, bool set)
    {
        foreach (CheckBox cb in VisualTreeHelpers.FindVisualChildren<CheckBox>(parent))
        {
            BindingExpression binding = cb.GetBindingExpression(CheckBox.IsCheckedProperty);
            // ReSharper disable once PossibleNullReferenceException
            if (binding.ParentBinding.Path.Path == "Selected")
            {
                if (set)
                    cb.Click += SelectedCheckBox_Click;
                else
                    cb.Click -= SelectedCheckBox_Click;
            }
        }

    }

    private void SelectedCheckBox_Click(object sender, RoutedEventArgs e)
    {
        _partSelectAllCheck.IsChecked = !ItemSourceList.AsQueryable().Any(x => x.Selected == false);

    }

    private void PartSelectAllCheckOnClick(object sender, RoutedEventArgs routedEventArgs)
    {
        foreach (CheckBox cb in VisualTreeHelpers.FindVisualChildren<CheckBox>(_partItemsListView))
        {
            BindingExpression binding = cb.GetBindingExpression(CheckBox.IsCheckedProperty);
            // ReSharper disable once PossibleNullReferenceException
            if (binding.ParentBinding.Path.Path == "Selected")
            {
                cb.IsChecked = _partSelectAllCheck.IsChecked ?? false;
            }
        }
    }

}

}

有人可以 post 显示如何设置 - 创建默认模板的代码吗?

事实证明这比我想象的要简单。因为 ItemTemplate 绑定到依赖项 属性 我可以在那里指定默认模板。那只是离开了模板的创建。见下文。

    #region ItemTemplate

    public DataTemplate ItemTemplate
    {
        get { return (DataTemplate)GetValue(ItemTemplateProperty); }
        set { SetValue(ItemTemplateProperty, value); }
    }

    // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ItemTemplateProperty =
        DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(SelectItemsControl),
        new UIPropertyMetadata(DefaultItemTemplate));

    private static DataTemplate DefaultItemTemplate
    {
        get
        {
            // tried using a MemoryStream - StreamWriter but was getting a
            // "Root element missing error", would be nice to know why.
            var sb = new StringBuilder();

            sb.Append("<DataTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">");
            sb.Append("<StackPanel Orientation=\"Horizontal\">");
            sb.Append("<CheckBox Margin=\"2\" IsChecked=\"{Binding Selected}\" />");
            sb.Append("<TextBlock Margin=\"5,2\" Text=\"{Binding Description}\" VerticalAlignment=\"Center\"/>");
            sb.Append("</StackPanel>");
            sb.Append("</DataTemplate>");

            var myByteArray = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
            var ms = new MemoryStream(myByteArray);

            return (DataTemplate) XamlReader.Load(ms);
        }
    }

    #endregion