web-dev-qa-db-ja.com

ViewModelへのWPFイベントバインディング(非コマンドクラス用)

私はアプリケーションの2番目のバージョンを使用しています。書き換えの一環として、MVVMアーキテクチャに移行する必要があります。ビューモデルクラスに絶対にすべてのコードを配置するようにプレッシャーを感じています。 (わかっています、わかっています...背後にあるコードは悪いことではありませんが、今回の呼び出しではありません)。

コマンドインターフェイスを実装するオブジェクトの場合は簡単です。これらのオブジェクトのコマンドをビューモデルのICommandにバインドする方法に関する大量の情報を見つけることができました。問題は、このインターフェースを持たないオブジェクトです。

<ListBox
   x:Name="myListBox"
   MouseDoubleClick="myCallbackFunction">

<!-- ... -->

</ListBox>

リストボックスのMouseDoubleClickイベントをビューモデルに実装されているmyCallbackFunctionにバインドする方法を知りたいです。これは可能ですか?

ありがとう!

20
RobotNerd

これは直接可能ではありません。これは、添付プロパティまたは動作を介して行うことができますが、適切なメソッドを見つけて呼び出すのはまだ少しトリッキーです(これは、Reflectionを介してかなり簡単に行うことができます)。

そうは言っても、これは通常ICommandを介して処理されます。たとえば、MVVM Lightは、イベントをViewModelのICommandにマップするための優れた EventToCommand 動作を備えています。 ICommandはプロパティとして公開されるため、ICommandを使用する利点は、DataBindingを引き続き使用できることです。

7
Reed Copsey

WPFは、.NET 4.5以降のイベントのマークアップ拡張機能をサポートしています。その機能を使用して、信じられないほど用途の広いメソッドバインディング拡張機能を実装し、それについてここに書きました。

http://www.singulink.com/CodeIndex/post/building-the-ultimate-wpf-event-method-binding-extension

完全なプロパティパス構文を使用してメソッドにバインドするために使用でき、バインディングおよびその他のマークアップ拡張機能を引数としてサポートし、提供された引数のシグネチャと一致するメソッドに自動的にルーティングします。以下に使用例をいくつか示します。

<!--  Basic usage  -->
<Button Click="{data:MethodBinding OpenFromFile}" Content="Open" />

<!--  Pass in a binding as a method argument  -->
<Button Click="{data:MethodBinding Save, {Binding CurrentItem}}" Content="Save" />

<!--  Another example of a binding, but this time to a property on another element  -->
<ComboBox x:Name="ExistingItems" ItemsSource="{Binding ExistingItems}" />
<Button Click="{data:MethodBinding Edit, {Binding SelectedItem, ElementName=ExistingItems}}" />

<!--  Pass in a hard-coded method argument, XAML string automatically converted to the proper type  -->
<ToggleButton Checked="{data:MethodBinding SetWebServiceState, True}"
                Content="Web Service"
                Unchecked="{data:MethodBinding SetWebServiceState, False}" />

<!--  Pass in sender, and match method signature automatically -->
<Canvas PreviewMouseDown="{data:MethodBinding SetCurrentElement, {data:EventSender}, ThrowOnMethodMissing=False}">
    <controls:DesignerElementTypeA />
    <controls:DesignerElementTypeB />
    <controls:DesignerElementTypeC />
</Canvas>

    <!--  Pass in EventArgs  -->
<Canvas MouseDown="{data:MethodBinding StartDrawing, {data:EventArgs}}"
        MouseMove="{data:MethodBinding AddDrawingPoint, {data:EventArgs}}"
        MouseUp="{data:MethodBinding EndDrawing, {data:EventArgs}}" />

<!-- Support binding to methods further in a property path -->
<Button Content="SaveDocument" Click="{data:MethodBinding CurrentDocument.DocumentService.Save, {Binding CurrentDocument}}" />

モデルメソッドシグネチャを表示します。

public void OpenFromFile();
public void Save(DocumentModel model);
public void Edit(DocumentModel model);

public void SetWebServiceState(bool state);

public void SetCurrentElement(DesignerElementTypeA element);
public void SetCurrentElement(DesignerElementTypeB element);
public void SetCurrentElement(DesignerElementTypeC element);

public void StartDrawing(MouseEventArgs e);
public void AddDrawingPoint(MouseEventArgs e);
public void EndDrawing(MouseEventArgs e);

