WPF M-V-VM: получить выбранные элементы из ListCollectionView?

У меня есть приложение WPF, использующее шаблон Model-View-ViewModel.
В моей модели ViewModel у меня есть ListCollectionView для хранения списка элементов. Этот ListCollectionView привязан к ListBox в моем представлении.

<ListBox Grid.Row = "1" ItemsSource = "{Binding Useragents}" SelectionMode = "Multiple"/>

ListBox имеет SelectionMode = Multiple, поэтому вы можете выбрать больше элементов за один раз. Теперь ViewModel должен знать, какие элементы были выбраны.

Проблема в следующем: в шаблоне View-Model-ViewModel ViewModel не имеет доступа к View, поэтому я не могу просто спросить ListBox, какие элементы были выбраны. Все, что у меня есть, это ListCollectionView, но я не могу найти способ узнать, какие элементы там были выбраны.

Итак, как мне узнать, какие элементы были выбраны в ListBox? Или трюк для достижения этого (возможно, привязать что-то к логическому «IsSelected» в моих элементах? Но что? Как?)

Может быть, кто-то, кто пользуется этим шаблоном, тоже может мне здесь помочь?

Я часто задавал этот же вопрос, но не получил приемлемого ответа. stackoverflow.com/questions/1235772/…

jpierson 04.06.2010 18:40

Это просто смешно. Хотя WPF прекрасен во многих аспектах, все же есть мелкие основы, которые действительно не работают. Как реализация может пропустить простые привязки selectedItems для обновления свойства в модели представления. Выбранный ответ для меня больше похож на объезд 10000 миль, чтобы добраться до вашего заднего двора. Такие вещи иногда делают работу с wpf действительно странной.

Matthias Wolf 26.02.2015 12:51

Да, в некотором отношении это отстой, но это не сам WPF, а связующий материал, использующий M-V-VM.

Sam 27.02.2015 18:35
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
16
3
19 719
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Вам необходимо создать ViewModel, который имеет концепцию IsSelected и привязан к свойству IsSelected фактического ListBoxItem, который представляет его в представлении с использованием стандартной архитектуры привязок WPF.

Затем в вашем коде, который знает о вашей ViewModel, но не тот факт, что он представлен каким-либо конкретным View, можно просто использовать это свойство, чтобы узнать, какие элементы из модели фактически выбраны, независимо от выбора дизайнеров того, как они представлены в Вид.

Звучит хорошо, но как? Не могли бы вы дать мне еще несколько советов о том, как добиться этого с помощью ListBox и ListCollectionView?

Sam 17.01.2009 01:19

Коллекция, которую вы собираетесь привязать к ItemsSource LB, будет содержать экземпляры класса VM. Затем вы создаете DataTemplate для этого класса виртуальной машины, который привязывает ListBoxItem.IsSelected к свойству IsSelected вашего класса. Когда LB заполняется, он будет использовать этот шаблон автоматически.

Drew Marsh 17.01.2009 03:15

Что ж, я до сих пор не понимаю, как это сделать, тем более что (как я уже сказал в своем вопросе) можно выбрать несколько элементов.

Sam 25.05.2010 13:09

Это правильный ответ. См. Мой ответ для примера кода.

surfen 03.12.2011 21:34

Раствор Дрю Марша работает очень хорошо, рекомендую. И у меня есть другое решение!

Модель View ViewModel - это Пассивный вид, вы также можете использовать Модель презентации для доступа к некоторым данным вашей презентации без связи с WPF (этот шаблон используется в примере Stocktrader для ПРИЗМА).

Вот еще один вариант шаблона View-Model-ViewModel, где ViewModel имеет доступ к представлению через интерфейс IView.

Я встречал довольно много сценариев, в которых вы не можете использовать привязку WPF, и тогда вам нужен способ в коде для синхронизации состояния между View и ViewModel.

Как это можно сделать, показано здесь:

Платформа приложений WPF (WAF)

Ответ Дрю Марша хорош, если у вас небольшой список, если у вас большой список, снижение производительности при нахождении всех выбранных вами элементов может быть неприятным! Мое любимое решение - создать прикрепленное свойство в вашем ListBox, которое затем привяжется к ObservableCollection, содержащему выбранные вами элементы. Затем с помощью прикрепленного свойства вы подписываетесь на событие SelectionChanged элементов для добавления / удаления элементов из вашей коллекции.

