Help with calling Delegate commands or Methods from ViewModel

Jun 28, 2011 at 4:44 PM

Tony,

I've started integrating Simple MVVM (With WCF RIA) into a new project. In addition to using RIA this project also has the need to connect directly to a WCF service. In the past I have invoke queries from this service using a standard delegate command and binding to this using command binding from the view.  I am currently trying this using the toolkit but I have experiencing a few errors. Hopefully you or someone can point me in the right direction.

From the view (Search.xaml) I have a user control (searchcontrol.xaml). The user control has a text field used for searching and then a search button

<TextBox x:Name="txtSearchField"
                 Grid.Column="0"
                 Style="{StaticResource SearchTxtBoxStyle}"
                 Text="{Binding SearchTerm, Mode=TwoWay}"
                 ToolTipService.ToolTip="{StaticResource TTsearchField}">
            <i:Interaction.Triggers>
                <ei:KeyTrigger Key="Enter" FiredOn="KeyUp">
                    <i:InvokeCommandAction Command="{Binding GetSearchResultCommand, Mode=OneWay}" />
                </ei:KeyTrigger>
            </i:Interaction.Triggers>
        </TextBox>
        <StackPanel x:Name="searchButtons"
                    Grid.Row="0"
                    Grid.Column="1"
                    Margin="3,2,5,2"
                    Orientation="Horizontal">
            <Button x:Name="SearchButton"
                    Margin="13,1,9,-1"
                    ap:AttachedProperties.TabIndex="2"
                    Content="{StaticResource btnSearch}"
                    Style="{StaticResource blackButton}"
                    ToolTipService.ToolTip="{StaticResource TTSavebtn}" >
            	<i:Interaction.Triggers>
            		<i:EventTrigger EventName="Click">
            			<ei:CallMethodAction
						TargetObject="{Binding}"
						MethodName="MyFoo"/>
            		</i:EventTrigger>
            	</i:Interaction.Triggers>
            </Button>
             
        </StackPanel>

I've set the data context within the view which is being picked up by the control so all is good there.

Where I am having issues is in the ability to properly call the commands from the view model using the SimpleMVVM toolkit delegate command or by the CallMethodAction.

my View model looks as:

