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)
- {
-
- BikeViewModel selectedBike = this;
-
-
- await Task.Delay(1);
-
-
- }
- }
- }
2. Now define a ViewModel for your App Page that uses your BikeViewModel defined in the previous step to form an observable collection.
- 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;
- }
-
- }
- }
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.
- <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>
And use it in the Items Control such as a GridView like this:
- <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>
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.
- 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)
- {
-
- CarViewModel selectedCar = this;
-
-
- if (bubbleUpSelection != null)
- {
- bubbleUpSelection(selectedCar);
- }
- }
- }
- }
2. Now define a ViewModel for your App Page that uses your CarViewModel defined in the previous step to form an observable collection.
- 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)
- {
-
-
-
- Cars.Add(new CarViewModel(car, handleCarSelection));
- }
- }
-
- return true;
- }
-
- private async void handleCarSelection(CarViewModel car)
- {
-
- await Task.Delay(1);
-
-
- }
-
- }
- }
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:
- <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>
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.