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.
- using HandleTapOrClickEvents.Model.Entity;
- using System.Threading.Tasks;
- namespace HandleTapOrClickEvents.ViewModel
- {
- public class BikeViewModel : BaseViewModel
- {
- private int id;
- private string name;
- private readonly SimpleRelayCommand selected;
- public int Id
- {
- get { return id; }
- set { SetProperty(ref id, value); }
- }
- public string Name
- {
- get { return name; }
- set { SetProperty(ref name, value); }
- }
- public SimpleRelayCommand Selected
- {
- get { return selected; }
- }
- public BikeViewModel(Bike bike)
- {
- selected = new SimpleRelayCommand(handleSelectionAsync);
- id = bike.Id;
- name = bike.Name;
- }
- private async void handleSelectionAsync(object obj)
- {
- //This function will run under instance of BikeViewModel which is clicked
- BikeViewModel selectedBike = this;
- //Dummy delay just to show some async work is going on
- await Task.Delay(1);
- //Do any processing or action here
- }
- }
- }
- using HandleTapOrClickEvents.Model.DataAccess;
- using HandleTapOrClickEvents.Model.Entity;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Threading.Tasks;
- namespace HandleTapOrClickEvents.ViewModel
- {
- public class HomeViewModel : BaseViewModel
- {
- private ObservableCollection<BikeViewModel> bikes;
- private Task<bool> populateDataTask;
- public ObservableCollection<BikeViewModel> Bikes
- {
- get { return bikes; }
- set { SetProperty(ref bikes, value); }
- }
- public HomeViewModel()
- {
- bikes = new ObservableCollection<BikeViewModel>();
- populateDataTask = populateDataAsync();
- }
- private async Task<bool> populateDataAsync()
- {
- List<Bike> allBikes = await DummyDataAccess.GetBikesAsync();
- if (allBikes != null)
- {
- foreach (Bike bike in allBikes)
- {
- Bikes.Add(new BikeViewModel(bike));
- }
- }
- return true;
- }
- }
- }
- <Style x:Key="PlainButtonWithNoStyle" TargetType="Button">
- <Setter Property="Background" Value="Transparent"/>
- <Setter Property="BorderBrush" Value="Transparent"/>
- <Setter Property="Foreground" Value="Transparent"/>
- <Setter Property="BorderThickness" Value="0"/>
- <Setter Property="FontSize" Value="15"/>
- <Setter Property="Padding" Value="0,0"/>
- <Setter Property="HorizontalAlignment" Value="Left"/>
- <Setter Property="VerticalAlignment" Value="Center"/>
- <Setter Property="Template">
- <Setter.Value>
- <ControlTemplate TargetType="Button">
- <Grid x:Name="Grid" Background="Transparent">
- <VisualStateManager.VisualStateGroups>
- <VisualStateGroup x:Name="CommonStates">
- <VisualStateGroup.Transitions>
- <VisualTransition From="Pressed" To="PointerOver"/>
- <VisualTransition From="PointerOver" To="Normal"/>
- <VisualTransition From="Pressed" To="Normal"/>
- </VisualStateGroup.Transitions>
- <VisualState x:Name="Normal"/>
- <VisualState x:Name="PointerOver"/>
- <VisualState x:Name="Pressed"/>
- <VisualState x:Name="Disabled"/>
- </VisualStateGroup>
- </VisualStateManager.VisualStateGroups>
- <Border x:Name="Border" BorderBrush="{TemplateBinding BorderBrush}"
- BorderThickness="{TemplateBinding BorderThickness}"
- Background="{TemplateBinding Background}"
- Margin="{ThemeResource PhoneTouchTargetOverhang}">
- <ContentPresenter x:Name="ContentPresenter"
- AutomationProperties.AccessibilityView="Raw"
- ContentTemplate="{TemplateBinding ContentTemplate}"
- Content="{TemplateBinding Content}"
- Foreground="{TemplateBinding Foreground}"
- HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
- Margin="{TemplateBinding Padding}"
- VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
- </Border>
- </Grid>
- </ControlTemplate>
- </Setter.Value>
- </Setter>
- </Style>
- <GridView ItemsSource="{Binding Bikes}">
- <GridView.ItemTemplate>
- <DataTemplate>
- <Button Style="{StaticResource PlainButtonWithNoStyle}" Command="{Binding Selected}">
- <StackPanel Height="100" Width="200" Background="Yellow">
- <TextBlock Text="{Binding Name}"/>
- </StackPanel>
- </Button>
- </DataTemplate>
- </GridView.ItemTemplate>
- </GridView>
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.
- using HandleTapOrClickEvents.Model.Entity;
- using System;
- using System.Threading.Tasks;
- namespace HandleTapOrClickEvents.ViewModel
- {
- public class CarViewModel : BaseViewModel
- {
- private int id;
- private string name;
- private readonly SimpleRelayCommand selected;
- private Action<CarViewModel> bubbleUpSelection;
- public int Id
- {
- get { return id; }
- set { SetProperty(ref id, value); }
- }
- public string Name
- {
- get { return name; }
- set { SetProperty(ref name, value); }
- }
- public SimpleRelayCommand Selected
- {
- get { return selected; }
- }
- public CarViewModel(Car car, Action<CarViewModel> handleSelection)
- {
- bubbleUpSelection = handleSelection;
- selected = new SimpleRelayCommand(routeSelection);
- id = car.Id;
- name = car.Name;
- }
- private void routeSelection(object obj)
- {
- //This function will run under instance of CarViewModel which is clicked
- CarViewModel selectedCar = this;
- //Just ensure that some handler was passed
- if (bubbleUpSelection != null)
- {
- bubbleUpSelection(selectedCar);
- }
- }
- }
- }
- using HandleTapOrClickEvents.Model.DataAccess;
- using HandleTapOrClickEvents.Model.Entity;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Threading.Tasks;
- namespace HandleTapOrClickEvents.ViewModel
- {
- public class HomeViewModel : BaseViewModel
- {
- private ObservableCollection<CarViewModel> cars;
- private Task<bool> populateDataTask;
- public ObservableCollection<CarViewModel> Cars
- {
- get { return cars; }
- set { SetProperty(ref cars, value); }
- }
- public HomeViewModel()
- {
- cars = new ObservableCollection<CarViewModel>();
- populateDataTask = populateDataAsync();
- }
- private async Task<bool> populateDataAsync()
- {
- List<Car> allCars = await DummyDataAccess.GetCarsAsync();
- if (allCars != null)
- {
- foreach (Car car in allCars)
- {
- //Here HomeViewModel is hooking a handler to CarViewModel
- //This is needed because the command will fire in CarViewModel
- //but then CarViewModel will bubblle it up and give it back to HomeViewModel
- Cars.Add(new CarViewModel(car, handleCarSelection));
- }
- }
- return true;
- }
- private async void handleCarSelection(CarViewModel car)
- {
- //Dummy delay just to show some async work is going on
- await Task.Delay(1);
- //Do any processing or action here
- }
- }
- }
- <GridView ItemsSource="{Binding Cars}">
- <GridView.ItemTemplate>
- <DataTemplate>
- <Button Style="{StaticResource PlainButtonWithNoStyle}" Command="{Binding Selected}">
- <StackPanel Height="100" Width="200" Background="Yellow">
- <TextBlock Text="{Binding Name}"/>
- </StackPanel>
- </Button>
- </DataTemplate>
- </GridView.ItemTemplate>
- </GridView>

Join the conversation! Your thoughts help the community grow.