18

I am trying to display a login window once my MainWindow loads while sticking to the MVVM pattern. So I am trying to Bind my main windows Loaded event to an event in my viewmodel. Here is what I have tried:

MainWindowView.xaml

 <Window x:Class="ScrumManagementClient.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        DataContext="ViewModel.MainWindowViewModel"
        Loaded="{Binding ShowLogInWindow}">
    <Grid>

    </Grid>
 </Window>

MainWindowViewModel.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScrumManagementClient.ViewModel
{
    class MainWindowViewModel : ViewModelBase
    {
        public void ShowLogInWindow(object sender, EventArgs e)
        {
            int i = 0;
        }
    }
}

The error message I am getting is "Loaded="{Binding ShowLogInWindow}" is not valid. '{Binding ShowLogInWindow}' is not a valid event handler method name. Only instance methods on the generated or code-behind class are valid."

Eamonn McEvoy
  • 8,349
  • 13
  • 49
  • 78

5 Answers5

36

You're going to have to use the System.Windows.Interactivity dll.

Then add the namespace in your XAML:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

Then you can do stuff like:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Loaded">
        <i:InvokeCommandAction Command="{Binding MyICommandThatShouldHandleLoaded}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

Please note that you will have to use an ICommand (or DelegateCommand is you use Prism, or RelayCommand if you use MVVMLight), and the DataContext of your Window must hold that ICommand.

Louis Kottmann
  • 15,133
  • 4
  • 56
  • 85
7

Use Attached Behavior. That is allowed in MVVM ....

(code below may / may not compile just like that)

XAML ...

   <Window x:Class="..."
           ...
           xmlns:local="... namespace of the attached behavior class ..."
           local:MyAttachedBehaviors.LoadedCommand="{Binding ShowLogInWindowCommand}">
     <Grid>
     </Grid>
  </Window> 

Code Behind...

  class MainWindowViewModel : ViewModelBase
  {
      private ICommand _showLogInWindowCommand;

      public ICommand ShowLogInWindowCommand
      {
         get
         {
              if (_showLogInWindowCommand == null)
              {
                  _showLogInWindowCommand = new DelegateCommand(OnLoaded)
              }
              return _showLogInWindowCommand;
         }
      }

      private void OnLoaded()
      {
          //// Put all your code here....
      }
  } 

And the attached behavior...

  public static class MyAttachedBehaviors
  {
      public static DependencyProperty LoadedCommandProperty
        = DependencyProperty.RegisterAttached(
             "LoadedCommand",
             typeof(ICommand),
             typeof(MyAttachedBehaviors),
             new PropertyMetadata(null, OnLoadedCommandChanged));

      private static void OnLoadedCommandChanged
           (DependencyObject depObj, DependencyPropertyChangedEventArgs e)
      {
          var frameworkElement = depObj as FrameworkElement;
          if (frameworkElement != null && e.NewValue is ICommand)
          {
               frameworkElement.Loaded 
                 += (o, args) =>
                    {
                        (e.NewValue as ICommand).Execute(null);
                    };
          }
      }

      public static ICommand GetLoadedCommand(DependencyObject depObj)
      {
         return (ICommand)depObj.GetValue(LoadedCommandProperty);
      }

      public static void SetLoadedCommand(
          DependencyObject depObj,
          ICommand  value)
      {
         depObj.SetValue(LoadedCommandProperty, value);
      }
  }

DelegateCommand source code can be found on the internet... Its the most suited ICommand API available for MVVM.

edit:19.07.2016 two minor syntax errors fixed

Community
  • 1
  • 1
WPF-it
  • 18,785
  • 6
  • 51
  • 68
  • I don't believe making an attached behavior is the classic way to go, interaction triggers are widely used. Besides, he's gonna have to handle CommandParameter, CanExecute etc... don't reinvent the wheel! – Louis Kottmann Oct 25 '11 at 11:40
  • 6
    I dont think Interactivity is `classic`! I am not a fan of interactivity due to various click once and redeployment issues it poses. I even observed it to be going a little unreliable for deep control templates. Plus I find the control and ownership of functionality much comforting in custom attached behaviors. – WPF-it Oct 25 '11 at 12:18
3

Update:

I made a post about a new more flexible version of the method binding that uses a slightly different syntax here:

http://www.singulink.com/CodeIndex/post/updated-ultimate-wpf-event-method-binding