public class SearchViewModel : ViewModelBase<SearchViewModel>
    {
        #region Delegates
           
        private SearchModel _DataModel = new SearchModel();       
        private string _SelectedItemID;

        #endregion

        #region Initialization and Cleanup

        //// TODO: Add a member for IXxxServiceAgent
        //private /* IXxxServiceAgent */ serviceAgent;

        // Default ctor
        public SearchViewModel() { }

        //// TODO: ctor that accepts IXxxServiceAgent
        //public SearchViewModel(/* IXxxServiceAgent */ serviceAgent)
        //{
        //    this.serviceAgent = serviceAgent;
        //}

        #endregion

        #region Commands
        public ICommand GetSearchResultCommand
        {
            get
            {
                return new DelegateCommand<string>(GetSearchResultCommandExecute);
            }
        }

        public ICommand ClearHistoryCommand
        {
            get
            {
                return new DelegateCommand<string>(ClearHistoryCommandExecute);
            }
        }

        public ICommand SaveSearchCommand
        {
            get
            {
                return new DelegateCommand<string>(SaveSearchCommandExecute);
            }
        }


        #endregion

        #region Notifications

        // TODO: Add events to notify the view or obtain data from the view
        public event EventHandler<NotificationEventArgs<Exception>> ErrorNotice;

        #endregion

        #region Properties

        #region Search Term
        
        private string _SearchTerm = string.Empty;
        public string SearchTerm
        {
            get { return this._SearchTerm; }
            set
            {
                this._SearchTerm = value;
                NotifyPropertyChanged(vm => vm.SearchTerm);
            }
        }
        #endregion

        #region Search Results

        private ObservableCollection<QueryResponse> _SearchResults = new ObservableCollection<QueryResponse>();
        public ObservableCollection<QueryResponse> SearchResults
        {
            get { return this._SearchResults; }
            set
            {
                this._SearchResults = value;
                NotifyPropertyChanged(vm => vm.SearchResults);
            }
        }

        #endregion

        #region Search History
        private ObservableCollection<SearchHistoryModel> _SearchHistory = new ObservableCollection<SearchHistoryModel>();
        public ObservableCollection<SearchHistoryModel> SearchHistory
        {
            get { return this._SearchHistory; }
            set
            {
                this._SearchHistory = value;
                NotifyPropertyChanged(vm => vm.SearchHistory);
            }
        }


        #endregion

        #region Saved Search Result

        private ObservableCollection<SavedSearchModel> _SavedSearchItems = new ObservableCollection<SavedSearchModel>();
        
        public ObservableCollection<SavedSearchModel> SavedSearchItems
        {
            get { return this._SavedSearchItems; }
            set
            {
                this._SavedSearchItems = value;
                NotifyPropertyChanged(vm => vm.SavedSearchItems);
            }
        }

        #endregion

        #region Time of Saving Search Result 
        /// <summary>
        /// to do this result may be handled someplace else. For now it is stubbed in to illustrate how a time stamp is added
        /// to a search result when it is saved and displayed within the UI
        /// </summary>
        private string _TimeOfSave;
        public string TimeOfSave
        {
            get { return _TimeOfSave; }
            set
            {
                _TimeOfSave = value;
                NotifyPropertyChanged(vm => vm.TimeOfSave);
            }
        }
        #endregion

        #endregion

        #region Methods


        public void MyFoo(object parameter)
        {
            //query
            this.SearchResults = this._DataModel.GetSearchResults(this.SearchTerm);
            //search history items
            this.SearchHistory = this._DataModel.AddSearchHistoryItem(this.SearchTerm); 
        }


        private void GetSearchResultCommandExecute(object parameter)
        {
            //query
            this.SearchResults = this._DataModel.GetSearchResults(this.SearchTerm);
            //search history items
            this.SearchHistory = this._DataModel.AddSearchHistoryItem(this.SearchTerm);
        }

        private void ClearHistoryCommandExecute(object parameter)
        {
            this.ClearSearchHistoryItems();
        }

        private void SaveSearchItemCommand(string item)
        {
            //search is saved to a temporary collection 
            //currently persists only for session
            //TODO: update data model to write to database vs. collection
            this._DataModel.SaveSearchItem(item);
        }

        private void SaveSearchCommandExecute(object parameter)
        {
            // this.SaveSearchItemCommand(this.SearchTerm);
            this.SavedSearchItems = this._DataModel.SaveSearchItem(this.SearchTerm);
        }

        private void ClearSearchHistoryItems()
        {
            //search history is maintained within the data model 
            // currently persists only for the session
            this._DataModel.ClearSearchHistoryItems();
            this.NotifyPropertyChanged(m => m.SearchHistory);
        }


        #endregion

        #region Completion Callbacks

        // TODO: Optionally add callback methods for async calls to the service agent
        
        #endregion

        #region Helpers

        // Helper method to notify View of an error
        private void NotifyError(string message, Exception error)
        {
            // Notify view of an error
            Notify(ErrorNotice, new NotificationEventArgs<Exception>(message, error));
        }

        #endregion
    }
}

When I use the key click (enter key) to call the Delegate Command GetSearchResultCommand I receive the following error:

Object reference not set to an instance of an object.

   at SimpleMvvmToolkit.DelegateCommand`1.ConvertParameter(Object parameter)
   at SimpleMvvmToolkit.DelegateCommand`1.Execute(Object parameter)
   at System.Windows.Interactivity.InvokeCommandAction.Invoke(Object parameter)
   at System.Windows.Interactivity.TriggerAction.CallInvoke(Object parameter)
   at System.Windows.Interactivity.TriggerBase.InvokeActions(Object parameter)
   at Microsoft.Expression.Interactivity.Input.KeyTrigger.OnKeyPress(Object sender, KeyEventArgs e)
   at MS.Internal.CoreInvokeHandler.InvokeEventHandler(Int32 typeIndex, Delegate handlerDelegate, Object sender, Object args)
   at MS.Internal.JoltHelper.FireEvent(IntPtr unmanagedObj, IntPtr unmanagedObjArgs, Int32 argsTypeIndex, Int32 actualArgsTypeIndex, String eventName)

 

If I try the button click to call the method I receive the following error:

