我正在尝试创建一个可重用的控件,它可以显示输入值的列表以及删除值的功能。它的呈现方式应该基于" 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="." />
答案 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);
}