2016-06-17 3 views
1

Я пытаюсь создать повторно используемый элемент управления, который может отображать список введенных значений и возможность удаления значений. Способ, которым он представлен, должен основываться на «DisplayMemberPath» или быть шаблоном. Это близко к тому, чего я пытаюсь достичь.Пользовательский UserControl с templating

[! [Вдохновение] [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"/> 

И у пользователя UserCo ntrol обрабатывает остальные. Там, где я отстаю, мы включаем «DisplayMemberPath» в шаблон, который может использовать ItemsControl.

Любые подсказки о том, как это сделать?

/Edit: Решение

Я создал пользовательский элемент управления, как это:

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

И использование это strai GHT вперед

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

Я рекомендую сделать CustomControl, полученный из ItemsControl вместо UserControl. Вы можете использовать эту тему, плюс вы можете использовать {TemplateBinding} – lokusking

+0

. Я действительно занимался этим, но я не мог найти многого, добавляя к нему текстовое поле. У вас есть фрагменты кода или лучше, некоторые документы. То, что я могу найти в MSDN, не показалось мне полезным. – hvidgaard

ответ

1

Я перевел код из UserC ontrol к 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); 
    } 

    } 

стиле (Переместить Generic!)

<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 связанных элементов.

+0

Пока все хорошо, но использование далека от идеала. ItemTemplate будет повторяться в 10+ местах, только показанное свойство будет отличаться. Может ли это быть в стиле? – hvidgaard

+0

Я не понимаю ваш вопрос. Это Itemtemplate. Он применяется к каждому элементу в вашем Itemssource. Не стесняйтесь редактировать стиль по своему усмотрению. Я в основном скопировал ваш код и переместил его в CustomControl, чтобы показать вам преимущества TemplateBinding – lokusking

+0

. Кроме того, вы можете поместить свой ItemTemplate непосредственно в ControlTemplate. Поэтому вам не нужно это делать, когда вы его используете. – lokusking

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

Edit сделайте Binding в вашем UserControl, как это и затем в ваш ViewModel просто удалить запись из вашего Коллекция:

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

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

Это работает для отображения правильного свойства, но я до сих пор не могу связать ничего с поставляемым RemoveCommand, так как он находится в шаблоне, и я не могу привязываться к отдельным элементам. – hvidgaard

+0

Я неправильно интерпретировал его. Я обновил вопрос, чтобы отразить текущие проблемы, а именно привязку в шаблоне, который нуждается в данных из свойства в коде. – hvidgaard