MVVM in Windows Store App - Handle Tap or Click Events on Items Control

It is quite common for developers to get carried away with easy event handlers violating MVVM fundamentals. In this article I will explain some scenarios and tell you how to easily avoid code behind event handlers when dealing with an Items Control (GridView, ListView, FlipView, ListBox and so on).

Scenario 1

On the selection of an item in a GridView we need to do some operation like getting data from the internet.

In this case we have an Items control bound to some Observable collection and when you tap your app should respond to the click on the item itself, it will not be handled at the Items control level. The following is procedure to do it.

1. Define a ViewModel for the Item, say BikeViewModel, and in that define a command called selected to handle the tap/click.

  1. using HandleTapOrClickEvents.Model.Entity;  
  2. using System.Threading.Tasks;  
  3.   
  4. namespace HandleTapOrClickEvents.ViewModel  
  5. {  
  6.     public class BikeViewModel : BaseViewModel  
  7.     {  
  8.         private int id;  
  9.         private string name;  
  10.         private readonly SimpleRelayCommand selected;  
  11.   
  12.   
  13.         public int Id  
  14.         {  
  15.             get { return id; }  
  16.             set { SetProperty(ref id, value); }  
  17.         }  
  18.   
  19.         public string Name  
  20.         {  
  21.             get { return name; }  
  22.             set { SetProperty(ref name, value); }  
  23.         }  
  24.   
  25.         public SimpleRelayCommand Selected  
  26.         {  
  27.             get { return selected; }  
  28.         }  
  29.   
  30.         public BikeViewModel(Bike bike)  
  31.         {  
  32.             selected = new SimpleRelayCommand(handleSelectionAsync);  
  33.   
  34.             id = bike.Id;  
  35.             name = bike.Name;  
  36.         }  
  37.   
  38.         private async void handleSelectionAsync(object obj)  
  39.         {  
  40.             //This function will run under instance of BikeViewModel which is clicked  
  41.             BikeViewModel selectedBike = this;  
  42.   
  43.             //Dummy delay  just to show some async work is going on  
  44.             await Task.Delay(1);  
  45.   
  46.             //Do any processing or action here  
  47.         }  
  48.     }  
  49. }  
2. Now define a ViewModel for your App Page that uses your BikeViewModel defined in the previous step to form an observable collection.
  1. using HandleTapOrClickEvents.Model.DataAccess;  
  2. using HandleTapOrClickEvents.Model.Entity;  
  3. using System.Collections.Generic;  
  4. using System.Collections.ObjectModel;  
  5. using System.Threading.Tasks;  
  6.   
  7. namespace HandleTapOrClickEvents.ViewModel  
  8. {  
  9.     public class HomeViewModel : BaseViewModel  
  10.     {  
  11.         private ObservableCollection<BikeViewModel> bikes;  
  12.           
  13.         private Task<bool> populateDataTask;  
  14.   
  15.         public ObservableCollection<BikeViewModel> Bikes  
  16.         {  
  17.             get { return bikes; }  
  18.             set { SetProperty(ref bikes, value); }  
  19.         }  
  20.   
  21.         public HomeViewModel()  
  22.         {  
  23.             bikes = new ObservableCollection<BikeViewModel>();  
  24.   
  25.             populateDataTask = populateDataAsync();  
  26.         }  
  27.   
  28.         private async Task<bool> populateDataAsync()  
  29.         {  
  30.             List<Bike> allBikes = await DummyDataAccess.GetBikesAsync();  
  31.   
  32.             if (allBikes != null)  
  33.             {  
  34.                 foreach (Bike bike in allBikes)  
  35.                 {  
  36.                     Bikes.Add(new BikeViewModel(bike));  
  37.                 }  
  38.             }  
  39.   
  40.             return true;  
  41.         }  
  42.   
  43.     }  
  44. }  
