True MVVM by implementing Event Triggers for non button base controls in Silverlight

First of all, I had a great time in Dallas Tech Fest. It was great. Hopefully, I get to present again next time as well.

While preparing for the presentation, I realized something, so far all the coding I have done, I did not truly develop in MVVM pattern. When I develop application which has buttons, I was able to move the code for button click event to Command thus removed code from code behind. But I did not do the same for other controls. Currently, Command only support Button Base classes. As you can guess, ListBox, ComboBox and others do not derive from Button Base, thus making use Command on these controls not possible. So I was going back to my old codes and found out that, I will adding ‘SelectionChanged’ event in the code behind and from there, I fire an event to View Model to perform an action. That was really bad, it never occurred to me to investigate this at that time. While preparing for Jounce Talk at Dallas Tech Fest I spend some more time looking at the problem and the answer was out there all along and I never noticed, as always.

So in this blog, lets look at how to remove the code from code behind for ListBox. This will be same for other controls. For simplicity, we will create a small listbox which will have three values and once the selection is made, we will display a text block at the bottom of the list box with “You selected {0}” of the value. Simple enough. First lets look at the XAML.

<UserControl x:Class="EventTrigger.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:sampleData="clr-namespace:EventTrigger.SampleData"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"         
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White"
          d:DataContext="{d:DesignInstance sampleData:DesignMainViewModel, IsDesignTimeCreatable=True}">
        <StackPanel>
        <ListBox Name="MyListBox" ItemsSource="{Binding ListItems}">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="SelectionChanged">
                        <i:InvokeCommandAction Command="{Binding MyCommand}" CommandParameter="{Binding ElementName=MyListBox, Path=SelectedIndex}"/>
                    </i:EventTrigger>    
                </i:Interaction.Triggers>
        </ListBox>
            <TextBlock Text="{Binding SelectedValue}" />
        </StackPanel>
    </Grid>
</UserControl>

 

In the XAML the point of interests are

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

 

We add the interactivity to the name space. This is very powerful dll to add behaviors, event triggers and more. The second part of interest is the triggers implementation right below ListBox

<ListBox Name="MyListBox" ItemsSource="{Binding ListItems}">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="SelectionChanged">
                        <i:InvokeCommandAction Command="{Binding MyCommand}" CommandParameter="{Binding ElementName=MyListBox, Path=SelectedIndex}"/>
                    </i:EventTrigger>    
                </i:Interaction.Triggers>
        </ListBox>

 

What we are saying here is that, when SelectionChanged event fires, invoke MyCommand, with Selected Index of MyListBox. As simple as that. InvokeCommandAction is straight forward, it specifies what command in view model to execute and what are its parameter. One thing to note here, I did not send either, SelectedItem or SelectedValue. Only reason I did not send it because, those two are of type ListBoxItem. I do not want any UI related information in my view model. Since I know the collection that is bound to the List box, with selected index, I can easily find out what item was selected.

Now that we know the command, if we look at the EventTrigger, here we define which which event that we want the command to be fired. In the list box, we are interested in selection changed. If the selection changes, myCommand will be fired. You need to make sure the event name spelled out correctly. Now that we know how triggers are hooked up, lets look at the view model code.

The ListItems which are bound to the ListBox was generated as follows

private List<string> listItems = new List<string>();

        public List<string> ListItems
        {
            get
            {
                if (listItems == null)
                    listItems = new List<string>();
                listItems.Add("1");
                listItems.Add("2");
                listItems.Add("3");
                return listItems;
            }
        }

The TextBlock which displays the message is nothing but a string property.

public string SelectedValue {get; set;}

This is typical command implementation, there is nothing special about handling anything event trigger.

public IActionCommand<int> MyCommand { get; private set; }

        public MainViewModel()
        {
            MyCommand = new ActionCommand<int>(TestMethod);
        }

        private void TestMethod(int parameter)
        {
            SelectedValue = string.Format("You selected : {0}", listItems[parameter]);
            RaisePropertyChanged(() => SelectedValue);
        }

First we declare the MyCommand and then in the ViewModel constructor we instantiate it and then we implement the method which executes the command.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s