web-dev-qa-db-ja.com

WPF MVVM:コマンドは簡単です。 ViewとViewModelをRoutedEventに接続する方法

リソースディクショナリ内にDataTempateとして実装されたビューがあるとします。そして、対応するViewModelがあります。バインドコマンドは簡単です。しかし、ビューにListBoxなどのコントロールが含まれていて、リストで変更されるアイテムに基づいて(PrismのEvent Aggreagtorを使用して)アプリケーション全体のイベントを公開する必要がある場合はどうなりますか。

listBoxがコマンドをサポートしている場合は、それをViewModelのコマンドにバインドして、イベントを公開できます。しかし、リストボックスはそのようなオプションを許可していません。どうすればこれを橋渡しできますか?

編集:多くの素晴らしい答え。

このリンクを見てください http://blogs.Microsoft.co.il/blogs/tomershamam/archive/2009/04/14/wpf-commands-everywhere.aspx

ありがとう

アリエル

16
ArielBH

1つのオプションは、問題のコントロールを拡張し、必要な特定のコマンドのサポートを追加することです。たとえば、ItemActivatedイベントと関連コマンドをサポートするために 以前にListViewを変更 しました。

8
Kent Boogaart

アイテムが変更されたときにコマンドをバインドしようとする代わりに、別の方法で問題を調べました。

ListBoxの選択したアイテムをViewModelのプロパティにバインドすると、そのプロパティが変更されたときにイベントを公開できます。そうすれば、ViewModelはイベントのソースのままであり、アイテムの変更によってトリガーされます。これは、必要なものです。

<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />

.。

public class ViewModel
{
    public IEnumerable<Item> Items { get; set; } 

    private Item selectedItem;
    public Item SelectedItem
    {
        get { return selectedItem; }
        set
        {
            if (selectedItem == value)
                return;
            selectedItem = value;
            // Publish event when the selected item changes
        }
}
43

コントロールを拡張してICommandSourceをサポートし、コマンドをトリガーするアクションを決定します。

これをコンボボックスで行い、コマンドのトリガーとしてOnSelectionChangedを使用しました。最初に、コマンドをCommandComboBoxと呼んだ拡張Control ComboBoxにバインドする方法をXAMLで示し、次にICommandSourceのサポートをComboBoxに追加するCommandComboBoxのコードを示します。

1)XAMLコードでCommandComboBoxを使用する:

XAML名前空間宣言には次のものが含まれます

   xmlns:custom="clr-namespace:WpfCommandControlsLibrary;Assembly=WpfCommandControlsLibrary">

ComboBoxの代わりにCommandComboBoxを使用し、次のようにコマンドをバインドします。この例では、ViewModelにSetLanguageCommandというコマンドを定義し、このComboBoxに選択した値をパラメーターとしてコマンドに渡していることに注意してください。

 <custom:CommandComboBox 
    x:Name="ux_cbSelectLanguage"
    ItemsSource="{Binding Path = ImagesAndCultures}"
    ItemTemplate="{DynamicResource LanguageComboBoxTemplate}"           
    Command="{Binding Path=SetLanguageCommand, Mode=Default}"
    CommandParameter="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=SelectedValue, Mode=Default}"
    IsSynchronizedWithCurrentItem="True" 
    HorizontalAlignment="Right" 
    VerticalAlignment="Center" 
    Grid.Column="1" Margin="0,0,20,0" Style="{DynamicResource GlassyComboBox}" ScrollViewer.IsDeferredScrollingEnabled="True"
 />

2)CommandComboBoxのコード

