web-dev-qa-db-ja.com

UserControlと親ウィンドウの間でWPFコマンドをバインドする方法

まず、写真に話しかけることから始めます。

MVVM User Control to Window wireframe

ご覧のとおり、親ウィンドウのDataContextへのバインドをサポートするWPFユーザーコントロールを作成します。ユーザーコントロールは、ボタンとカスタムItemTemplateを備えたListBoxであり、LabelとRemove Buttonを表示します。

[追加]ボタンは、メインビューモデルでICommandを呼び出して、新しいもの(IThingのインスタンス)を選択する際にユーザーと対話する必要があります。ユーザーコントロールのListBoxItemの[削除]ボタンも同様に、メインビューモデルのICommandを呼び出して、関連するものの削除を要求します。そのためには、[削除]ボタンは、削除を要求しているものに関する特定の情報をビューモデルに送信する必要があります。したがって、このコントロールにバインドできるコマンドには2つのタイプがあります。 AddThingCommand()やRemoveThingCommand(IThing thing)のようなもの。

Clickイベントを使用して機能を動作させましたが、それはハック感があり、XAMLの背後に大量のコードを生成し、元のMVVM実装の残りをこすります。本当に普通にコマンドとMVVMを使いたいです。

基本的なデモを機能させるのに十分なコードが含まれているので、混乱を減らすためにすべてを投稿することを控えています。動作しているので、ListBoxのDataTemplateがLabelを正しくバインドし、親ウィンドウがコレクションにアイテムを追加すると、それらが表示されます。

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

必要なのは、[追加]ボタンの「基本的な」修正です。これにより、親ウィンドウのコマンドを呼び出して物を追加し、[削除]ボタンのより複雑な修正を行います。そのバインドされたもの。

洞察に感謝します、

22
Todd Sprang

これは簡単なことであり、UserControlをコントロール(つまり、たまたま他のコントロールから構成されている)のように扱うことで実現されています。どういう意味ですか?つまり、他のコントロールと同様に、ViewModelをバインドできるUCにDependencyPropertiesを配置する必要があります。ボタンはCommandプロパティを公開し、TextBoxesはTextプロパティなどを公開します。ジョブを実行するために必要なすべてをUserControlの表面に公開する必要があります。

ささいな(2分以内に一緒に投げる)例を見てみましょう。 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>

必要なものすべてのプロパティを公開しているアイテムエディタがあることに注意してください。編集中のアイテムのリスト、新しいアイテムを追加するコマンド、アイテムを削除するコマンドです。

次に、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の表面に定義されたDPにバインドします。このアンチパターンはより複雑なUC実装を破壊するので、DataContext=this;のようなナンセンスはしないでください。

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

UCの表面上のちょうどDP。大したことない。 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));
    }
}

3つの異なるコレクションを編集しているので、追加または削除するものを明確にするために、より多くのICommandsを公開することができます。または、安価にCommandParameterを使用して計算することもできます。

35
Will

以下のコードを参照してください。 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);
    }
}

Baseコマンドの代わりに、MVVMLightのRelayCommandまたはPRISMライブラリのDelegateCommandを試すことができます。

デフォルトでは、ユーザーコントロールはコンテナのDataContextを継承します。したがって、ウィンドウが使用するViewModelクラスは、XAMLのバインディング表記を使用して、ユーザーコントロールによって直接バインドできます。 DependentPropertiesやRoutedEventsを指定する必要はなく、通常どおりコマンドプロパティにバインドするだけです。

2
Rye bread