Получение WPF ListView.SelectedItems в ViewModel

Есть несколько сообщений, посвященных добавлению возможности привязки данных для ListView.SelectedItems с нетривиальным количеством кода. В моем сценарии мне не нужно устанавливать его из ViewModel, просто получая выбранные элементы для выполнения действий над ними, и он запускается командой, поэтому обновление push также не требуется.

Есть ли простое решение (в терминах строк кода), возможно, в коде? Я в порядке с кодом, пока View и ViewModel не должны ссылаться друг на друга. Я думаю, что это более общий вопрос: "Лучшая практика для VM для получения данных из представления по требованию", но я не могу найти ничего...

Ответ 1

Чтобы получить SelectedItems только при выполнении команды, используйте CommandParameter и перейдите в ListView.SelectedItems.

<ListBox x:Name="listbox" ItemsSource="{Binding StringList}" SelectionMode="Multiple"/>
<Button Command="{Binding GetListItemsCommand}" CommandParameter="{Binding SelectedItems, ElementName=listbox}" Content="GetSelectedListBoxItems"/>

Ответ 2

Это может быть достигнуто с помощью триггеров взаимодействия, как показано ниже

  • Вам нужно будет добавить ссылку на

    Microsoft.Expression.Interactions System.Windows.Interactivity

Добавьте ниже xmlns в свой xaml

xmlns:i="http://schemas.microsoft.com/expression//2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"

Добавьте код ниже только в свой тег GridView

<GridView x:Name="GridName">
<i:Interaction.Triggers>
   <i:EventTrigger EventName="SelectionChanged">
       <i:InvokeCommandAction Command="{Binding Datacontext.SelectionChangedCommand, ElementName=YourUserControlName}" CommandParameter="{Binding SelectedItems, ElementName=GridName}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

Код Inside ViewModel объявляет свойство ниже

public DelegateCommand<object> SelectionChangedCommand {get;set;}

внутри конструктора Viewmodel инициализирует команду ниже

SelectionChangedCommand = new DelegateCommand<object> (items => {
   var itemList = (items as ObservableCollection<object>).Cast<YourDto>().ToList();
}

Ответ 3

Я не думаю, что это правильное условие считать, что View и ViewModel не должны знать друг друга; В представлении MVVM всегда знают о ViewModel.

Я также сталкивался с такой ситуацией, когда мне приходилось обращаться к ViewModel в виде кода, а затем заполнять некоторые данные (например, выбранные элементы), это становится необходимым при использовании 3-х сторонних элементов управления, таких как ListView, DataGrid и т.д.

Если прямое связывание свойства VM невозможно, я бы прослушал событие ListViw.SelectionChanged, а затем обновил свойство ViewModels SelectedItems в этом случае.

Update:

Чтобы включить вывод данных из ВМ, вы можете открыть интерфейс в представлении, который обрабатывает специфичные для просмотра функции, а ViewModel будет ссылаться на ваш вид через этот интерфейс; Использование интерфейса по-прежнему удерживает View и ViewModel в значительной степени развязанным, но я в общем не предпочитаю этого.

MVVM, предоставляя ассоциацию View to ViewModel

Я бы предпочел бы одобрить обработку события в представлении и сохранить обновленную виртуальную машину (с выбранными элементами), таким образом, VM не нужно беспокоиться о том, чтобы вытащить данные перед выполнением какой-либо операции, просто нужно использовать доступные данные (поскольку это всегда будет обновлено).

Ответ 4

Я могу заверить вас: SelectedItems действительно может быть связан как XAML CommandParameter

После большого поиска и поиска в Google я наконец нашел простое решение этой общей проблемы.

Чтобы заставить его работать, вы должны следовать ВСЕМ следующим правилам:

  • Следуя Ed Ball suggestion ', вы привязываетесь к базе данных XAML, определите свойство CommandParameter ПЕРЕД Командой свойство. Это очень трудоемкая ошибка.

  • Убедитесь, что методы ICommand CanExecute и Execute имеют параметр object. Таким образом, вы можете предотвратить исключение из-за исключения, которое возникает, когда тип привязки CommandParameter не соответствует типу параметра метода команды.

    private bool OnDeleteSelectedItemsCanExecute(object SelectedItems)  
    {
        // Your goes heres
    }
    
    private bool OnDeleteSelectedItemsExecute(object SelectedItems)  
    {
        // Your goes heres
    }
    

Например, вы можете отправить свойство listview/listbox SelectedItems для вас ICommand или listview/listbox. Отлично, не так ли?

Надеюсь, что это не позволяет кому-то потратить огромное количество времени, которое я сделал, чтобы выяснить, как получить SelectedItems как параметр CanExecute.

Ответ 5

Так как ни один из ответов не помог мне (используя SelectedItems как CommandParameter всегда null), вот решение для приложений Universal Windows Platform (UWP). Он работает с использованием Microsoft.Xaml.Interactivity и Microsoft.Xaml.Interactions.Core.

Здесь Вид:

<ListView x:Name="ItemsList">
    <Interactivity:Interaction.Behaviors>
         <Core:EventTriggerBehavior EventName="SelectionChanged">
             <Core:InvokeCommandAction Command="{x:Bind ViewModel.SelectedItemsChanged}" />
         </Core:EventTriggerBehavior>
    </Interactivity:Interaction.Behaviors>
    <!-- content etc. -->
</ListView>

Здесь ViewModel (RelayCommand является классом из MVVM Light):

private List<YourType> _selectedItems = new List<YourType>();

private RelayCommand<SelectionChangedEventArgs> _selectedItemsChanged;
public RelayCommand<SelectionChangedEventArgs> SelectedItemsChanged
{
    get
    {
        if (_selectedItemsChanged == null)
            _selectedItemsChanged = new RelayCommand<SelectionChangedEventArgs>((selectionChangedArgs) =>
            {
                // add a guard here to immediatelly return if you are modifying the original collection from code

                foreach (var item in selectionChangedArgs.AddedItems)
                    _selectedItems.Add((YourType)item);

                foreach (var item in selectionChangedArgs.RemovedItems)
                    _selectedItems.Remove((YourType)item);
            });
        return _selectedItemsChanged;
    }
}

Помните, что если вы собираетесь удалить элементы из исходной коллекции после завершения выбора (пользователь нажимает кнопку и т.д.), он также удалит элементы из вашего списка _selectedItems! Если вы сделаете это в цикле foreach, вы получите InvalidOperationException. Чтобы этого избежать, просто добавьте охрану в отмеченное место, например:

if (_deletingItems)
    return;

а затем в методе, где вы, например, удаляете элементы, выполните следующее:

_deletingItems = true;
foreach (var item in _selectedItems)
    YourOriginalCollection.Remove(item);
_deletingItems = false;