Звучит многообещающе - у вас случайно нет примеров кода для этого?

Sam 25.05.2010 13:12

Решение Дэвида Роджерса великолепно и подробно описано в следующем вопросе:

Синхронизировать выбранные элементы в списке множественного выбора с коллекцией в ViewModel

Ответ принят как подходящий

Эталонная реализация PRISM MVVM имеет поведение, называемое SynchronizeSelectedItems, используемое в Prism4 \ MVVM RI \ MVVM.Client \ Views \ MultipleSelectionView.xaml, которое синхронизирует отмеченные элементы со свойством ViewModel с именем Selections:

        <ListBox Grid.Column = "0" Grid.Row = "1" IsTabStop = "False" SelectionMode = "Multiple"
                 ItemsSource = "{Binding Question.Range}" Margin = "5">

            <ListBox.ItemContainerStyle>
                <!-- Custom style to show the multi-selection list box as a collection of check boxes -->
                <Style TargetType = "ListBoxItem">
                    <Setter Property = "Template">
                        <Setter.Value>
                            <ControlTemplate TargetType = "ListBoxItem">
                                <Grid Background = "Transparent">
                                    <CheckBox IsChecked = "{Binding IsSelected, RelativeSource = {RelativeSource TemplatedParent}, Mode=TwoWay}" 
                                              IsHitTestVisible = "False" IsTabStop = "True"
                                              AutomationProperties.AutomationId = "CheckBoxAutomationId">
                                        <ContentPresenter/>
                                    </CheckBox>
                                </Grid>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ListBox.ItemContainerStyle>
            <i:Interaction.Behaviors>
                <!-- Custom behavior that synchronizes the selected items with the view models collection -->
                <Behaviors:SynchronizeSelectedItems Selections = "{Binding Selections}"/>
            </i:Interaction.Behaviors>
        </ListBox>

Перейдите к http://compositewpf.codeplex.com/ и возьмите все это или используйте это:

//===================================================================================
// Microsoft patterns & practices
// Composite Application Guidance for Windows Presentation Foundation and Silverlight
//===================================================================================
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===================================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===================================================================================
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace MVVM.Client.Infrastructure.Behaviors
{
    /// <summary>
    /// Custom behavior that synchronizes the list in <see cref = "ListBox.SelectedItems"/> with a collection.
    /// </summary>
    /// <remarks>
    /// This behavior uses a weak event handler to listen for changes on the synchronized collection.
    /// </remarks>
    public class SynchronizeSelectedItems : Behavior<ListBox>
    {
        public static readonly DependencyProperty SelectionsProperty =
            DependencyProperty.Register(
                "Selections",
                typeof(IList),
                typeof(SynchronizeSelectedItems),
                new PropertyMetadata(null, OnSelectionsPropertyChanged));

        private bool updating;
        private WeakEventHandler<SynchronizeSelectedItems, object, NotifyCollectionChangedEventArgs> currentWeakHandler;

        [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly",
            Justification = "Dependency property")]
        public IList Selections
        {
            get { return (IList)this.GetValue(SelectionsProperty); }
            set { this.SetValue(SelectionsProperty, value); }
        }

        protected override void OnAttached()
        {
            base.OnAttached();

            this.AssociatedObject.SelectionChanged += this.OnSelectedItemsChanged;
            this.UpdateSelectedItems();
        }

        protected override void OnDetaching()
        {
            this.AssociatedObject.SelectionChanged += this.OnSelectedItemsChanged;

            base.OnDetaching();
        }

        private static void OnSelectionsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var behavior = d as SynchronizeSelectedItems;

            if (behavior != null)
            {
                if (behavior.currentWeakHandler != null)
                {
                    behavior.currentWeakHandler.Detach();
                    behavior.currentWeakHandler = null;
                }

                if (e.NewValue != null)
                {
                    var notifyCollectionChanged = e.NewValue as INotifyCollectionChanged;
                    if (notifyCollectionChanged != null)
                    {
                        behavior.currentWeakHandler =
                            new WeakEventHandler<SynchronizeSelectedItems, object, NotifyCollectionChangedEventArgs>(
                                behavior,
                                (instance, sender, args) => instance.OnSelectionsCollectionChanged(sender, args),
                                (listener) => notifyCollectionChanged.CollectionChanged -= listener.OnEvent);
                        notifyCollectionChanged.CollectionChanged += behavior.currentWeakHandler.OnEvent;
                    }

                    behavior.UpdateSelectedItems();
                }
            }
        }

        private void OnSelectedItemsChanged(object sender, SelectionChangedEventArgs e)
        {
            this.UpdateSelections(e);
        }

        private void UpdateSelections(SelectionChangedEventArgs e)
        {
            this.ExecuteIfNotUpdating(
                () =>
                {
                    if (this.Selections != null)
                    {
                        foreach (var item in e.AddedItems)
                        {
                            this.Selections.Add(item);
                        }

                        foreach (var item in e.RemovedItems)
                        {
                            this.Selections.Remove(item);
                        }
                    }
                });
        }

        private void OnSelectionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            this.UpdateSelectedItems();
        }

        private void UpdateSelectedItems()
        {
            this.ExecuteIfNotUpdating(
                () =>
                {
                    if (this.AssociatedObject != null)
                    {
                        this.AssociatedObject.SelectedItems.Clear();
                        foreach (var item in this.Selections ?? new object[0])
                        {
                            this.AssociatedObject.SelectedItems.Add(item);
                        }
                    }
                });
        }

        private void ExecuteIfNotUpdating(Action execute)
        {
            if (!this.updating)
            {
                try
                {
                    this.updating = true;
                    execute();
                }
                finally
                {
                    this.updating = false;
                }
            }
        }
    }
}

