Search code examples
.net-coredata-bindingavaloniauiavalonia

How to bind ListBoxItem.IsSelected to a property in the ViewModel?


I want to do the same thing as in question How to bind ListBoxItem.IsSelected to boolean data property but using Avalonia instead of WPF.

Given this ViewModel (MainViewModel.cs)

using System.Collections.Generic;
using CommunityToolkit.Mvvm.ComponentModel;

namespace AvaloniaAskOverflow.ViewModels;

public partial class MainViewModel : ViewModelBase
{

    private List<Item> items= new()
    {
        new ("Item 1", false),
        new ("Item 2", true),
        new ("Item 3", false),
        new ("Item 4", true)
    };

    public List<Item> Items => items;

}

public class Item: ObservableObject
{
    string text;
    public string Text { get => text; set => SetProperty(ref text, value); }

    bool isSelected;
    public bool IsSelected { get => isSelected; set => SetProperty(ref isSelected, value); }

    public Item(string text, bool isSelected)
    {
        this.text = text;
        this.isSelected = isSelected;
    }
}

I got as far as this (MainView.axaml)

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:vm="clr-namespace:AvaloniaAskOverflow.ViewModels"
             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
             x:Class="AvaloniaAskOverflow.Views.MainView"
             x:DataType="vm:MainViewModel">
    <Design.DataContext>
        <!-- This only sets the DataContext for the previewer in an IDE,
         to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
        <vm:MainViewModel />
    </Design.DataContext>
    <ListBox
        SelectionMode="Multiple,Toggle"
        ItemsSource="{Binding Items}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Text}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
        <ListBox.ItemContainerTheme>
            <Style Selector="ListBoxItem">
                <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
            </Style>
        </ListBox.ItemContainerTheme>
    </ListBox>
</UserControl>

Notes:

  • Kept the comments to keep the line number on the error message consistent with the code.
  • (Differences with the WPF answer) There is no ListBox.ItemContainerStyle in Avalonia, the equivalent is ListBox.ItemContainerTheme according to https://github.com/AvaloniaUI/Avalonia/discussions/10018)

but the compiler replies with this error:

Unable to resolve property or method of name 'IsSelected' on type 'AvaloniaAskOverflow.ViewModels.MainViewModel'. línea 24, posición 35.

Obviously, it is trying to find IsSelected in MainViewModel class instead of using Item.IsSelected.

So, how I bind the IsSelected property of each ListBoxItem to the corresponding Item.IsSelected?

It should be easy as in WPF, but obviously I'm missing something.

Tried Clemens' suggestion, but to no avail. Without ListBox.Styles everything is ok:

Without ListBox.Styles

but as soon as I complete the code:

Binding error


Solution

  • Put the ListBoxItem Style into <ListBox.Styles>:

    <ListBox SelectionMode="Multiple,Toggle"
             ItemsSource="{Binding Items}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Text}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
        <ListBox.Styles>
            <Style Selector="ListBoxItem">
                <Setter Property="IsSelected"
                        Value="{Binding IsSelected, Mode=TwoWay}"/>
            </Style>
        </ListBox.Styles>
    </ListBox>
    

    Or shorter:

    <ListBox SelectionMode="Multiple,Toggle"
             ItemsSource="{Binding Items}"
             DisplayMemberBinding="{Binding Text}">
        <ListBox.Styles>
            <Style Selector="ListBoxItem">
                <Setter Property="IsSelected"
                        Value="{Binding IsSelected, Mode=TwoWay}"/>
            </Style>
        </ListBox.Styles>
    </ListBox>
    

    In case you are using Compiled Bindings, add x:DataType="vm:Item" to the Style:

    <ListBox SelectionMode="Multiple,Toggle"
             ItemsSource="{Binding Items}"
             DisplayMemberBinding="{Binding Text}">
        <ListBox.Styles>
            <Style Selector="ListBoxItem" x:DataType="vm:Item">
                <Setter Property="IsSelected"
                        Value="{Binding IsSelected, Mode=TwoWay}"/>
            </Style>
        </ListBox.Styles>
    </ListBox>