2015-03-18 4 views
14

Начну с того, что вы можете сделать несколько снимков.Как связать команды WPF между UserControl и родительским окном

MVVM User Control to Window wireframe

Итак, вы видите, я хочу, чтобы создать пользовательский элемент управления WPF, который поддерживает привязку к DataContext родительского окна. Пользовательский элемент управления - это просто Button и ListBox с пользовательским ItemTemplate для представления вещей с помощью метки и кнопки удаления.

Кнопка «Добавить» должна вызывать ICommand в модели основного вида, чтобы взаимодействовать с пользователем при выборе новой вещи (экземпляр IThing). Кнопки «Удалить» в элементе ListBoxItem в пользовательском элементе управления также должны вызвать ICommand в основной модели представления, чтобы запросить удаление связанной вещи. Чтобы это сработало, кнопка «Удалить» должна была отправить некоторую идентифицирующую информацию в модель представления о том, что требуется удалить. Таким образом, существует два типа команд, которые должны быть привязаны к этому элементу управления. Что-то вроде AddThingCommand() и RemoveThingCommand (вещь IThing).

У меня есть функциональность, работающая с использованием событий Click, но это кажется взломанным, создавая кучу кода за XAML и протирает остальную часть первоначальной реализации MVVM. Я действительно хочу использовать Commands и MVVM в обычном режиме.

Существует достаточно кода, чтобы получить базовую демонстрационную работу, я держусь за публикацию всего, чтобы уменьшить путаницу. Что работает, что заставляет меня чувствовать, что я настолько близок, что DataTemplate для ListBox правильно привязывает метку, и когда родительское окно добавляет элементы в коллекцию, они появляются.

<Label Content="{Binding Path=DisplayName}" /> 

Хотя это правильно отображает IThing, кнопка «Удалить» рядом с ним ничего не делает, когда я нажимаю на нее.

<Button Command="{Binding Path=RemoveItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}"> 

Это не очень неожиданным, поскольку конкретный пункт не предусмотрен, но кнопка Добавить не указывать ничего, и это также не вызвать команду.

<Button Command="{Binding Path=AddItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}"> 

Так что мне нужно, это «базовый» Фикс для кнопки Add, так что он вызывает команду родительского окна, чтобы добавить кое-что, и тем сложнее исправить для кнопки Remove, так что он также называет родительская команда, но также проходит по ее связанной вещи.

благодарит за любые мысли,

+0

Добавьте две свойства ICommand на поверхность UserControl. Привяжите кнопки в элементе управления к этим свойствам. Свяжите свойства с добавлением и удалением ICommands в модель представления вашего окна. Сделано и сделано. – Will

+0

Спасибо, Уилл. Я работаю с решением Ганеша и все еще испытываю трудности. Я попробую ваше предложение и дам вам знать. –

ответ

27

Это тривиально и сделано так, обрабатывая ваш UserControl как то, что он есть - элемент управления (который, как правило, состоит из других элементов управления). Что это значит? Это означает, что вы должны размещать DependencyProperties в своем UC, с которым может связываться ViewModel, как и любой другой элемент управления. Кнопки отображают свойство Command, TextBoxes выставляют свойство Text и т. Д. Вам нужно выставить на поверхности вашего UserControl все необходимое для выполнения своей работы.

Давайте рассмотрим тривиальные (заброшенные вместе в течение двух минут) примеры. Я не буду использовать ICommand.

Во-первых, наше окно

<Window x:Class="UCsAndICommands.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:t="clr-namespace:UCsAndICommands" 
     Title="MainWindow" Height="350" Width="525"> 
    <Window.DataContext> 
     <t:ViewModel /> 
    </Window.DataContext> 
    <t:ItemsEditor Items="{Binding Items}" 
        AddItem="{Binding AddItem}" 
        RemoveItem="{Binding RemoveItem}" /> 
</Window> 

Заметьте, что мы имеем наш Items редактор, который предоставляет свойства для всего, что нужно - список элементов, которые он редактирует, команда для добавления нового элемента, и команду для удаления элемента.

Далее UserControl