CommandComboBox.csファイルのコードを以下に示します。このファイルをWpfCommandControlsLibraryというクラスライブラリに追加して別のプロジェクトにしたので、拡張コマンドを使用するために必要なソリューションに簡単に追加でき、WPFコントロールを簡単に追加して、ICommandSourceインターフェイスをサポートするように拡張できました。

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace WpfCommandControlsLibrary
{
   /// <summary>
   /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
   ///
   /// Step 1a) Using this custom control in a XAML file that exists in the current project.
   /// Add this XmlNamespace attribute to the root element of the markup file where it is 
   /// to be used:
   ///
   ///     xmlns:MyNamespace="clr-namespace:WpfCommandControlsLibrary"
   ///
   ///
   /// Step 1b) Using this custom control in a XAML file that exists in a different project.
   /// Add this XmlNamespace attribute to the root element of the markup file where it is 
   /// to be used:
   ///
   ///     xmlns:MyNamespace="clr-namespace:WpfCommandControlsLibrary;Assembly=WpfCommandControlsLibrary"
   ///
   /// You will also need to add a project reference from the project where the XAML file lives
   /// to this project and Rebuild to avoid compilation errors:
   ///
   ///     Right click on the target project in the Solution Explorer and
   ///     "Add Reference"->"Projects"->[Select this project]
   ///
   ///
   /// Step 2)
   /// Go ahead and use your control in the XAML file.
   ///
   ///     <MyNamespace:CustomControl1/>
   ///
   /// </summary>

   public class CommandComboBox : ComboBox, ICommandSource
   {
      public CommandComboBox() : base()
      {
      }

  #region Dependency Properties
  // Make Command a dependency property so it can use databinding.
  public static readonly DependencyProperty CommandProperty =
      DependencyProperty.Register(
          "Command",
          typeof(ICommand),
          typeof(CommandComboBox),
          new PropertyMetadata((ICommand)null,
          new PropertyChangedCallback(CommandChanged)));

  public ICommand Command
  {
     get
     {
        return (ICommand)GetValue(CommandProperty);
     }
     set
     {
        SetValue(CommandProperty, value);
     }
  }

  // Make CommandTarget a dependency property so it can use databinding.
  public static readonly DependencyProperty CommandTargetProperty =
      DependencyProperty.Register(
          "CommandTarget",
          typeof(IInputElement),
          typeof(CommandComboBox),
          new PropertyMetadata((IInputElement)null));

  public IInputElement CommandTarget
  {
     get
     {
        return (IInputElement)GetValue(CommandTargetProperty);
     }
     set
     {
        SetValue(CommandTargetProperty, value);
     }
  }

  // Make CommandParameter a dependency property so it can use databinding.
  public static readonly DependencyProperty CommandParameterProperty =
      DependencyProperty.Register(
          "CommandParameter",
          typeof(object),
          typeof(CommandComboBox),
          new PropertyMetadata((object)null));

  public object CommandParameter
  {
     get
     {
        return (object)GetValue(CommandParameterProperty);
     }
     set
     {
        SetValue(CommandParameterProperty, value);
     }
  }

  #endregion

  // Command dependency property change callback.
  private static void CommandChanged(DependencyObject d,
      DependencyPropertyChangedEventArgs e)
  {
     CommandComboBox cb = (CommandComboBox)d;
     cb.HookUpCommand((ICommand)e.OldValue, (ICommand)e.NewValue);
  }

  // Add a new command to the Command Property.
  private void HookUpCommand(ICommand oldCommand, ICommand newCommand)
  {
     // If oldCommand is not null, then we need to remove the handlers.
     if (oldCommand != null)
     {
        RemoveCommand(oldCommand, newCommand);
     }
     AddCommand(oldCommand, newCommand);
  }

  // Remove an old command from the Command Property.
  private void RemoveCommand(ICommand oldCommand, ICommand newCommand)
  {
     EventHandler handler = CanExecuteChanged;
     oldCommand.CanExecuteChanged -= handler;
  }

  // Add the command.
  private void AddCommand(ICommand oldCommand, ICommand newCommand)
  {
     EventHandler handler = new EventHandler(CanExecuteChanged);
     canExecuteChangedHandler = handler;
     if (newCommand != null)
     {
        newCommand.CanExecuteChanged += canExecuteChangedHandler;
     }
  }
  private void CanExecuteChanged(object sender, EventArgs e)
  {

     if (this.Command != null)
     {
        RoutedCommand command = this.Command as RoutedCommand;

        // If a RoutedCommand.
        if (command != null)
        {
           if (command.CanExecute(CommandParameter, CommandTarget))
           {
              this.IsEnabled = true;
           }
           else
           {
              this.IsEnabled = false;
           }
        }
        // If a not RoutedCommand.
        else
        {
           if (Command.CanExecute(CommandParameter))
           {
              this.IsEnabled = true;
           }
           else
           {
              this.IsEnabled = false;
           }
        }
     }
  }

  // If Command is defined, selecting a combo box item will invoke the command;
  // Otherwise, combo box will behave normally.
  protected override void OnSelectionChanged(SelectionChangedEventArgs e)
  {
     base.OnSelectionChanged(e);

     if (this.Command != null)
     {
        RoutedCommand command = Command as RoutedCommand;

        if (command != null)
        {
           command.Execute(CommandParameter, CommandTarget);
        }
        else
        {
           ((ICommand)Command).Execute(CommandParameter);
        }
     }
  }

  // Keep a copy of the handler so it doesn't get garbage collected.
  private static EventHandler canExecuteChangedHandler;

  }
}
17
eesh