На мгновение я задумался. Теперь я дрожу от страха. Так вот что нужно, чтобы выяснить, какие элементы выбраны в списке? Я думаю, что какой-то архитектор должен умереть.

stmax 28.01.2012 19:41

@stmax, ничего себе, 100% мои мысли, я не мог бы сказать это лучше. Вероятно, трудно более цинично относиться к тем, кто рекламирует WPF как инструмент пользовательского интерфейса в ближайшие 10 лет. Если разработчики не могут предоставить такую ​​простую функциональность, я просто не могу удержаться от смеха. Это не нацелено на серфинг, но меня расстраивает, потому что я потратил слишком много времени на решение этой проблемы.

Matthias Wolf 26.02.2015 12:54

Есть ли способ сделать это с помощью обычного <ListBox.ItemTemplate> вместо настройки ListBox.ItemContainerStyle.Template?

Dai 04.09.2015 02:26

@Dai: Да, ItemContainerStyle.Template использовался здесь только для индивидуального представления. Это не обязательно. Вам нужно только скопировать файл кода поведения и сослаться на него в xaml: <Behaviors: SynchronizeSelectedItems Selections = "{Binding Selections}" />

surfen 13.09.2015 13:10

Разве вы не видите ошибку в коде Microsoft, ребята? Он имеет this.AssociatedObject.SelectionChanged + = this.OnSelectedItemsChanged; в методе OnDetaching ().

Vitaly 07.09.2016 03:28

Для меня лучший ответ - немного нарушить принцип MVVM.

О коде позади 1. Создайте копию своей модели viewModel. 2. добавить обработчик событий SelectionChanged 3. перебрать выбранные вами элементы и добавить каждый элемент в свой список viewModel.

ViewModel viewModel = new ViewModel();

viewModel.SelectedModules = new ObservableCollection<string>();

foreach (var selectedModule in listBox1.SelectedItems)
{
    viewModel.SelectedModules.Add(selectedModule.ToString());
}

Мне кажется, что это простой способ снова и снова ломать MVVM. Решение для поведения - это практически одноразовая запись, использующая все время. Свойство IsSelected в ViewModel я использую уже давно. Можно легко создать модель представления Selectable <T>, которая добавляет свойство IsSelected поверх T.

tomasz_kajetan_stanczak 04.09.2013 19:52

Другие вопросы по теме