<UserControl x:Class="UCsAndICommands.ItemsEditor" 
      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" 
      xmlns:t="clr-namespace:UCsAndICommands" 
      x:Name="root"> 
    <UserControl.Resources> 
     <DataTemplate DataType="{x:Type t:Item}"> 
      <StackPanel Orientation="Horizontal"> 
       <Button Command="{Binding RemoveItem, ElementName=root}" 
         CommandParameter="{Binding}">Remove</Button> 
       <TextBox Text="{Binding Name}" Width="100"/> 
      </StackPanel> 
     </DataTemplate> 
    </UserControl.Resources> 
    <StackPanel> 
     <Button Command="{Binding AddItem, ElementName=root}">Add</Button> 
     <ItemsControl ItemsSource="{Binding Items, ElementName=root}" /> 
    </StackPanel> 
</UserControl> 

Мы связываем наши элементы управления дпс, определенной на поверхности UC. Пожалуйста, не делайте никаких глупостей, таких как DataContext=this;, поскольку этот анти-шаблон разбивает более сложные UC-реализации.

Вот определения этих свойств на UC

public partial class ItemsEditor : UserControl 
{ 
    #region Items 
    public static readonly DependencyProperty ItemsProperty = 
     DependencyProperty.Register(
      "Items", 
      typeof(IEnumerable<Item>), 
      typeof(ItemsEditor), 
      new UIPropertyMetadata(null)); 
    public IEnumerable<Item> Items 
    { 
     get { return (IEnumerable<Item>)GetValue(ItemsProperty); } 
     set { SetValue(ItemsProperty, value); } 
    } 
    #endregion 
    #region AddItem 
    public static readonly DependencyProperty AddItemProperty = 
     DependencyProperty.Register(
      "AddItem", 
      typeof(ICommand), 
      typeof(ItemsEditor), 
      new UIPropertyMetadata(null)); 
    public ICommand AddItem 
    { 
     get { return (ICommand)GetValue(AddItemProperty); } 
     set { SetValue(AddItemProperty, value); } 
    } 
    #endregion   
    #region RemoveItem 
    public static readonly DependencyProperty RemoveItemProperty = 
     DependencyProperty.Register(
      "RemoveItem", 
      typeof(ICommand), 
      typeof(ItemsEditor), 
      new UIPropertyMetadata(null)); 
    public ICommand RemoveItem 
    { 
     get { return (ICommand)GetValue(RemoveItemProperty); } 
     set { SetValue(RemoveItemProperty, value); } 
    }   
    #endregion 
    public ItemsEditor() 
    { 
     InitializeComponent(); 
    } 
} 

Просто Dps на поверхности UC. Нет, biggie. И наша ViewModel это так же просто

public class ViewModel 
{ 
    public ObservableCollection<Item> Items { get; private set; } 
    public ICommand AddItem { get; private set; } 
    public ICommand RemoveItem { get; private set; } 
    public ViewModel() 
    { 
     Items = new ObservableCollection<Item>(); 
     AddItem = new DelegatedCommand<object>(
      o => true, o => Items.Add(new Item())); 
     RemoveItem = new DelegatedCommand<Item>(
      i => true, i => Items.Remove(i)); 
    } 
} 

Вы редактировали три различные коллекции, так что вы можете выставить больше ICommands, чтобы понять, который вы добавление/удаление. Или вы можете дешево и использовать CommandParameter, чтобы понять это.

+4

Хотя я не уверен, что я бы назвал решение «тривиальным» :), вы объяснили это отлично, и ваше решение «просто сработало». Я вижу, что я делал некоторые из них более сложными, чем необходимо, и мне не хватало нескольких мелочей, которые все добавляли в никуда. Большое спасибо за ясный ответ. Я снова в мечте MVVM! –

+2

Возможно, стоит отметить, что использование 'x: Name =" root'' внутри настраиваемого элемента управления может быть плохой идеей, потому что, если содержащее окно использует одно и то же имя, все может стать довольно уродливым (у меня просто был пользовательский элемент управления, который связывает собственность сама по себе, потому что элемент управления был внутри того же названия, что и окно).Лучше использовать уникальное имя, например 'itemsEditorRoot', или, может быть, даже лучше, использовать что-то вроде' {RelativeSource Mode = FindAncestor, AncestorType = {x: Тип t: ItemsEditor}} '. –

