Synchronisation multi-select ListBox avec MVVM
j'ai deux vues de certaines données: une vue de liste (a ListBox
maintenant, mais j'ai eu l'intention de passer à ListView
) et une représentation graphique sur une carte. Dans la vue de l'utilisateur peut cliquer sur un objet et il sera sélectionné dans les deux vues. Multi-Select est également possible, donc chaque ViewModel
l'instance a sa propre IsSelected
propriété.
actuellement je lie ListBoxItem.IsSelected
ViewModel.IsSelected
, mais cela ne fonctionne correctement que si le ListBox
n'est pas virtualisante (voir ici). Malheureusement, désactiver la virtualisation nuit aux performances et mon application est devenue trop lente.
donc je dois à nouveau activer la virtualisation. Afin de maintenir l' ViewModel.IsSelected
propriété des objets hors écran, j'ai remarqué que ListBox
et ListView
SelectionChanged
événement que je peux (sans doute) utiliser pour propager l'état de la sélection de l' ListBox/ListView
ViewModel
.
ma question Est, comment propager l'état de sélection dans la direction opposée? SelectedItems
propriété de ListBox/ListView
est en lecture seule! Supposons que l'utilisateur clique sur un élément dans la représentation graphique, mais il est hors de l'écran.w.r.t. la liste. Si je viens de mettre ViewModel.IsSelected
puis ListBox/ListView
ne sera pas au courant de la nouvelle sélection, et par conséquent, il échouera à désélectionner si l'utilisateur clique sur un élément différent dans la liste. Je pouvais l'appeler ListBox.ScrollIntoView
ViewModel
, mais il ya un couple de problèmes:
- dans mon UI il est en fait possible de sélectionner deux éléments avec un cliquez s'ils se trouvent au même endroit graphiquement, bien qu'ils puissent se trouver à des endroits complètement différents dans le
ListBox/ListView
. - il brise l'isolement de ViewModel (mon ViewModel n'est absolument pas au courant de WPF et j'aimerais le garder ainsi.)
Alors, mes chers experts WPF, des idées?
EDIT: j'ai fini par passer à un contrôle Infragistique et en utilisant une solution Moche et plutôt lente. Le point est, je n'ai plus besoin d'une réponse.
1 réponses
Vous pouvez créer un Comportement synchronisation
public class MultiSelectionBehavior : Behavior<ListBox>
{
protected override void OnAttached()
{
base.OnAttached();
if (SelectedItems != null)
{
AssociatedObject.SelectedItems.Clear();
foreach (var item in SelectedItems)
{
AssociatedObject.SelectedItems.Add(item);
}
}
}
public IList SelectedItems
{
get { return (IList)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IList), typeof(MultiSelectionBehavior), new UIPropertyMetadata(null, SelectedItemsChanged));
private static void SelectedItemsChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var behavior = o as MultiSelectionBehavior;
if (behavior == null)
return;
var oldValue = e.OldValue as INotifyCollectionChanged;
var newValue = e.NewValue as INotifyCollectionChanged;
if (oldValue != null)
{
oldValue.CollectionChanged -= behavior.SourceCollectionChanged;
behavior.AssociatedObject.SelectionChanged -= behavior.ListBoxSelectionChanged;
}
if (newValue != null)
{
behavior.AssociatedObject.SelectedItems.Clear();
foreach (var item in (IEnumerable)newValue)
{
behavior.AssociatedObject.SelectedItems.Add(item);
}
behavior.AssociatedObject.SelectionChanged += behavior.ListBoxSelectionChanged;
newValue.CollectionChanged += behavior.SourceCollectionChanged;
}
}
private bool _isUpdatingTarget;
private bool _isUpdatingSource;
void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_isUpdatingSource)
return;
try
{
_isUpdatingTarget = true;
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
AssociatedObject.SelectedItems.Remove(item);
}
}
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
AssociatedObject.SelectedItems.Add(item);
}
}
if (e.Action == NotifyCollectionChangedAction.Reset)
{
AssociatedObject.SelectedItems.Clear();
}
}
finally
{
_isUpdatingTarget = false;
}
}
private void ListBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_isUpdatingTarget)
return;
var selectedItems = this.SelectedItems;
if (selectedItems == null)
return;
try
{
_isUpdatingSource = true;
foreach (var item in e.RemovedItems)
{
selectedItems.Remove(item);
}
foreach (var item in e.AddedItems)
{
selectedItems.Add(item);
}
}
finally
{
_isUpdatingSource = false;
}
}
}
ce comportement peut être utilisé comme suit:
<ListBox ItemsSource="{Binding Items}"
DisplayMemberPath="Name"
SelectionMode="Extended">
<i:Interaction.Behaviors>
<local:MultiSelectionBehavior SelectedItems="{Binding SelectedItems}" />
</i:Interaction.Behaviors>
</ListBox>
(notez que le SelectedItems
collection dans votre Dernier doit être initialisé; le comportement ne sera pas réglé, il va seulement changer son contenu)