У меня есть приложение 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» в моих элементах? Но что? Как?)
Может быть, кто-то, кто пользуется этим шаблоном, тоже может мне здесь помочь?
Это просто смешно. Хотя WPF прекрасен во многих аспектах, все же есть мелкие основы, которые действительно не работают. Как реализация может пропустить простые привязки selectedItems для обновления свойства в модели представления. Выбранный ответ для меня больше похож на объезд 10000 миль, чтобы добраться до вашего заднего двора. Такие вещи иногда делают работу с wpf действительно странной.
Да, в некотором отношении это отстой, но это не сам WPF, а связующий материал, использующий M-V-VM.





Вам необходимо создать ViewModel, который имеет концепцию IsSelected и привязан к свойству IsSelected фактического ListBoxItem, который представляет его в представлении с использованием стандартной архитектуры привязок WPF.
Затем в вашем коде, который знает о вашей ViewModel, но не тот факт, что он представлен каким-либо конкретным View, можно просто использовать это свойство, чтобы узнать, какие элементы из модели фактически выбраны, независимо от выбора дизайнеров того, как они представлены в Вид.
Звучит хорошо, но как? Не могли бы вы дать мне еще несколько советов о том, как добиться этого с помощью ListBox и ListCollectionView?
Коллекция, которую вы собираетесь привязать к ItemsSource LB, будет содержать экземпляры класса VM. Затем вы создаете DataTemplate для этого класса виртуальной машины, который привязывает ListBoxItem.IsSelected к свойству IsSelected вашего класса. Когда LB заполняется, он будет использовать этот шаблон автоматически.
Что ж, я до сих пор не понимаю, как это сделать, тем более что (как я уже сказал в своем вопросе) можно выбрать несколько элементов.
Это правильный ответ. См. Мой ответ для примера кода.
Посмотрите эту запись в блоге Джоша Смита Первоначально выбранный элемент при привязке к сгруппированному ICollectionView
Раствор Дрю Марша работает очень хорошо, рекомендую. И у меня есть другое решение!
Модель View ViewModel - это Пассивный вид, вы также можете использовать Модель презентации для доступа к некоторым данным вашей презентации без связи с WPF (этот шаблон используется в примере Stocktrader для ПРИЗМА).
Вот еще один вариант шаблона View-Model-ViewModel, где ViewModel имеет доступ к представлению через интерфейс IView.
Я встречал довольно много сценариев, в которых вы не можете использовать привязку WPF, и тогда вам нужен способ в коде для синхронизации состояния между View и ViewModel.
Как это можно сделать, показано здесь:
Ответ Дрю Марша хорош, если у вас небольшой список, если у вас большой список, снижение производительности при нахождении всех выбранных вами элементов может быть неприятным! Мое любимое решение - создать прикрепленное свойство в вашем ListBox, которое затем привяжется к ObservableCollection, содержащему выбранные вами элементы. Затем с помощью прикрепленного свойства вы подписываетесь на событие SelectionChanged элементов для добавления / удаления элементов из вашей коллекции.
Звучит многообещающе - у вас случайно нет примеров кода для этого?
Решение Дэвида Роджерса великолепно и подробно описано в следующем вопросе:
Синхронизировать выбранные элементы в списке множественного выбора с коллекцией в 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, ничего себе, 100% мои мысли, я не мог бы сказать это лучше. Вероятно, трудно более цинично относиться к тем, кто рекламирует WPF как инструмент пользовательского интерфейса в ближайшие 10 лет. Если разработчики не могут предоставить такую простую функциональность, я просто не могу удержаться от смеха. Это не нацелено на серфинг, но меня расстраивает, потому что я потратил слишком много времени на решение этой проблемы.
Есть ли способ сделать это с помощью обычного <ListBox.ItemTemplate> вместо настройки ListBox.ItemContainerStyle.Template?
@Dai: Да, ItemContainerStyle.Template использовался здесь только для индивидуального представления. Это не обязательно. Вам нужно только скопировать файл кода поведения и сослаться на него в xaml: <Behaviors: SynchronizeSelectedItems Selections = "{Binding Selections}" />
Разве вы не видите ошибку в коде Microsoft, ребята? Он имеет this.AssociatedObject.SelectionChanged + = this.OnSelectedItemsChanged; в методе OnDetaching ().
Для меня лучший ответ - немного нарушить принцип 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.
Я часто задавал этот же вопрос, но не получил приемлемого ответа. stackoverflow.com/questions/1235772/…