+0

@SergeyTachenov Интересно, как это могло произойти, так как имена окончательно охвачены внутри их контейнеров. Я делаю это все время, и я всегда использую «root» как имя корневого элемента в определенной области. У меня никогда не было никаких проблем с этим. – Will

2

См. Нижеприведенный код. UserControl.XAML

<Grid> 
    <ListBox ItemsSource="{Binding Things}" x:Name="lst"> 
     <ListBox.ItemTemplate> 
      <DataTemplate> 
       <StackPanel Orientation="Horizontal"> 
        <TextBlock Text="{Binding ThingName}" Margin="3"/> 
        <Button Content="Remove" Margin="3" Command="{Binding ElementName=lst, Path=DataContext.RemoveCommand}" CommandParameter="{Binding}"/> 
       </StackPanel> 
      </DataTemplate> 
     </ListBox.ItemTemplate> 
    </ListBox> 
</Grid> 

Window.Xaml

<Window x:Class="MultiBind_Learning.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:MultiBind_Learning" 
    Title="Window1" Height="300" Width="300"> 
<StackPanel Orientation="Horizontal"> 
    <Button Content="Add" Width="50" Height="25" Command="{Binding AddCommnd }"/> 
    <local:UserControl2/> 
</StackPanel> 

Window.xaml.cs

public partial class Window1 : Window 
{ 
    public Window1() 
    { 
     InitializeComponent(); 
     this.DataContext = new ThingViewModel(); 
    } 
} 

ThingViewModel.cs

class ThingViewModel 
{ 
    private ObservableCollection<Thing> things = new ObservableCollection<Thing>(); 

    public ObservableCollection<Thing> Things 
    { 
     get { return things; } 
     set { things = value; } 
    } 

    public ICommand AddCommnd { get; set; } 
    public ICommand RemoveCommand { get; set; } 

    public ThingViewModel() 
    { 
     for (int i = 0; i < 10; i++) 
     { 
      things.Add(new Thing() { ThingName="Thing" +i}); 
     } 

     AddCommnd = new BaseCommand(Add); 
     RemoveCommand = new BaseCommand(Remove); 
    } 

    void Add(object obj) 
    { 
     things.Add(new Thing() {ThingName="Added New" }); 
    } 

    void Remove(object obj) 
    { 
     things.Remove((Thing)obj); 
    } 
} 

Thing.cs

class Thing :INotifyPropertyChanged 
{ 
    private string thingName; 

    public string ThingName 
    { 
     get { return thingName; } 
     set { thingName = value; OnPropertyChanged("ThingName"); } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
    private void OnPropertyChanged(string propName) 
    { 
     if (PropertyChanged != null) 
     { 
      PropertyChanged(this, new PropertyChangedEventArgs(propName)); 
     } 
    } 
} 

BaseCommand.CS

public class BaseCommand : ICommand 
{ 
    private Predicate<object> _canExecute; 
    private Action<object> _method; 
    public event EventHandler CanExecuteChanged; 

    public BaseCommand(Action<object> method) 
    { 
     _method = method;    
    } 

    public bool CanExecute(object parameter) 
    { 
     return true; 
    } 

    public void Execute(object parameter) 
    { 
     _method.Invoke(parameter); 
    } 
} 

Вместо базы команды вы можете попробовать RelayCommand из MVVMLight или DelegateCommand из библиотек PRISM.

+0

Спасибо за подробный ответ. При первом чтении это выглядит так, как будто это дает мне образец, который мне нужен, чтобы разобраться. Позвольте мне попробовать, и я скоро отправлю ответ. Благодаря! –

0

По умолчанию ваш пользовательский элемент наследует DataContext своего контейнера. Таким образом, класс ViewModel, который использует ваше окно, может быть привязан непосредственно пользовательским элементом управления, используя нотацию привязки в XAML. Нет необходимости указывать DependentProperties или RoutedEvents, просто привязывайте к свойствам команды как обычно.

Смежные вопросы