WPF Event Binding to ViewModel (for non-Command classes)
Asked Answered
B

5

25

I'm working an the second version of an application, and as part of the rewrite I have to move to an MVVM architecture. I'm getting pressure to put absolutely every bit of code in the view model class--having c# in the code behind file is frowned upon. (I know, I know...I understand that code behind isn't a bad thing, but it isn't my call this time).

For objects which implement the command interface, it's easy. I've been able to find a ton of information on how to bind the Command of these objects to an ICommand in the view model. The problem is for objects which don't have this interface, e.g.

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

<!-- ... -->

</ListBox>

I want to know how to bind the MouseDoubleClick event for the Listbox to myCallbackFunction, which is implemented in the view model. Is this even possible?

Thanks!

Bove answered 21/6, 2011 at 18:34 Comment(2)
Duplicate question https://mcmap.net/q/455566/-binding-commands-to-eventsVoleta
Possible duplicate of Binding Commands to Events?Approve
R
11

This isn't directly possible. It could be done via an Attached Property or Behavior, though it would still be a little tricky to find and invoke the appropriate method (this could be done via Reflection fairly easily).

That being said, this is typically handled via ICommand - For example, MVVM Light has a great EventToCommand behavior to map any event to an ICommand on the ViewModel. The advantage of using ICommand is that you can still use DataBinding, since the ICommand is exposed as a property.

Regarding answered 21/6, 2011 at 18:41 Comment(0)
S
7

WPF supports markup extensions on events as of .NET 4.5. Using that capability, I implemented a versatile method binding extension and wrote about it here:

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

Can be used to bind to a method using full property path syntax, supports bindings and other markup extensions as arguments, and automatically routes to the method that matches the signature of the arguments provided. Here are some usage examples:

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

View model method signatures:

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);
}
Snow answered 5/7, 2016 at 18:14 Comment(0)
B
4

To directly answer your question, please refer to Why to avoid the codebehind in WPF MVVM pattern? It suggests two possible things you want.

However, why do you want to bind the MouseDoubleClick of the ListBox to your ICommand in the viewmodel?

The alternative way is that you write a method in a codebehind to register the MouseDoubleClick. It is not bad due to the facts of the following.

  1. The meaningful databinding is the interaction between a view and a viewmodel. For example, when a user input some text to a TextBox, a viewmodel is also updated. On the contrary, if a viewmodel gets data from a database, it will be shown at a view. However, it is not this case that the ICommand in your viewmodel binds to the view.

  2. Of course, the CanExcute of the ICommand would important to your viewmodel, but in many cases, it is not related with the viewmodel or not concerned. In this case, the difference between the ICommand binding and the writing the codebehind is where the MouseDoubleClick event is binded with a ICommand or registered with a event handler.

Bend answered 21/6, 2011 at 20:15 Comment(3)
The MouseDoubleClick event on the ListBox was just an example that existed in the legacy code, and I may not keep that particular event. However, I want to know if there's a way to avoid the code behind for any event like this. And my code currently has a simple event handler in the code behind which invokes a method in the view model. But for political reasons at my office, I was instructed to find a way to avoid writing code in the code behind.Bove
As the top of my answer and @Reed Copsey also mentiond above, there are two possible things that you want to use, which is the Attached Property or the Behavior. They are described at my question. However, I want to know why you are encouraged to avoid writing code in the code hehind. I think that the @slugster's answer of my question would help with you.Bend
The rationale is that the business logic needs to be completely decoupled from the HMI (I'm working with "purists"). However, I've tried the solutions you linked above, but I haven't been able to get either to work yet. Every potential solution is much less elegant than simply using the code behind as an intermediary. I've decided to stick with my original code and see if I can push that through on my next peer review.Bove
T
1

One way could be to handle the event in the code behind and call appropriate method of view model from code behind

You can also go for some ready made commanding library like this tutorial which is using ACB

Twinkling answered 21/6, 2011 at 18:39 Comment(0)
A
0

Try EventBinder which will allow you to bind your method directly to any event, including your own events, without a need to wrap the method in ICommand container.

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

.NET Framework 3.0+, .NET Core 3.0+ and Avalonia are supported.

Features:

  • Binding to methods without ICommand
  • Binding to methods with return types
  • Binding to async methods
  • Binding to nested objects using . delimiter, properties and fields are supported
  • Passing user parameters of int, double, decimal or string type
  • Passing event parameters using $ sign and position number ($0, $1, etc)
  • Passing default {Binding} as a parameter

Usage:

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

Binding:

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

or

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");
Aphid answered 11/1, 2020 at 21:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.