Could not find method named 'MyFoo' on object of type 'SearchViewModel' that matches the expected signature.

   at Microsoft.Expression.Interactivity.Core.CallMethodAction.Invoke(Object parameter)
   at System.Windows.Interactivity.TriggerAction.CallInvoke(Object parameter)
   at System.Windows.Interactivity.TriggerBase.InvokeActions(Object parameter)
   at System.Windows.Interactivity.EventTriggerBase.OnEvent(EventArgs eventArgs)
   at System.Windows.Interactivity.EventTriggerBase.OnEventImpl(Object sender, EventArgs eventArgs)
   at System.Windows.Controls.Primitives.ButtonBase.OnClick()
   at System.Windows.Controls.Button.OnClick()
   at System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e)
   at System.Windows.Controls.Control.OnMouseLeftButtonUp(Control ctrl, EventArgs e)
   at MS.Internal.JoltHelper.FireEvent(IntPtr unmanagedObj, IntPtr unmanagedObjArgs, Int32 argsTypeIndex, Int32 actualArgsTypeIndex, String eventName)

Note: I created the MyFoo method just as a test to see if I could even hit a method from within the view model. In the code MyFoo resides above the original method that needed to be invoked. These methods then called methods within my model/service which handled the actual query.

SearchModel.cs

 public class SearchModel : ModelBase<SearchModel>
    {
        #region Delegates

        private ObservableCollection<SearchHistoryModel> SearchHistory = new ObservableCollection<SearchHistoryModel>();
        private ObservableCollection<SavedSearchModel> SavedSearches;
        
        #endregion

        #region Methods

        #region Get Search Results

        public ObservableCollection<QueryResponse> GetSearchResults(string searchQuery)
        {
            SearchClient sc = new SearchClient();
            sc.QueryCompleted +=new EventHandler<QueryCompletedEventArgs>(sc_QueryCompleted);
            sc.QueryAsync(new Query { QueryText = searchQuery });
            return this.SearchResults;
        }  
 
        void sc_QueryCompleted(object sender, QueryCompletedEventArgs e)
        {
            try
            {
                if (SearchResults != null)
                {
                    SearchResults.Clear();
                    this.SearchResults.Add(e.Result);
                }
                else
                {
                    this.SearchResults.Add(e.Result);
                }
            }
            catch(Exception ex)
            {
                ex.StackTrace.ToString();
            }
        }

..rest omitted for brevity.

I realize I am cutting in existing code and the challenge is trying to figure out what is available vs. how I used to write it. Any advice or suggestions (from you or others)  as to what I am missing would be appreciated.

Coordinator
Jun 28, 2011 at 9:48 PM

@rlcrews: Thanks very much for your question.  I think I can address your question(s) best by providing a little information about commands versus Blend event triggers.  For many scenarios I tend to favor event triggers over commands.  This is because the commanding infrastructure adds boilerplate code to the ViewModel without adding a whole lot of value.  However, the method called by an event trigger must return void and take no parameters.  The data needed by the method must come from members on the ViewModel, which works in many cases.  These can be properties that get their data from being bound to the View.  You need to remove the parameter from your MyFoo method to use it with an event trigger with a CallMethodAction.

There are some cases, however, where it makes more sense to use a command, specifically where you need to pass data to a method parameter.  This is where the generic version of DelegateCommand comes in.  Make sure that the type of the method parameter matches the type argument used by DelegateCommand<T>.  In your sample, I've noticed that you use a DelegateCommand<string> but that your method takes a parameter of object.  But I also noticed that you're not doing anything with the parameter, so in this instance it would make more sense to use an event trigger to call a method that has no parameters.

Additionally, you did run into an error that I've corrected in the most recent build of the source code.  I will incorporate this fix into the a new version of the toolkit that I plan to release in the next week.  So stay tuned. :)

Cheers,

Tony

Jun 29, 2011 at 3:03 AM

Tony,

you mentioned "you did run into an error that I've corrected in the most recent build of the source code." However, this error was generated after I downloaded and applied the latest version (2.0.0.3). Are you suggesting that I use a generic delegate command over what is provided within the Simple MVVM toolkit? Please understand I am trying to learn the best practice and would appreciate any guidance .

thank you,

randy

Jun 29, 2011 at 12:54 PM

@Tony, Thanks also for the clarification on differences between commands and event triggers.  I've worked through this a few moe times and got things working. I still receive the null parameter error when using the Simple MVVM toolkit delegate command but thanks to your advice I now know how to prevent/bypass this.

regards,

 

Randy

Coordinator
Jun 29, 2011 at 2:24 PM

@Randy, Great to hear you've made progress.  To sum up, if the method in your ViewModel does not require a parameter, you should configure the method to not have any parameters and use an Event Trigger with a CallMethodAction.  For example, the public method for MyFoo1 on your ViewModel would look like this:

public void MyFoo1() { /* code goes here */ }

Your XAML would use an event trigger like so:

<i:Interaction.Triggers>
  <i:EventTrigger EventName="Click">
    <ei:CallMethodAction
        TargetObject="{Binding}"
        MethodName="MyFoo1"/>
  </i:EventTrigger>
</i:Interaction.Triggers>

If, on the other hand, you want to pass some data to the method that cannot be derived from a property on the ViewModel, you can use a DelegateCommand<T>, but you need to make sure that the parameter on the method matches the type argument <T> specified for the command.  For example, the command and method in the ViewModel would look like this:

public ICommand MyFoo2Command { return new DelegateCommand<string>(MyFoo2); }

public void MyFoo2(string arg) { /* code goes here */ }

The XAML would use a command like so, passing a string as the CommandParameter:

<Button Command="{Binding MyFoo2Command}" CommandParameter="SomeText" />

You may be getting the null reference exception because you are not setting the CommandParameter value.

Cheers,

Tony

P.S. I plan to write a blog post on commands versus event triggers and when to use them.

Jul 13, 2011 at 7:41 PM

Hi Tony, this is my first post here so first of all thanks for the great toolkit and your replies here...

With MVVM-Light EventToCommand implementation I am able to use this XAML with elements that do not have the Command property to bind an event to a command and pass a parameter:

    xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4"

    <ListBox ...>
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="MouseDoubleClick">
                <cmd:EventToCommand Command="{Binding OpenProcCommand}"
                                    CommandParameter="{Binding ElementName=lstProcs, Path=SelectedItem}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </ListBox>

How can I achieve this with Simple MVVM Toolkit?

I tried:

    xmlns:smt="clr-namespace:SimpleMvvmToolkit;assembly=SimpleMvvmToolkit-WPF"

    <ListBox ...>
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="MouseDoubleClick">
                <smt:EventToCommand Command="{Binding OpenProcCommand}"
                                    CommandParameter="{Binding ElementName=lstProcs, Path=SelectedItem}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </ListBox>

but the SimpleMvvmToolkit namespace does not contain EventToCommand method and I was not able to find anything like that in the samples provided with the installation...

Thanks in advance,

 

Dean

Coordinator
Jul 14, 2011 at 8:48 PM
Edited Jul 22, 2011 at 4:01 PM
Hello Dean,
Good question about EventToCommand. I have only put it into the Windows Phone version of Simple MVVM Toolkit and have so far kept it out of the Silverlight and WPF versions because it is possible instead to use a Event Trigger with a CallMethodAction to directly invoke a method in a ViewModel without the need for commands. In other words, you can pretty much accomplish the same thing with an event trigger that you can with a command. Commands in general force you to have a lot of plumbing code in your ViewModel without adding very much value.
One thing a command offers is a CanExecute property that points to a method returning bool and will disable a button when the method returns false. But the implementation in Silverlight is inferior to that of WPF, requiring you to raise a CanExecuteChanged event manually. I find it much easier to simply bind the IsEnabled property of a control to bool property on the ViewModel and to call NotifyPropertyChanged when it changes, just like you would any other property on the ViewModel.
The only place where I would use a command is where you wish to pass a parameter to the method you wish to invoke. The CallMethodAction must be set to a method in the ViewModel that does not have any parameters. Usually that is not an issue, because controls in the View are generally bound to ViewModel properties, which you can refer to directly from within the target method assigned to the CallMethodAction.
So that's basically the rationale for why EventToCommand is not present in the Silverlight and WPF versions of the toolkit. That said, if there is still a fair amount of demand for it, I would consider incorporating it.

Cheers,
Tony