Home > Back-end >  ListView SelectedItems with WinUI3 and Microsoft.Toolkit.Mvvm
ListView SelectedItems with WinUI3 and Microsoft.Toolkit.Mvvm

Time:12-16

I'm making a simple ListView with WinUI and Microsoft.Toolkit.Mvvm based on 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.

CodePudding user response:

You can create a custom control like this:

ListViewEx.cs

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Collections;
using System.Collections.Generic;

namespace ListViews;

public class ListViewEx : ListView
{
    public ListViewEx() : base()
    {
        this.SelectionChanged  = ListViewEx_SelectionChanged;
    }

    public new IList SelectedItems
    {
        get => (IList)GetValue(SelectedItemsProperty);
        set => SetValue(SelectedItemsProperty, value);
    }

    public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
        nameof(SelectedItems),
        typeof(IList),
        typeof(ListViewEx),
        new PropertyMetadata(new List<object>()));

    private void ListViewEx_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        foreach (object item in e.RemovedItems)
        {
            SelectedItems.Remove(item);
        }

        foreach (object item in e.AddedItems)
        {
            SelectedItems.Add(item);
        }
    }
}

And use it like this:

MainPage.xaml

<Grid RowDefinitions="Auto,*,*">
    <StackPanel
        Grid.Row="0"
        Orientation="Horizontal">
        <NumberBox
            x:Name="LoadingItemsCount"
            Value="1000" />
        <Button
            Command="{x:Bind ViewModel.LoadItemsCommand}"
            CommandParameter="{x:Bind LoadingItemsCount.Value, Mode=OneWay}" />
    </StackPanel>
    <local:ListViewEx
        Grid.Row="1"
        ItemsSource="{x:Bind ViewModel.Items, Mode=OneWay}"
        SelectedItems="{x:Bind ViewModel.SelectedItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
        SelectionMode="Multiple">
        <local:ListViewEx.Header>
            <TextBlock>
                <Run Text="Items: " />
                <Run Text="{x:Bind ViewModel.Items.Count, Mode=OneWay}" />
            </TextBlock>
        </local:ListViewEx.Header>
    </local:ListViewEx>
    <ListView
        Grid.Row="2"
        ItemsSource="{x:Bind ViewModel.SelectedItems, Mode=OneWay}">
        <ListView.Header>
            <TextBlock>
                <Run Text="Selected items: " />
                <Run Text="{x:Bind ViewModel.SelectedItems.Count, Mode=OneWay}" />
            </TextBlock>
        </ListView.Header>
    </ListView>
</Grid>

MainPage.xaml.cs

[ObservableObject]
public partial class MainPageViewModel
{
    [ObservableProperty]
    private ObservableCollection<string> items = new();

    [ObservableProperty]
    private ObservableCollection<string> selectedItems = new();

    public MainPageViewModel()
    {
        var ilist = items as IList;
    }

    [RelayCommand]
    private void LoadItems(double itemsCount)
    {
        for (int i = 0; i < itemsCount; i  )
        {
            Items.Add(i.ToString());
        }
    }
}

CodePudding user response:

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 /!\");
        }
    }
}
  • Related