3. On your Page use a Button as the container for the data template, this will help as Button support the MVVM Command out of the box. There can be some default styling that can be easily handled using this style.
  1. <Style x:Key="PlainButtonWithNoStyle" TargetType="Button">  
  2.     <Setter Property="Background" Value="Transparent"/>  
  3.     <Setter Property="BorderBrush" Value="Transparent"/>  
  4.     <Setter Property="Foreground" Value="Transparent"/>  
  5.     <Setter Property="BorderThickness" Value="0"/>  
  6.     <Setter Property="FontSize" Value="15"/>  
  7.     <Setter Property="Padding" Value="0,0"/>  
  8.     <Setter Property="HorizontalAlignment" Value="Left"/>  
  9.     <Setter Property="VerticalAlignment" Value="Center"/>  
  10.     <Setter Property="Template">  
  11.         <Setter.Value>  
  12.             <ControlTemplate TargetType="Button">  
  13.                 <Grid x:Name="Grid" Background="Transparent">  
  14.                     <VisualStateManager.VisualStateGroups>  
  15.                         <VisualStateGroup x:Name="CommonStates">  
  16.                             <VisualStateGroup.Transitions>  
  17.                                 <VisualTransition From="Pressed" To="PointerOver"/>  
  18.                                 <VisualTransition From="PointerOver" To="Normal"/>  
  19.                                 <VisualTransition From="Pressed" To="Normal"/>  
  20.                             </VisualStateGroup.Transitions>  
  21.                             <VisualState x:Name="Normal"/>  
  22.                             <VisualState x:Name="PointerOver"/>  
  23.                             <VisualState x:Name="Pressed"/>  
  24.                             <VisualState x:Name="Disabled"/>  
  25.                         </VisualStateGroup>  
  26.                     </VisualStateManager.VisualStateGroups>  
  27.                     <Border x:Name="Border" BorderBrush="{TemplateBinding BorderBrush}"   
  28.                         BorderThickness="{TemplateBinding BorderThickness}"   
  29.                         Background="{TemplateBinding Background}"   
  30.                         Margin="{ThemeResource PhoneTouchTargetOverhang}">  
  31.                         <ContentPresenter x:Name="ContentPresenter"   
  32.                             AutomationProperties.AccessibilityView="Raw"   
  33.                             ContentTemplate="{TemplateBinding ContentTemplate}"   
  34.                             Content="{TemplateBinding Content}"   
  35.                             Foreground="{TemplateBinding Foreground}"   
  36.                             HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"   
  37.                             Margin="{TemplateBinding Padding}"   
  38.                             VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>  
  39.                     </Border>  
  40.                 </Grid>  
  41.             </ControlTemplate>  
  42.         </Setter.Value>  
  43.     </Setter>  
  44. </Style>  
And use it in the Items Control such as a GridView like this:
  1. <GridView ItemsSource="{Binding Bikes}">  
  2.     <GridView.ItemTemplate>  
  3.         <DataTemplate>  
  4.             <Button Style="{StaticResource PlainButtonWithNoStyle}" Command="{Binding Selected}">  
  5.                 <StackPanel Height="100" Width="200" Background="Yellow">  
  6.                     <TextBlock Text="{Binding Name}"/>  
  7.                 </StackPanel>  
  8.             </Button>  
  9.         </DataTemplate>  
  10.     </GridView.ItemTemplate>  
  11. </GridView>  
Scenario 2

On the selection of an item in a GridView we need to do some operation that needs to be handled at the higher level, in other words at the parent ViewModel level.

In this case we have an Items control that is bound to some Observable collection and when you tap your app should respond to the click on the page level where you have the Items Control. The following is the procedure to do it.

