我来到这里是为了获取答案,现在我得到了很多好的回答。我将它们都合并到一个附加属性中,非常类似于Omar上面提供的那个,但在一个类中实现。处理INotifyCollectionChanged和切换列表。不会泄漏事件。我编写它以使代码更加简单易懂。使用C#编写,处理listbox selectedItems和dataGrid selectedItems。
这适用于DataGrid和ListBox。
(我刚学会如何使用GitHub)
GitHub https://github.com/ParrhesiaJoe/SelectedItemsAttachedWpf
使用方法:
<ListBox ItemsSource="{Binding MyList}" a:Ex.SelectedItems="{Binding ObservableList}"
SelectionMode="Extended"/>
<DataGrid ItemsSource="{Binding MyList}" a:Ex.SelectedItems="{Binding OtherObservableList}" />
这是代码。在Git上有一个小样例。
public class Ex : DependencyObject
{
public static readonly DependencyProperty IsSubscribedToSelectionChangedProperty = DependencyProperty.RegisterAttached(
"IsSubscribedToSelectionChanged", typeof(bool), typeof(Ex), new PropertyMetadata(default(bool)));
public static void SetIsSubscribedToSelectionChanged(DependencyObject element, bool value) { element.SetValue(IsSubscribedToSelectionChangedProperty, value); }
public static bool GetIsSubscribedToSelectionChanged(DependencyObject element) { return (bool)element.GetValue(IsSubscribedToSelectionChangedProperty); }
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached(
"SelectedItems", typeof(IList), typeof(Ex), new PropertyMetadata(default(IList), OnSelectedItemsChanged));
public static void SetSelectedItems(DependencyObject element, IList value) { element.SetValue(SelectedItemsProperty, value); }
public static IList GetSelectedItems(DependencyObject element) { return (IList)element.GetValue(SelectedItemsProperty); }
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is ListBox || d is MultiSelector))
throw new ArgumentException("Somehow this got attached to an object I don't support. ListBoxes and Multiselectors (DataGrid), people. Geesh =P!");
var selector = (Selector)d;
var oldList = e.OldValue as IList;
if (oldList != null)
{
var obs = oldList as INotifyCollectionChanged;
if (obs != null)
{
obs.CollectionChanged -= OnCollectionChanged;
}
if (e.NewValue == null)
{
selector.SelectionChanged -= OnSelectorSelectionChanged;
SetIsSubscribedToSelectionChanged(selector, false);
}
}
var newList = (IList)e.NewValue;
if (newList != null)
{
var obs = newList as INotifyCollectionChanged;
if (obs != null)
{
obs.CollectionChanged += OnCollectionChanged;
}
PushCollectionDataToSelectedItems(newList, selector);
var isSubscribed = GetIsSubscribedToSelectionChanged(selector);
if (!isSubscribed)
{
selector.SelectionChanged += OnSelectorSelectionChanged;
SetIsSubscribedToSelectionChanged(selector, true);
}
}
}
private static void PushCollectionDataToSelectedItems(IList obs, DependencyObject selector)
{
var listBox = selector as ListBox;
if (listBox != null)
{
if (obs.Count > 0)
{
listBox.SelectedItems.Clear();
foreach (var ob in obs) { listBox.SelectedItems.Add(ob); }
}
else
{
foreach (var ob in listBox.SelectedItems) { obs.Add(ob); }
}
return;
}
var grid = selector as MultiSelector;
if (grid != null)
{
if (obs.Count > 0)
{
grid.SelectedItems.Clear();
foreach (var ob in obs) { grid.SelectedItems.Add(ob); }
}
else
{
foreach (var ob in grid.SelectedItems) { obs.Add(ob); }
}
return;
}
throw new ArgumentException("Somehow this got attached to an object I don't support. ListBoxes and Multiselectors (DataGrid), people. Geesh =P!");
}
private static void OnSelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var dep = (DependencyObject)sender;
var items = GetSelectedItems(dep);
var col = items as INotifyCollectionChanged;
if (col != null) col.CollectionChanged -= OnCollectionChanged;
foreach (var oldItem in e.RemovedItems) items.Remove(oldItem);
foreach (var newItem in e.AddedItems) items.Add(newItem);
if (col != null) col.CollectionChanged += OnCollectionChanged;
}
private static void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
var listbox = sender as ListBox;
if (listbox != null)
{
listbox.SelectionChanged -= OnSelectorSelectionChanged;
if (e.Action == NotifyCollectionChangedAction.Reset) listbox.SelectedItems.Clear();
else
{
foreach (var oldItem in e.OldItems) listbox.SelectedItems.Remove(oldItem);
foreach (var newItem in e.NewItems) listbox.SelectedItems.Add(newItem);
}
listbox.SelectionChanged += OnSelectorSelectionChanged;
}
var grid = sender as MultiSelector;
if (grid != null)
{
grid.SelectionChanged -= OnSelectorSelectionChanged;
if (e.Action == NotifyCollectionChangedAction.Reset) grid.SelectedItems.Clear();
else
{
foreach (var oldItem in e.OldItems) grid.SelectedItems.Remove(oldItem);
foreach (var newItem in e.NewItems) grid.SelectedItems.Add(newItem);
}
grid.SelectionChanged += OnSelectorSelectionChanged;
}
}
}
DataGrid
,如果用户通过InputBinding
的KeyBinding
访问其ContextMenu
的MenuItem
的Command
,并且CommandParameter="{Binding ElementName=MyDataGrid, Path=SelectedItems}"
,它将把SelectedItems
传递给绑定的ICommand
。但是,如果通过ContextMenu
访问,则传递null
。我尝试过CommandParameter=
"{Binding SelectedItems}"
、"{Binding ElementName=MyDataGrid, Path=SelectedItems}"
和"{Binding RelativeSource={RelativeSource Self}, Path=SelectedItems}"
。是的,在Command
之前设置了CommandParameter
。 - Tom"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=SelectedItems}"
。FYI,ContextMenu
是通过<DataGrid.ContextMenu>
内联定义的,然后是ContextMenu
。 - TomCommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}}"
。 - Robin BennettCommandParameter
必须在Command
之前。我最终放弃并不得不采用其他方法。有了这个想法再次尝试,它就可以工作了! - Adam L. S.<MenuItem Header="预览" Command="{Binding PreviewSelectedItemsCommand}" CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}}"/>
- Kux