自定义UserControl与模板

时间:2016-06-17 09:31:56

标签: c# wpf

我正在尝试创建一个可重用的控件,它可以显示输入值的列表以及删除值的功能。它的呈现方式应该基于" DisplayMemberPath",或者是模板化的。这接近我想要实现的目标。

[![启示] [1] [1]

到目前为止,我已经创建了一个自定义UserControl

<UserControl x:Class="MyNamespace.CriteriaView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ItemsControl x:Name="ItemsControl"
                      Grid.Row="0"
                      Padding="2,2,0,0">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Border BorderBrush="Gray"
                            BorderThickness="0.6"
                            Margin="0,0,2,2">
                        <StackPanel Orientation="Horizontal">
                            <Label Content="{Binding .}"
                                   Padding="0"
                                   Margin="1" />
                            <Button Click="ButtonBase_OnClick"
                                    Margin="1">
                                <Button.Template>
                                    <ControlTemplate>
                                        <Image Source="{DynamicResource RemoveIcon}" />
                                    </ControlTemplate>
                                </Button.Template>
                            </Button>
                        </StackPanel>

                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
        <TextBox x:Name="TextBlock"
                   Grid.Row="1"
                   PreviewKeyDown="UIElement_OnPreviewKeyDown"/>
    </Grid>
</UserControl>

背后的代码

using System;
using System.Collections;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;

namespace MyNamespace
{
    /// <summary>
    /// Interaction logic for CriteriaView.xaml
    /// </summary>
    public partial class CriteriaView : UserControl
    {
        public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
            "ItemsSource",
            typeof (IEnumerable),
            typeof (CriteriaView),
            new PropertyMetadata(default(IEnumerable)));

        public IEnumerable ItemsSource {
            get { return (IEnumerable)this.GetValue(ItemsSourceProperty); }
            set { this.SetValue(ItemsSourceProperty, value); }
        }

        public static readonly DependencyProperty DisplayMemberPathProperty = DependencyProperty.Register(
            "DisplayMemberPath",
            typeof (string),
            typeof (CriteriaView),
            new PropertyMetadata(default(string)));

        public string DisplayMemberPath {
            get { return (string)GetValue(DisplayMemberPathProperty); }
            set { SetValue(DisplayMemberPathProperty, value); }
        }

        public static readonly DependencyProperty AddCommandProperty = DependencyProperty.Register(
            "AddCommand",
            typeof (ICommand),
            typeof (CriteriaView),
            new PropertyMetadata(default(ICommand)));

        public ICommand AddCommand {
            get { return (ICommand)GetValue(AddCommandProperty); }
            set { SetValue(AddCommandProperty, value); }
        }

        public static readonly DependencyProperty RemoveCommandProperty = DependencyProperty.Register(
            "RemoveCommand",
            typeof (ICommand),
            typeof (CriteriaView),
            new PropertyMetadata(default(ICommand)));

        public ICommand RemoveCommand {
            get { return (ICommand)GetValue(RemoveCommandProperty); }
            set { SetValue(RemoveCommandProperty, value); }
        }

        public CriteriaView()
        {
            this.InitializeComponent();
        }

        public override void OnApplyTemplate() {
            base.OnApplyTemplate();
            this.ItemsControl.ItemsSource = this.ItemsSource;
        }

        private void UIElement_OnPreviewKeyDown(object sender, KeyEventArgs e) {
            if (this.AddCommand != null && (e.Key == Key.Enter || e.Key == Key.Return || e.Key == Key.Tab))
                this.AddCommand.Execute(this.TextBlock.Text);

        }

        private void ButtonBase_OnClick(object sender, RoutedEventArgs e) {
            if (RemoveCommand == null) return;
            var no = (sender as FrameworkElement)?.DataContext as int?;
            RemoveCommand.Execute(no);
        }
    }
}

期望用法

<view:CriteriaView ItemsSource="{Binding Path=Criteria.SerialNumbers}"
                   AddCommand="{Binding AddCommand}"
                   RemoveCommand="{Binding RemoveCommand}"
                   DisplayMemberPath="PropertyName"/>

让UserControl处理剩下的事情。我没说的就是整合了一个&#34; DisplayMemberPath&#34;到ItemsControl可以使用的模板中。

有关如何执行此操作的任何线索?

/编辑:解决方案

我创建了一个这样的自定义控件:

