1

I am using the class InvokeDelegateCommandAction from AlexeyZakharov's weblog on the basis of some advice from guys that this is the best way to send back a parameter from a View to a ViewModel from an EventTrigger.

Here's what I have.

In the View (a DataGrid to be specific):

<i:Interaction.Triggers>
    <i:EventTrigger EventName="SelectionChanged" >
        <cmnwin:InvokeDelegateCommandAction 
                Command="{Binding SelectedExcludedItemChangedCommand}"
                CommandParameter="{Binding RelativeSource={RelativeSource self}, Path=SelectedItems}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

In the ViewModel:

public DelegateCommandWithParameter SelectedActiveItemChangedCommand
{
    get
    {
        return selectedActiveItemChangedCommand ??
            (selectedActiveItemChangedCommand = new DelegateCommandWithParameter(DoSelectedActiveItemsChanged, CanDoSelectedActiveItemsChanged));
    }
}

public bool CanDoSelectedActiveItemsChanged(object param)
{
    return true;
}

public void DoSelectedActiveItemsChanged(object param)
{
    if (param != null && param is List<Object>)
    {
        var List = param as List<Object>;
        MyLocalField = List;
    }
}

The new kind of DelegateCommand that allows me to pass objects as args:

public class DelegateCommandWithParameter : ICommand
{
    #region Private Fields
    private Func<object, bool> canExecute;
    private Action<object> executeAction;
    private bool canExecuteCache;
    #endregion

    #region Constructor
    public DelegateCommandWithParameter(Action<object> executeAction, Func<object, bool> canExecute)
    {
        this.executeAction = executeAction;
        this.canExecute = canExecute;
    }
    #endregion

    #region ICommand Members
    public bool CanExecute(object parameter)
    {
        bool temp = canExecute(parameter);
        if (canExecuteCache != temp)
        {
            canExecuteCache = temp;
            if (CanExecuteChanged != null)
            {
                CanExecuteChanged(this, new EventArgs());
            }
        }
        return canExecuteCache;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        executeAction(parameter);
    }
    #endregion
}

Whenever my code gets to the DoSelectedActiveItemsChanged, the arg is always NULL.... am I being a complete doofus here? Where does the CommandParamter get linked to the command args? AKA, why does the View pass nothing back to the command? Please help.

Joel B Fant
  • 24,406
  • 4
  • 66
  • 67
tigerswithguitars
  • 2,497
  • 1
  • 31
  • 53
  • 1
    did you try the same with a simple button and your command? i use this for delete rows in my datagrid and it works fine. maybe InvokeDelegateCommandAction cause the problem? – blindmeis Jun 20 '11 at 14:32
  • I will try it with a button action and the InvokeCommandAction... But won't this have to map to the View code behind? I have a requirement to not have any code here. – tigerswithguitars Jun 20 '11 at 14:37
  • Have attempted a RelayCommand method and still no banana. I will try the button method at some point. But I'm still annoyed that it's not doing what I'd like it to do/I'm not sure what to do.... – tigerswithguitars Jun 20 '11 at 15:26

2 Answers2

3

I did it with a ListBox instead, but I got the same thing. The following is fine, as it passes CommandParameter instead of the invoke parameter. So why is CommandParameter null?

protected override void Invoke( object parameter ) {
    this.InvokeParameter = parameter;

    if ( this.AssociatedObject != null ) {
        ICommand command = this.ResolveCommand();
        if ( ( command != null ) && command.CanExecute( this.CommandParameter ) ) {
            command.Execute( this.CommandParameter );
        }
    }
}

CommandParameter doesn't seem to be working appropriately because your binding is setting it to null. {RelativeSource Self} resolves to an InvokeDelegateCommandAction, and that doesn't have a SelectedItems property. Instead, use this binding:

CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBox}}, Path=SelectedItems}"

Then CommandParameter will pass in a SelectedItemCollection from the ListBox.

There is one other issue that you'll quickly discover. DoSelectedActiveItemsChanged()'s param will be an instance of SelectedItemCollection, not List<Object>.

Joel B Fant
  • 24,406
  • 4
  • 66
  • 67
  • Many thanks. I should have looked a little closer at this code. I guess you should never quite unconditionally trust this stuff, as I had done. Break points set and debugging in action. I'll have a go with it tonight and report back in the morning. Again. Many thanks. – tigerswithguitars Jun 20 '11 at 16:02
  • 1
    Had to pretty much write that twice, as my first note of a bug in `Invoke()` was incorrect. I just didn't understand yet that misdirection was the point, that another parameter should be passed, and not the `EventArgs`. – Joel B Fant Jun 20 '11 at 16:04
1

I have solved the problem with the aid of Joel's observations... For those that may be interested, here's how. Although obviously I have credited Joel rightfully with the correct answer, it seems right to put this info as a answer rather than an edit to the question.

I discovered the generic DelegateCommand, so got rid of the DelegateCommandWithParameter

public ICommand SelectedObjectsChangedCommand 
{
    get
    {
        return selectedObjectsChangedCommand ??
            (selectedObjectsChangedCommand = new DelegateCommand<MyType>(DoSelectedObjectsChangedCommand , CanDoSelectedObjectsChangedCommand ));
    }
}

The 'Do' (Execute method) now with the return of the SelectionChangedEventArgs...

public void DoSelectedObjectsChangedCommand(object param)
{
    if (param != null && param is SelectionChangedEventArgs)
    {
        foreach (MyType object in ((SelectionChangedEventArgs)param).AddedItems.Cast<MyType>().ToList())
        {
            selectedObjects.Add(object);
        }
        foreach (MyType object in ((SelectionChangedEventArgs)param).RemovedItems.Cast<MyType>().ToList())
        {
            selectedObjects.Remove(object);
        }
        UpdateAllCanDos();
    }
} 

Along with the rest of my business logic it makes for a very smooth and intuitive UX. Many thanks to all the answer-ers. I have been doing WPF and MVVM with NHibernate not for only a month and I can't help but acknowledge that the SO community are getting me over the learning curves in the best and most enriching ways possible.

tigerswithguitars
  • 2,497
  • 1
  • 31
  • 53
  • Hahaha, so the things I pointed out in the first version of my answer is closer to your new approach (using `SelectionChangedEventArgs`), rather than my edits. **:)** – Joel B Fant Jun 21 '11 at 19:09