Share via


UWP: Disabling Selection Of Items In a ListView

https://msdnshared.blob.core.windows.net/media/2016/08/0841.NinjaAwardTinySilver.pngSilver Award Winner


Introduction

This article explains how you could disable the selection of particular items in a ListView control with the SelectionMode property set to Multiple in a Universal Windows Platform (UWP) app.

Setting the SelectionMode property of a ListView control to Multiple (Windows.UI.Xaml.Controls.ListViewSelectionMode.Multiple) in a Universal Windows Platform (UWP) app enables you to select several items by checking an automatically generated CheckBox element for each item that you want to select:

https://magnusmontin.files.wordpress.com/2016/02/uwplistviewms.png?w=402&h=183

public class  ViewModel
{
    private readonly  IList<string> _items = new  List<string>() { "First",  "Second", "Third" };
    public IList<string> Items { get  { return  _items; } }
}
<Page
    x:Class="App1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App1"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Page.DataContext>
        <local:ViewModel/>
    </Page.DataContext>
  
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <ListView x:Name="listView" ItemsSource="{Binding Items}" SelectionMode="Multiple">
        </ListView>
    </Grid>
</Page>

IsHitTestVisible

But what if you want to disable the selection of a particular item based on some logic? You could disable all items by setting the IsHitTestVisible property of the ListViewItem container to false in the ItemContainerStyle of the ListView. This will prevent the items from being interacted with, i.e. they will no longer respond to mouse input nor fire mouse-related events:

<ListView.ItemContainerStyle>
    <Style TargetType="ListViewItem">
        <Setter Property="IsHitTestVisible" Value="False"/>
    </Style>
</ListView.ItemContainerStyle>

Margin

You probably also want to hide the CheckBox and although there is no property for directly setting the Visibility property of the automatically generated CheckBox element, you could easily accomplish this by specifying a negative margin for the ListViewItem container:

<ListView x:Name="listView" ItemsSource="{Binding Items}" SelectionMode="Multiple">
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="IsHitTestVisible" Value="False"/>
                    <Setter Property="Margin" Value="-32 0 0 0"/>
                </Style>
            </ListView.ItemContainerStyle>
</ListView>

https://magnusmontin.files.wordpress.com/2016/02/uwplistviewms2.png?w=402&h=183

This will effectively hide the CheckBox. But setting the IsHitTestVisible and Margin properties in the ItemContainerStyle will also disable the selection and hide the CheckBox control for all items in the ListView which is probably not what you want if you did set the SelectionMode property to Multiple in the first place.

Since UWP still has no support for data triggers, there is no good way of setting the IsHitTestVisible and Margin properties conditionally based on the value of a property of the source data object (System.String in this particular sample code) directly in the XAML markup.

DataContextChanged

You could instead define an ItemTemplate, handle the DataContextChanged event of the root element in the DataTemplate and then set the properties of the visual ListViewItem container programmatically in the code-behind of the view. The ListView’s ContainerFromItem method will get a reference to the ListViewItem container for a specific data object:

<ListView x:Name="listView" ItemsSource="{Binding Items}" SelectionMode="Multiple">
    <ListView.ItemTemplate>
        <DataTemplate>
            <Grid DataContextChanged="Grid_DataContextChanged">
                <TextBlock Text="{Binding}"/>
            </Grid>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>
private void  Grid_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
    Grid grid = sender as  Grid;
    string s = grid.DataContext as string; //this is the data object in the ItemsSource of the ListView
    if (s == "Second")
    {
        ListViewItem lvi = listView.ContainerFromItem(s) as  ListViewItem;
        if (lvi != null)
        {
            lvi.IsHitTestVisible = false;
            lvi.Margin = new  Thickness(-32, 0, 0, 0);
        }
    }
}

https://magnusmontin.files.wordpress.com/2016/02/uwplistviewms3.png?w=402&h=183

MVVM

Note that the approach of handling the DataContextChanged event doesn’t really break the Model-View-ViewModel (MVVM) pattern since you are basically just moving the comparison of the value of the source property and the code that sets the properties of the ListViewItem from the XAML markup of the view to the code-behind of the same view. The MVVM design pattern is all about separation of concerns and not about eliminating all code from the views.

In a WPF application, you could have used data triggers to accomplish the same thing:

<ListView x:Name="listView" ItemsSource="{Binding Items}" SelectionMode="Multiple">
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Style.Triggers>
                <DataTrigger Binding="{Binding}" Value="Second">
                    <Setter Property="IsHitTestVisible" Value="False"/>
                    <Setter Property="Margin" Value="-32 0 0 0"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </ListView.ItemContainerStyle>
</ListView>

But until UWP gets support for data triggers and bindings in style setters, “workarounds” like the one presented in this post are both required and perfectly acceptable.