Home > Mobile >  UWP ListView Multi-Selection binding with MVVM Model
UWP ListView Multi-Selection binding with MVVM Model

Time:10-03

I'm trying to implement a multi-selection listview in UWP while staying as close as possible with the MVVM model. My problem is that I'm unable to get the selected items in the viewmodel via binding.

Looking at other answers on SO, I found out that the only possible way to achieve this is by binding via the Command and CommandParameter fields of my ListView. The answers, however, usually either focused on a simple code-behind approach or were made with WPF, which led to me being stuck at implementing the command.

A short note before my MWE: The program takes a .pdf file as input and displays it by converting every page into a BitmapImage. What I want is to select these single pages (BitmapImages) and perform an action on all selected items (in this case different actions; my MWE, however, only includes a single button). I'm trying to implement a multi-selection listview in UWP while staying as close as possible with the MVVM model. My problem is that I'm unable to get the selected items in the viewmodel via binding.

This is my MWE:

Model

 public class PdfPageModel
    {
        public string Title { get; set; }
        public PdfDocument PdfDoc { get; set; }

        public PdfPageModel(string Title, PdfDocument pdfdoc)
        {
            this.Title = Title;
            this.PdfDoc = pdfdoc;
        }

View

<ListView
                x:Name="PdfPageViewer"
                CanReorderItems="True" AllowDrop="True" CanDragItems="True"
                ItemsSource="{x:Bind ViewModel.PdfPages}"
                IsItemClickEnabled="True"
                SelectionMode="Multiple"
                
                IsMultiSelectCheckBoxEnabled="False"
                ScrollViewer.HorizontalScrollBarVisibility="Auto"
                ScrollViewer.HorizontalScrollMode="Enabled"
                ScrollViewer.IsHorizontalRailEnabled="True"
                ScrollViewer.ZoomMode="Enabled"
                IsZoomedInView="False"
                >
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <Image Source="{Binding }"/>
                    </DataTemplate>
                </ListView.ItemTemplate>

                <ListView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel
                            Orientation="Horizontal">
                        </StackPanel>
                    </ItemsPanelTemplate>
                </ListView.ItemsPanel>
            </ListView>
<Button
                CommandParameter="{Binding SelectedItems, Mode=OneWay, ElementName=PdfPageViewer}"
                Command="{x:Bind ViewModel.SelectedPagesCommand}"
                />

ViewModel

public ObservableCollection<BitmapImage> PdfPages { get; set; }

private ICommand _selectedPagesCommand;
public ICommand SelectedPagesCommand
{
    get
    {
        if (_selectedPagesCommand == null)
        {
                _selectedPagesCommand = new RelayCommand<?>(async () =>
                {
                    // ?? 
                });
        }
        return _selectedPagesCommand;
    }
}

CodePudding user response:

I finally found out how to achieve this. For anyone curious, or also stumbling on this problem, here's what I did.

I found this thread, which pretty much solved what I tried to do.

The OP implemented a converter (see below) to get all the selected items from the list as a ListView element. From the docs, I learned that the returned type of the SelectedItems property is the List Interface IList<>, which I implemented in my SelectedPagesCommand.

Then, they used this converter in the command call. (Note here that the SelectedItems call was removed, as it isn't needed anymore due to using the converter. This is a necessary and important step.) With this, I was finally able to get the selected elements in a list. The returned elements were of type System.__ComObject, instead of whatever was expected. This is easy to circumvene, though; simply cast the type to what it should be (in my case, it was a simple BitmapImage).

Converter

public class ListViewSelectedItemsConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        var listView = value as ListView;
        return listView.SelectedItems;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

View

<Page.Resources>
        <helper:ListViewSelectedItemsConverter x:Key="ListViewSelectedItemsConverter"/>
    </Page.Resources>
<!-- ... -->

<ListView
                x:Name="PdfPageViewer"
                CanReorderItems="True" AllowDrop="True" CanDragItems="True"
                ItemsSource="{x:Bind ViewModel.PdfPages}"
                IsItemClickEnabled="True"
                SelectionMode="Multiple"
                
                IsMultiSelectCheckBoxEnabled="False"
                ScrollViewer.HorizontalScrollBarVisibility="Auto"
                ScrollViewer.HorizontalScrollMode="Enabled"
                ScrollViewer.IsHorizontalRailEnabled="True"
                ScrollViewer.ZoomMode="Enabled"
                IsZoomedInView="False"
                >
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <Image Source="{Binding }"/>
                    </DataTemplate>
                </ListView.ItemTemplate>

                <ListView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel
                            Orientation="Horizontal">
                        </StackPanel>
                    </ItemsPanelTemplate>
                </ListView.ItemsPanel>
            </ListView>

<Button
                CommandParameter="{Binding ElementName=PdfPageViewer, Converter={StaticResource ListViewSelectedItemsConverter}}"
                Command="{x:Bind ViewModel.SelectedPagesCommand}"
                />

ViewModel

public ObservableCollection<BitmapImage> PdfPages { get; set; }

private ObservableCollection<BitmapImage> _selectedPdfPages;
public ObservableCollection<BitmapImage> SelectedPdfPages { get; set; }

private ICommand _selectedPagesCommand;
public ICommand SelectedPagesCommand
{
    get
    {
        if (_selectedPagesCommand == null)
        {
           _selectedPagesCommand = new RelayCommand<IList<object>>(async param =>
           {
              foreach (var i in param)
               {
                   var img = i as BitmapImage;
                   SelectedPdfPages.Add(img);
                }
             }
          }
     }
}
  • Related