Introduction
Recently, I came across a requirement in which I was supposed to data bind a command from my viewModel
to an event. In other words, my code-behind was not supposed to contain any code related to event handlers of a control.
After digging more into Prism, luckily I found my answer. The above requirement can be achieved using InvokeCommandAction
provided in Prism 5.0.
Well, so my article will elaborate more on how to achieve this.
InvokeCommandAction
InvokeCommandAction
consents us to invoke ICommand
implementation on our viewModel
to an event that exists on our control. It basically means, we are no longer bound or required to have a command property on a control in order to invoke a command.
Now, you may say you have seen this similar thing before.
And you are right because Blend SDK ships one. But the InvokeCommandAction
which Prism provides is a little bit different in two ways:
- First, it manages the state of the element. It updates the enable state of the control that is attached to based on the return value of the command
CanExecute
. So, here you are getting the same behavior that you do for any control, which is super cool. Isn't it?
- Second, it allows us to pass event arguments as a parameter to the command.
Now one may ask a question, whether we have to pass entire EventArgs
as an parameter or we can just pass the required stuff?
And the answer is YES. Of course, you are not forced to pass entire EventArgs
to your viewModel
. One can also pass specific portion of an EventArgs
which is really required. This can be achieved by setting TriggerPathParameter
on the InvokeCommandAction
.
Let's quickly jump on to the code.
Using Code
In order to explain the notion of InvokeCommandAction
, I'll create a very simple application having a ListBox
and a Label
. The Label
will be updated based on the selected item in the list.
Expectation: Whenever an item is selected in the list, we have to listen to the SelectionChanged
event and then handle that event in viewModel
through command and then update the selected text item on Label
.
Setting Up a View
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top">
<ListBox Height="100" Width="300" Margin="1,20,0,0"
ItemsSource="{Binding ListOfItems}" SelectionMode="Single"/>
<LabelFontWeight="Bold" Width="170"
Height="30" Content="Please select"/>
</StackPanel>
I am setting datacontext for this view in code-behind. But same can be done in XAML also.
Setting Up a ViewModel
Here, I'll not explain each and every line of viewModel
's code as I already covered the same in my other articles in a detailed manner.
public class MainWindowViewModel:BindableBase
{
public ObservableCollection<string> ListOfItems { get; set; }
public ICommand SelectCommand { get; set; }
private string _selectedItem = string.Empty;
public string SelectedItem
{
get { return _selectedItem; }
set { SetProperty<string>(ref _selectedItem, value); }
}
public MainWindowViewModel()
{
ListOfItems = new ObservableCollection<string>()
{"One","Two", "Three", "Four", "Five"};
SelectCommand = new DelegateCommand<object[]>(SetSelectedItem);
}
private void SetSelectedItem(object[] listOfItems)
{
if (listOfItems != null && listOfItems.Count()>0)
{
SelectedItem=listOfItems.FirstOrDefault().ToString();
}
}
}
If you will notice the parameter of DelegateCommand
, it is an object array. That's because our viewModel
is going to expect a command action in the form of object array from the list of items we want to choose from.
Next thing is to handle a SelectionChanged
event of our ListBox
but we don't want to handle that in our code-behind as we are dealing with MVVM structure. So, in order to achieve this behavior, we will use triggers.
Let's go ahead and add a reference of System.Windows.Interactivity
and add the namespace in XAML as:
xmlns:interactivity="http://schemas.microsoft.com/expression/2010/interactivity"
Now above namespace can be used to attach the interaction triggers to the attached property as:
<ListBox Height="100" Width="300"
Margin="1,20,0,0" ItemsSource="{Binding ListOfItems}"
SelectionMode="Single">
<interactivity:Interaction.Triggers>
...
</interactivity:Interaction.Triggers>
</ListBox>
Next, we need to add the EventTrigger
with EventName
property set as SelectionChanged
. Please note, your EventName
should match with the event of the control. In this case, we are interested in ListBox
's SelectionChanged
event.
Now whenever this event occurs, we want to invoke our command. So, this is where we are using our Prism's InvokeCommandAction
as:
<ListBox Height="100" Width="300"
Margin="1,20,0,0" ItemsSource="{Binding ListOfItems}"
SelectionMode="Single">
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="SelectionChanged">
<prism:InvokeCommandAction Command="{Binding SelectCommand}"/>
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
</ListBox>
Let's quickly run the application and see how it reacts.
As soon as you try to select any item in ListBox
, you will get a BIG exception:
It looks like the parameter being passed to a command is the SelectionChangedEventArgs
. We don't want that. We want something specific, we want our object. So, let's come back to our InvokeCommandAction
and set property TriggerParamaterPath
with the path of incoming EventArgs
of the trigger. In our case, we want the AddedItems
. Hence,
<ListBox Height="100" Width="300"
Margin="1,20,0,0" ItemsSource="{Binding ListOfItems}"
SelectionMode="Single">
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="SelectionChanged">
<prism:InvokeCommandAction Command="{Binding SelectCommand}"
TriggerParameterPath="AddedItems"/>
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
</ListBox>
Now when you will run the application, you will get the output as expected.
Hope you enjoyed learning!!!