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:

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

Upvotes: 0

Views: 201

Answers (2)

Copied verbatim from https://github.com/AvaloniaUI/Avalonia/discussions/15925


This should work:

<ListBox.ItemContainerTheme>
    <ControlTheme 
        TargetType="ListBoxItem"
        BasedOn="{StaticResource {x:Type ListBoxItem}}"
        x:DataType="vm:Item">
        <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
    </ControlTheme>
</ListBox.ItemContainerTheme>

EDIT: x:DataType is needed because there is no type inference in this case, not sure if there are plans to implement it (or if it's even possible).


There are not enough "Thank you"s for user jp2masa!!!

Complete code (with a contribution from Clemens):

<ListBox SelectionMode="Multiple,Toggle"
     ItemsSource="{Binding Items}"
     DisplayMemberBinding="{Binding Text}">
    <ListBox.ItemContainerTheme>
        <ControlTheme
            TargetType="ListBoxItem"
            BasedOn="{StaticResource {x:Type ListBoxItem}}"
            x:DataType="vm:Item">
            <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
        </ControlTheme>
    </ListBox.ItemContainerTheme>
</ListBox>

Update: Now, with something to chew uppon, I revisited Clemens answer. The only thing missing was x:DataType="vm:Item" so this works too:

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

It didn't work in my case because I was using compiled bindings, and the axaml compiler isn't smart enough to infer the correct type.

Update 2: I´m so sorry for the delay, I only got time to update my answer right now, from my workplace internet connection. I marked Clemens answer as the correct answer (Honor to whom honor is due! as Cuba's national hero used to say... Thanks mate for your help!) I will keep this answer as it shows a somewhat different axaml solution also.

Upvotes: 0

Clemens
Clemens

Reputation: 128077

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>

Upvotes: 1

Related Questions