using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace MyNamespace.Views
{
    public class CriteriaView : ItemsControl
    {
        static CriteriaView()
        {
            DefaultStyleKeyProperty.OverrideMetadata(
                typeof(CriteriaView),
                new FrameworkPropertyMetadata(typeof(CriteriaView)));
        }

        public static readonly DependencyProperty AddCommandProperty = DependencyProperty.Register(
            "AddCommand",
            typeof(ICommand),
            typeof(CriteriaView),
            new PropertyMetadata(default(ICommand)));

        public ICommand AddCommand
        {
            get { return (ICommand)GetValue(AddCommandProperty); }
            set { SetValue(AddCommandProperty, value); }
        }

        public static readonly DependencyProperty RemoveCommandProperty = DependencyProperty.Register(
            "RemoveCommand",
            typeof(ICommand),
            typeof(CriteriaView),
            new PropertyMetadata(default(ICommand)));

        public ICommand RemoveCommand
        {
            get { return (ICommand)GetValue(RemoveCommandProperty); }
            set { SetValue(RemoveCommandProperty, value); }
        }

        private TextBox _box;

        public override void OnApplyTemplate()
        {
            this._box = this.GetTemplateChild("PART_AddElementTextBox") as TextBox;
            if (this._box != null) this._box.PreviewKeyDown += this.UIElement_OnPreviewKeyDown;

            base.OnApplyTemplate();
        }

        private void UIElement_OnPreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (this.AddCommand != null
                && (e.Key == Key.Enter || e.Key == Key.Return || e.Key == Key.Tab)) {
                var preCount = this.ItemsSource.Cast<object>().Count();
                this.AddCommand.Execute(this._box.Text);
                var postCount = this.ItemsSource.Cast<object>().Count();
                if (postCount != preCount) this._box.Text = String.Empty;
            }
        }
    }
}

Generic.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:views="clr-namespace:MyNamespace.Views">
    <views:DisplayMemberPathConverter x:Key="DisplayMemberPathConverter" />
    <Style TargetType="{x:Type views:CriteriaView}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type views:CriteriaView}">
                    <Border BorderBrush="Gray"
                            BorderThickness="0.6"
                            Margin="0,0,2,2">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            <ItemsControl Grid.Row="0"
                                          Padding="2,2,0,0"
                                          ItemsSource="{TemplateBinding ItemsSource}">
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <WrapPanel />
                                    </ItemsPanelTemplate>
                                </ItemsControl.ItemsPanel>
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <Border BorderBrush="Gray"
                                                BorderThickness="0.6"
                                                Margin="0,0,2,2">
                                            <StackPanel Orientation="Horizontal">
                                                <Label Padding="0"
                                                       Margin="1">
                                                    <Label.Content>
                                                        <MultiBinding Converter="{StaticResource DisplayMemberPathConverter}">
                                                            <Binding Path="."/>
                                                            <Binding Path="DisplayMemberPath"
                                                                     RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=views:CriteriaView}"/>
                                                        </MultiBinding>
                                                    </Label.Content>
                                                </Label>
                                                <Button Margin="1"
                                                        Command="{Binding RemoveCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=views:CriteriaView}}"
                                                        CommandParameter="{Binding .}">
                                                    <Button.Template>
                                                        <ControlTemplate>
                                                            <Image Source="{DynamicResource RemoveIcon}" />
                                                        </ControlTemplate>
                                                    </Button.Template>
                                                </Button>
                                            </StackPanel>
                                        </Border>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>
                            <TextBox x:Name="PART_AddElementTextBox"
                                     Grid.Row="1" />
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

转换器

using System;
using System.Globalization;
using System.Windows.Data;

namespace MyNamespace.Views
{
    public class DisplayMemberPathConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (values.Length != 2) return null;
            var prop = values[1] as string;
            var obj = values[0];
            if (prop == null || obj == null) return obj;

            var result = obj.GetType().GetProperty(prop)?.GetValue(obj, null);
            return result ?? obj;

        }
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotImplementedException(); }
    }
}

用法很简单

<view:CriteriaView ItemsSource="{Binding Path=Criteria.SerialNumbers}"
                   AddCommand="{Binding AddSerial}"
                   RemoveCommand="{Binding RemoveSerial}"
                   DisplayMemberPath="." />

2 个答案:

答案 0 :(得分:1)

我将您的代码从UserControl移动到CustomControl,以便您可以使用TemplateBinding:

控制:

public class CriteriaView : ItemsControl {

