My .NET focused coding blog.

Disabling selection of some items in a UWP ListView

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:

ListView

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>

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>

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>

ListView
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.

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);
        }
    }
}

ListView

Note that the approach of handling the DataContextChanged event doesn’t really break the 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.


2 Comments on “Disabling selection of some items in a UWP ListView”

  1. Burhan says:

    Hi Magnus,
    I tried on a list consisting of 50 lines, but there are problems (like in the picture).

    How can I solve this problem?

    My codes are as follows:

    public class ViewModel
    {
    private readonly IList _items = new List();
    public ViewModel()
    {
    for (int i = 0; i < 50; i++)
    {
    if (i == 10)
    {
    _items.Add("uncompleted");
    }
    else
    {
    _items.Add("completed");
    }

    }
    }
    public IList Items { get { return _items; } }
    }

  2. burhan says:

    [WP 8.1]
    The above works well for around the first 30 items in the list. But as you scroll further down the list, it has been observed that the listitem is not getting properly IsHitTestVisible value, you get unacticipated IsHitTestVisible values in unexpected views.


Leave a comment