Search code examples
c#xamllistviewmvvmwinui-3

ListView SelectedItems with WinUI3 and Microsoft.Toolkit.Mvvm


I'm making a simple ListView with WinUI and Microsoft.Toolkit.Mvvm based on TemplateStudio for WinUI.

I try to achieve a simple ListView that contains fruits names, a 2nd ListView to get SelectedItems from the first one and finally a TextBox with count of selected items.

enter image description here

MainPage.xaml

<Page
    x:Class="ListViewWinUISample.Views.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    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:i="using:Microsoft.Xaml.Interactivity"
    xmlns:core="using:Microsoft.Xaml.Interactions.Core"
    mc:Ignorable="d">

    <Grid x:Name="ContentArea" Margin="20">
        <Grid.RowDefinitions>
            <RowDefinition Height="25" />
            <RowDefinition Height="150" />
            <RowDefinition Height="25" />
            <RowDefinition Height="150" />
            <RowDefinition Height="30" />
        </Grid.RowDefinitions>
        <TextBlock Grid.Row="0" Text="Fruits list :" Style="{StaticResource PageTitleStyle}" />
        <ListView Grid.Row="1" x:Name="LvwFruits" ItemsSource="{x:Bind ViewModel.ListFruits}" BorderBrush="#212121" SelectionMode="Multiple">
            <i:Interaction.Behaviors>
                <core:EventTriggerBehavior EventName="SelectionChanged">
                    <core:InvokeCommandAction Command="{x:Bind ViewModel.GetSelectedFruits}" CommandParameter="{Binding SelectedItems, ElementName=LvwFruits}"/>
                </core:EventTriggerBehavior>
            </i:Interaction.Behaviors>
        </ListView>
        <TextBlock Grid.Row="2" Text="Selected fruits :" Style="{StaticResource PageTitleStyle}" />
        <ListView Grid.Row="3" x:Name="LvwSelectedFruits" ItemsSource="{x:Bind ViewModel.ListSelectedFruits}" BorderBrush="#212121"/>
        <StackPanel Grid.Row="4" Orientation="Horizontal">
            <TextBlock Text="Count selected fruits : " VerticalAlignment="Center"/>
            <TextBox Text="{Binding SelectedItems.Count, ElementName=LvwFruits}" VerticalAlignment="Center"/>
        </StackPanel>
    </Grid>
</Page>

MainViewModel.cs

using System.Collections;
using System.Collections.ObjectModel;
using System.Diagnostics;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace ListViewWinUISample.ViewModels;

public class MainViewModel : ObservableRecipient
{
    public List<string> ListFruits;
    public List<string> ListSelectedFruits;

    public RelayCommand<IList> GetSelectedFruits { get; set; }


    public MainViewModel()
    {
        GetSelectedFruits = new RelayCommand<IList>(GetSelectedFruitsMethod);

        var fruits = new List<string>
        {
            "Apple",
            "Banana",
            "Strawberry"
        };

        ListFruits = fruits;
    }

    private void GetSelectedFruitsMethod(IList selectedFruits)
    {
        Debug.WriteLine("Command successfully executed");
        var listSelectedFruits = new List<string>();
        foreach (var item in selectedFruits)
        {
            listSelectedFruits.Add(item.ToString());
            Debug.WriteLine("selected fruit : " + item.ToString());
        }
    }
}

The GetSelectedFruitsMethod is successfully started but the parameter IList selectedFruits in GetSelectedFruitsMethod is always null and I can't figure out how to get the SelectedItems parameter from the View even if I passed it as CommandParameter in XAML.

Thanks and regards.


Solution

  • I finally got the answer by myself with the full MVVM way.

    To retrieve SelectedItems from first ListView, there was just to set CommandParameter="{x:Bind LvwFruits.SelectedItems}" and not {Binding SelectedItems, ElementName=LvwFruits} and to replace IList by IList<object>.

    MainPage.xaml

    <Page
        x:Class="ListViewWinUISample.Views.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        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:i="using:Microsoft.Xaml.Interactivity"
        xmlns:core="using:Microsoft.Xaml.Interactions.Core"
        mc:Ignorable="d">
    
        <Grid x:Name="ContentArea" Margin="20">
            <Grid.RowDefinitions>
                <RowDefinition Height="25" />
                <RowDefinition Height="150" />
                <RowDefinition Height="25" />
                <RowDefinition Height="150" />
                <RowDefinition Height="30" />
            </Grid.RowDefinitions>
            <TextBlock Grid.Row="0" Text="Fruits list :" Style="{StaticResource PageTitleStyle}" />
            <ListView Grid.Row="1" x:Name="LvwFruits" ItemsSource="{x:Bind ViewModel.ListFruits}" BorderBrush="#212121" SelectionMode="Multiple" BorderThickness="1">
                <i:Interaction.Behaviors>
                    <core:EventTriggerBehavior EventName="SelectionChanged">
                        <core:InvokeCommandAction Command="{x:Bind ViewModel.GetSelectedFruits, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" CommandParameter="{x:Bind LvwFruits.SelectedItems}"/>
                    </core:EventTriggerBehavior>
                </i:Interaction.Behaviors>
            </ListView>
            <TextBlock Grid.Row="2" Text="Selected fruits :" Style="{StaticResource PageTitleStyle}" />
            <ListView Grid.Row="3" x:Name="LvwSelectedFruits" ItemsSource="{x:Bind ViewModel.ListSelectedFruits, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" CanDragItems="True" CanReorderItems="True" AllowDrop="True" SelectionMode="Single" BorderBrush="#212121" BorderThickness="1"/>
            <StackPanel Grid.Row="4" Orientation="Horizontal">
                <TextBlock Text="Count selected fruits : " VerticalAlignment="Center"/>
                <TextBox Text="{x:Bind ViewModel.CountSelectedCommands, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Margin="5,0,0,0" Width="10"/>
            </StackPanel>
        </Grid>
    </Page>
    

    MainViewModel.cs

    using System.Collections;
    using System.Collections.ObjectModel;
    using System.Diagnostics;
    using CommunityToolkit.Mvvm.ComponentModel;
    using CommunityToolkit.Mvvm.Input;
    
    namespace ListViewWinUISample.ViewModels;
    
    public class MainViewModel : ObservableRecipient
    {
        public List<string> ListFruits;
    
        private ObservableCollection<string> listSelectedFruits;
        public ObservableCollection<string> ListSelectedFruits
        {
            get => listSelectedFruits;
            set => SetProperty(ref listSelectedFruits, value);
        }
    
        private int countSelectedCommands;
        public int CountSelectedCommands
        {
            get => countSelectedCommands;
            set => SetProperty(ref countSelectedCommands, value);
        }
    
        public RelayCommand<IList<object>> GetSelectedFruits
        {
            get; set;
        }
    
    
        public MainViewModel()
        {
            GetSelectedFruits = new RelayCommand<IList<object>>(GetSelectedFruitsMethod);
    
            var fruits = new List<string>
            {
                "Apple",
                "Banana",
                "Strawberry"
            };
    
            ListFruits = fruits;
        }
    
        private void GetSelectedFruitsMethod(IList<object> selectedFruits)
        {
            if (selectedFruits != null)
            {
                var selected = new ObservableCollection<string>();
                foreach (var item in selectedFruits)
                {
                    selected.Add(item.ToString());
                    Debug.WriteLine("Selected fruit : " + item.ToString());
                }
                ListSelectedFruits = selected;
                CountSelectedCommands = selected.Count;
            }
            else
            {
                Debug.WriteLine(@"/!\  NO SELECTED FRUITS /!\");
            }
        }
    }