    public static readonly DependencyProperty AddCommandProperty = DependencyProperty.Register(
            "AddCommand",
            typeof(ICommand),
            typeof(CriteriaView),
            new PropertyMetadata(default(ICommand)));

    public ICommand AddCommand {
      get {
        return (ICommand)GetValue(AddCommandProperty);
      }
      set {
        SetValue(AddCommandProperty, value);
      }
    }

    public static readonly DependencyProperty RemoveCommandProperty = DependencyProperty.Register(
        "RemoveCommand",
        typeof(ICommand),
        typeof(CriteriaView),
        new PropertyMetadata(default(ICommand)));

    public ICommand RemoveCommand {
      get {
        return (ICommand)GetValue(RemoveCommandProperty);
      }
      set {
        SetValue(RemoveCommandProperty, value);
      }
    }

    private TextBox _box;

    public override void OnApplyTemplate() {
      this._box = this.GetTemplateChild("TextBlock") as TextBox;
      this._box.PreviewKeyDown += this.UIElement_OnPreviewKeyDown;
      base.OnApplyTemplate();
    }

    private void UIElement_OnPreviewKeyDown(object sender, KeyEventArgs e) {
      if (this.AddCommand != null && (e.Key == Key.Enter || e.Key == Key.Return || e.Key == Key.Tab))
        this.AddCommand.Execute(this._box.Text);

    }

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e) {
      if (RemoveCommand == null)
        return;
      var no = (sender as FrameworkElement)?.DataContext as int?;
      RemoveCommand.Execute(no);
    }

  }

风格(转向通用!)

<Style TargetType="xx:CriteriaView">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="xx:CriteriaView">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <ItemsControl Grid.Row="0" Padding="2,2,0,0">
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <WrapPanel />
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                        </ItemsControl>
                        <TextBox x:Name="TextBlock" Grid.Row="1" Text="{TemplateBinding DisplayMemberPath}"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>

    </Style>

用法:

<xx:CriteriaView ItemsSource="{Binding Path=Criteria.SerialNumbers}"
               AddCommand="{Binding AddCommand}"
               RemoveCommand="{Binding RemoveCommand}"
               DisplayMemberPath="{Binding YOURPROPERTY">
                <xx:CriteriaView.ItemTemplate>
                    <DataTemplate>
                        <Border BorderBrush="Gray"
                    BorderThickness="0.6"
                    Margin="0,0,2,2">
                            <StackPanel Orientation="Horizontal">
                                <Label Content="{Binding .}" 
                           Padding="0"
                           Margin="1"/>
                                <Button Command="{Binding RemoveCommand}"
                            CommandParameter="{Binding .}"
                            Margin="1">
                                    <Button.Template>
                                        <ControlTemplate>
                                            <Image Source="{DynamicResource RemoveIcon}" />
                                        </ControlTemplate>
                                    </Button.Template>
                                </Button>
                            </StackPanel>

                        </Border>
                    </DataTemplate>
                </xx:CriteriaView.ItemTemplate>
            </xx:CriteriaView>

正如您所看到的,一些DependencyProps已经过时,因为我们从ItemsControls派生出来并简单地给它一个ControlTemplate。现在,您可以使用绑定元素的DisplayMememberPath。

答案 1 :(得分:0)

您可以合并模板,将DependencyProperty“DisplayMemberPath”添加到UserControl,然后使用CustomValueConverter作为标签内容。

价值转换器

public class DisplayMemberPathConverter : IValueConverter
    {
        /// <summary>
        /// Convert 
        /// </summary>
        /// <param name="value">YourItemSourceItem</param>
        /// <param name="targetType"></param>
        /// <param name="parameter">DisplayMemberPath</param>
        /// <param name="culture"></param>
        /// <returns></returns>
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value.GetType().GetProperty(parameter.ToString()).GetValue(value, null);
        }
    }

并在您的ItemsControl ItemTemplate中执行类似这样的操作

 <Label Content="{Binding CriteriaView, 
    ConverterParameter=DisplayMemberPath, 
    Converter={StaticResource DisplayMemberPathConverter}}"/>
<Button Command="{Binding RemoveCommand,ElementName=CriteriaView}"
                                CommandParameter="{Binding CriteriaView}"
                                Margin="1">

编辑在您的UserControl中执行Binding,然后在ViewModel中删除集合中的条目:

public ICommand RemoveCommand => new DelegateCommand<object>(OnRemoveCommand);

        private void OnRemoveCommand(object obj)
        {
            myCriteriaViewCollection.Remove(obj as CriteriaView);
        }