1. Define a ViewModel for the Item, say CarViewModel, and in that define a command called selected to handle the tap/click.
  1. using HandleTapOrClickEvents.Model.Entity;  
  2. using System;  
  3. using System.Threading.Tasks;  
  4.   
  5. namespace HandleTapOrClickEvents.ViewModel  
  6. {  
  7.     public class CarViewModel : BaseViewModel  
  8.     {  
  9.         private int id;  
  10.         private string name;  
  11.         private readonly SimpleRelayCommand selected;  
  12.         private Action<CarViewModel> bubbleUpSelection;  
  13.   
  14.         public int Id  
  15.         {  
  16.             get { return id; }  
  17.             set { SetProperty(ref id, value); }  
  18.         }  
  19.   
  20.         public string Name  
  21.         {  
  22.             get { return name; }  
  23.             set { SetProperty(ref name, value); }  
  24.         }  
  25.   
  26.         public SimpleRelayCommand Selected  
  27.         {  
  28.             get { return selected; }  
  29.         }  
  30.   
  31.         public CarViewModel(Car car, Action<CarViewModel> handleSelection)  
  32.         {  
  33.             bubbleUpSelection = handleSelection;  
  34.             selected = new SimpleRelayCommand(routeSelection);  
  35.   
  36.             id = car.Id;  
  37.             name = car.Name;  
  38.         }  
  39.   
  40.         private void routeSelection(object obj)  
  41.         {  
  42.             //This function will run under instance of CarViewModel which is clicked  
  43.             CarViewModel selectedCar = this;  
  44.   
  45.             //Just ensure that some handler was passed  
  46.             if (bubbleUpSelection != null)  
  47.             {  
  48.                 bubbleUpSelection(selectedCar);  
  49.             }  
  50.         }  
  51.     }  
  52. }  
2. Now define a ViewModel for your App Page that uses your CarViewModel defined in the previous step to form an observable collection.
  1. using HandleTapOrClickEvents.Model.DataAccess;  
  2. using HandleTapOrClickEvents.Model.Entity;  
  3. using System.Collections.Generic;  
  4. using System.Collections.ObjectModel;  
  5. using System.Threading.Tasks;  
  6.   
  7. namespace HandleTapOrClickEvents.ViewModel  
  8. {  
  9.     public class HomeViewModel : BaseViewModel  
  10.     {  
  11.         private ObservableCollection<CarViewModel> cars;  
  12.         private Task<bool> populateDataTask;  
  13.   
  14.         public ObservableCollection<CarViewModel> Cars  
  15.         {  
  16.             get { return cars; }  
  17.             set { SetProperty(ref cars, value); }  
  18.         }  
  19.   
  20.         public HomeViewModel()  
  21.         {  
  22.             cars = new ObservableCollection<CarViewModel>();  
  23.   
  24.             populateDataTask = populateDataAsync();  
  25.         }  
  26.   
  27.         private async Task<bool> populateDataAsync()  
  28.         {  
  29.               
  30.             List<Car> allCars = await DummyDataAccess.GetCarsAsync();  
  31.   
  32.             if (allCars != null)  
  33.             {  
  34.                 foreach (Car car in allCars)  
  35.                 {  
  36.                     //Here HomeViewModel is hooking a handler to CarViewModel  
  37.                     //This is needed because the command will fire in CarViewModel  
  38.                     //but then CarViewModel will bubblle it up and give it back to HomeViewModel  
  39.                     Cars.Add(new CarViewModel(car, handleCarSelection));  
  40.                 }  
  41.             }  
  42.   
  43.             return true;  
  44.         }  
  45.   
  46.         private async void handleCarSelection(CarViewModel car)  
  47.         {  
  48.             //Dummy delay  just to show some async work is going on  
  49.             await Task.Delay(1);  
  50.   
  51.             //Do any processing or action here  
  52.         }  
  53.   
  54.     }  
  55. }  
3. On your Page use a Button as a container for the data template, this will help as Button support MVVM Command out of the box. And use it in the Items Control, say a GridView like this:
  1. <GridView ItemsSource="{Binding Cars}">  
  2.     <GridView.ItemTemplate>  
  3.         <DataTemplate>  
  4.             <Button Style="{StaticResource PlainButtonWithNoStyle}" Command="{Binding Selected}">  
  5.                 <StackPanel Height="100" Width="200" Background="Yellow">  
  6.                     <TextBlock Text="{Binding Name}"/>  
  7.                 </StackPanel>  
  8.             </Button>  
  9.         </DataTemplate>  
  10.     </GridView.ItemTemplate>  
  11. </GridView>  
You can refer to the attached code that I have used in this article. In the code you will see that the UI is not done and that was my intention, just so that you can do some hands-on.


Similar Articles