このタイプの問題に対する優れた解決策は、添付プロパティの使用から得られます。 Marlon Grechは、 Attached Command Behaviors を作成することにより、AttachedPropertiesの使用法を次のレベルに引き上げました。これらを使用すると、ViewModelに存在する任意のコマンドをビューに存在する任意のイベントにバインドできます。

これは、ListBoxで同様の問題を処理するためによく使用するもので、ダブルクリックで開いたり、編集したり、何らかのアクションを実行したりします。

この例では、古いバージョンのAttached Command Behaviorsを使用していますが、効果は同じです。明示的にキー設定しているListBoxItemsに使用されるスタイルがあります。ただし、コマンドをはるかに高いレベルに設定するすべてのListBoxItemsに適用するアプリケーションまたはウィンドウ全体のスタイルを作成するのは簡単です。次に、CommandBehavior.EventプロパティにアタッチされたListBoxItemのイベントが発生するたびに、アタッチされたコマンドが発生します。

<!-- acb is the namespace reference to the Attached Command Behaviors -->
<Style x:Key="Local_OpenListItemCommandStyle">
    <Setter Property="acb:CommandBehavior.Event"
            Value="MouseDoubleClick" />
    <Setter Property="acb:CommandBehavior.Command"
            Value="{Binding ElementName=uiMyListBorder, Path=DataContext.OpenListItemCommand}" />
    <Setter Property="acb:CommandBehavior.CommandParameter"
            Value="{Binding}" />
</Style>

<DataTemplate x:Key="MyView">
<Border x:Name="uiMyListBorder">
<ListBox  ItemsSource="{Binding MyItems}"
          ItemContainerStyle="{StaticResource local_OpenListItemCommandStyle}" />
</Border>
</DataTemplate>
2
rmoore

私はこれを行うためのビヘイビアー(添付プロパティ)を作成してきましたが、それが必要な場合もあります。

ただし、通常の場合、イベントをコマンドにバインドするだけで、Blend SDK 4がインストールされていれば、Xamlですべてを実行できます。 System.Windows.Interactivity.dllへの参照を追加し、このアセンブリを再配布する必要があることに注意してください。

この例では、グリッドのDragEnterイベントが発生したときに、ViewModelでICommandDragEnterCommandを呼び出しています。

<UserControl xmlns:i="clr-namespace:System.Windows.Interactivity;Assembly=System.Windows.Interactivity" >
    <Grid>
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="DragEnter">
                <i:InvokeCommandAction Command="{Binding DragEnterCommand}" CommandParameter="{Binding ...}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Grid>
</UserControl>
1
Mike Fuchs

まあ、誰も答えませんでした。そこで、私はあきらめて、辞書の外にあるビューの実装を通常のUserControlに移動し、ViewModelへの参照を彼に注入しました。

これで、ListBoxがイベントを起動すると、ViewModelが呼び出され、そこからすべてが再び可能になります。

アリエル

1
ArielBH

Prism 2 を使用してみてください。

コマンドの優れた拡張機能が付属しており、多くの新しい可能性を開きます(ビジュアルツリーに関連付けるコマンドなど)。

0
Jarek Kardas