The full code listing is available here:

https://gist.github.com/mikernet/7eb18408ffbcc149f1d9b89d9483fc19

Any future updates will be posted to the blog so I suggest checking there for the latest version.

Original Answer:

.NET 4.5+ supports markup extensions on events now. I used this to create a method binding that can be used like this:

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

More details can be found here: http://www.singulink.com/CodeIndex/post/building-the-ultimate-wpf-event-method-binding-extension

The full class code is available here: https://gist.github.com/mikernet/4336eaa8ad71cb0f2e35d65ac8e8e161

Mike Marynowski
  • 2,668
  • 18
  • 29
  • This piece of code is incredibly useful, it should be upvoted. I had some issues with it and tweaked the code to get it working in my solution https://gist.github.com/alexis-/1b1d8b9791cba7c6fae5905c81d22766 – Alexis Sep 22 '16 at 20:02
  • I will post an update with some newer code in a few hours, I just haven't had time. This was a quick proof of concept and wasn't fully tested yet. The version we use now works a little bit differently - The first parameter is the target object (which can accept a full binding), the second parameter is the name of the method on the target, and the rest are the parameters to pass in. This gives you more flexibility to target different bindings. – Mike Marynowski Sep 23 '16 at 02:18
  • Do you mind telling me what issue you had so I can make sure the newer version doesn't have it? – Mike Marynowski Sep 23 '16 at 02:21
  • Certainly, I have commented your gist. A quick thought: this could actually be turned into some kind of multi-target dynamic binder - i.e. adjust the provided value depending TargetProperty type : EventInfo, MethodInfo, ... – Alexis Sep 24 '16 at 09:34
  • I will post a short update article on my blog and the updated code that we use now and then update this answer. We took out all the throw options and replaced it with debug.write as bindings / converters / etc should never throw. Also fixed a bug where it would crash when used inside templates. Working on it now :) – Mike Marynowski Sep 24 '16 at 13:36
  • Will look into it, thanks for the heads up. Please, do provide a license (preferably not a viral one -- ideally it would be MIT-compliant if you are comfortable with that). Alex. – Alexis Sep 24 '16 at 14:48
0

A more generic way using behaviors is proposed at AttachedCommandBehavior V2 aka ACB and it even supports multiple event-to-command bindings,

Here is a very basic example of use:

<Window x:Class="Example.YourWindow"
        xmlns:local="clr-namespace:AttachedCommandBehavior;assembly=AttachedCommandBehavior"
        local:CommandBehavior.Event="Loaded"
        local:CommandBehavior.Command="{Binding DoSomethingWhenWindowIsLoaded}"
        local:CommandBehavior.CommandParameter="Some information"
/>
JoanComasFdz
  • 2,562
  • 4
  • 30
  • 42
  • This works in the short term, but what about when we want to hook a second event, e.g. the `Unloaded` command? – ANeves thinks SE is evil Feb 29 '16 at 12:38
  • @ANeves Then I think that moving to the Interaction namespaces is the way to go: etc. More Info: http://stackoverflow.com/questions/1048517/wpf-calling-commands-via-events – JoanComasFdz Mar 01 '16 at 11:40
  • Yes, thnak you for helping. The point I wanted to make was that this approach is not very useful - I can use `local:MyAttachedBehaviors.LoadedCommand="{Binding ShowLogInWindowCommand}"` for simplicity, or the Interaction dll for complexity. This in-between solution is interesting, but I don't think it would be very useful. – ANeves thinks SE is evil Mar 01 '16 at 11:48
-1

For VS 2013 Update 5 I wasn't able to get around "Unable to cast object of type 'System.Reflection.RuntimeEventInfo' to type 'System.Reflection.MethodInfo". Instead in a "Core" directory I made a simple interface

interface ILoad
    {
        void load();
    }

My viewModel already had the load() function implementing ILoad. In my .xaml.cs I call the ViewModel load() through ILoad.

private void ml_Loaded(object sender, RoutedEventArgs e)
{
    (this.ml.DataContext as Core.ILoad).load();
}

The xaml.cs knows nothing about the ViewModel except the POCO ILoad, the ViewModel knows nothing about the xaml.cs. The ml_loaded event is mapped to ViewModel load().

Brad Larson
  • 168,330
  • 45
  • 388
  • 563
user2584621
  • 1,635
  • 2
  • 11
  • 9