public class Document
{
    // Fetches the document service for handling this document
    public DocumentService DocumentService { get; }
}

public class DocumentService
{
    public void Save(Document document);
}
5
Mike Marynowski

質問に直接回答するには、 WPF MVVMパターンの分離コードを回避する理由/ を参照してください。

しかし、なぜListBoxのMouseDoubleClickをビューモデルのICommandにバインドするのですか?

別の方法は、MouseDoubleClickを登録するコードビハインドでメソッドを記述することです。以下の事実により悪くないです。

  1. 意味のあるデータバインディングは、ビューとビューモデルの間の相互作用です。たとえば、ユーザーがTextBoxにテキストを入力すると、ビューモデルも更新されます。逆に、ビューモデルがデータベースからデータを取得すると、ビューに表示されます。ただし、ビューモデルのICommandがビューにバインドするのはこの場合ではありません。

  2. もちろん、ICommandのCanExcuteはビューモデルにとって重要ですが、多くの場合、それはビューモデルと関連していないか、関係ありません。この場合、ICommandバインディングと分離コードの記述の違いは、MouseDoubleClickイベントがICommandにバインドされるか、イベントハンドラーに登録されるかです。

4
Jin-Wook Chung

1つの方法は、コードビハインドでイベントを処理し、コードビハインドからビューモデルの適切なメソッドを呼び出すことです。

[this チュートリアル-- [〜#〜] acb [〜#〜]

1
Haris Hasan

ICommandコンテナーでメソッドをラップする必要なしに、独自のイベントを含む任意のイベントにメソッドを直接バインドできるようにするEventBinderを試してください。

https://github.com/Serg046/EventBinder
https://www.nuget.org/packages/EventBinder

.NET Framework 3.0 +および.NET Core 3.0 +がサポートされています

特徴:

  • ICommandを使用しないメソッドへのバインド
  • 戻り値の型を持つメソッドへのバインド
  • 非同期メソッドへのバインド
  • .区切り文字を使用したネストされたオブジェクトへのバインド、プロパティおよびフィールドがサポートされています
  • Int、double、decimal、またはstring型のユーザーパラメータを渡す
  • $記号と位置番号($0$1など)を使用してイベントパラメータを渡す
  • デフォルト{Binding}をパラメーターとして渡す

使用法:

public class ViewModel
{
    public MetadataViewModel Metadata { get; } = new MetadataViewModel();

    public async Task ShowMessage(string msg, decimal centenary, double year)
    {
        await Task.Delay(0);
        MessageBox.Show(msg + centenary + year);
    }

    public class MetadataViewModel
    {
        public void ShowInfo(Window window, double windowWidth, ViewModel viewModel, object sender, MouseButtonEventArgs eventArgs)
        {
            var sb = new StringBuilder("Window width: ")
                .AppendLine(windowWidth.ToString())
                .Append("View model type: ").AppendLine(viewModel.GetType().Name)
                .Append("Sender type: ").AppendLine(sender.GetType().Name)
                .Append("Clicked button: ").AppendLine(eventArgs.ChangedButton.ToString())
                .Append("Mouse X: ").AppendLine(eventArgs.GetPosition(window).X.ToString())
                .Append("Mouse Y: ").AppendLine(eventArgs.GetPosition(window).Y.ToString());
            MessageBox.Show(sb.ToString());
        }
    }
}

バインディング:

<Window xmlns:e="clr-namespace:EventBinder;Assembly=EventBinder" Name="Wnd">
    <Rectangle Fill="LightGray" Name="Rct"
        MouseLeftButtonDown="{e:EventBinding ShowMessage, `Happy `, 20m, 20.0 }"
        MouseRightButtonDown="{e:EventBinding Metadata.ShowInfo, {Binding ElementName=Wnd},
            {Binding ElementName=Wnd, Path=ActualWidth}, {Binding}, $0, $1 }" />
</Window>

または

EventBinding.Bind(Rct, nameof(Rct.MouseLeftButtonDown),
    nameof(ViewModel.ShowMessage),
    "`Happy `", 20m, 20.0);
EventBinding.Bind(Rct, nameof(Rct.MouseRightButtonDown),
    nameof(ViewModel.Metadata) + "." + nameof(ViewModel.Metadata.ShowInfo),
    new Binding { ElementName = nameof(Wnd)},
    new Binding("ActualWidth") { ElementName = nameof(Wnd) },
    new Binding(),
    "$0", "$1");
0
Serg046