[英]Bind to SelectedItems from DataGrid or ListBox in MVVM
Just doing some light reading on WPF where I need to bind the selectedItems from a DataGrid but I am unable to come up with anything tangible.只是在 WPF 上做一些简单的阅读,我需要从 DataGrid 绑定 selectedItems,但我无法想出任何有形的东西。 I just need the selected objects.
我只需要选定的对象。
DataGrid:数据网格:
<DataGrid Grid.Row="5"
Grid.Column="0"
Grid.ColumnSpan="4"
Name="ui_dtgAgreementDocuments"
ItemsSource="{Binding Path=Documents, Mode=TwoWay}"
SelectedItem="{Binding Path=DocumentSelection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="White"
SelectionMode="Extended" Margin="2,5"
IsReadOnly="True"
CanUserAddRows="False"
CanUserReorderColumns="False"
CanUserResizeRows="False"
GridLinesVisibility="None"
HorizontalScrollBarVisibility="Hidden"
columnHeaderStyle="{StaticResource GreenTea}"
HeadersVisibility="Column"
BorderThickness="2"
BorderBrush="LightGray"
CellStyle="{StaticResource NonSelectableDataGridCellStyle}"
SelectionUnit="FullRow"
HorizontalContentAlignment="Stretch" AutoGenerateColumns="False">
SelectedItems is bindable as a XAML CommandParameter . SelectedItems可绑定为 XAML CommandParameter 。
After a lot of digging and googling, I have finally found a simple solution to this common issue.经过大量的挖掘和谷歌搜索,我终于找到了解决这个常见问题的简单方法。
To make it work you must follow ALL the following rules :要使其正常工作,您必须遵循以下所有规则:
Following Ed Ball's suggestion ', on you XAML command databinding, define CommandParameter property BEFORE Command property.按照Ed Ball 的建议,在您的 XAML 命令数据绑定上,在Command属性之前定义CommandParameter属性。 This a very time-consuming bug.
这是一个非常耗时的错误。
Make sure your ICommand 's CanExecute and Execute methods have a parameter of object type.确保您的ICommand的CanExecute和Execute方法具有对象类型的参数。 This way you can prevent silenced cast exceptions that occurs whenever databinding CommandParameter type does not match your command method's parameter type.
这样,您可以防止数据绑定CommandParameter类型与命令方法的参数类型不匹配时发生的静默强制转换异常。
private bool OnDeleteSelectedItemsCanExecute(object SelectedItems) { // Your code goes here } private bool OnDeleteSelectedItemsExecute(object SelectedItems) { // Your code goes here }
For example, you can either send a listview/listbox's SelectedItems property to you ICommand methods or the listview/listbox it self.例如,您可以将 listview/listbox 的SelectedItems属性发送给您的ICommand方法或它自己的 listview/listbox。 Great, isn't it?
太好了,不是吗?
Hope it prevents someone spending the huge amount of time I did to figure out how to receive SelectedItems as CanExecute parameter.希望它可以防止有人花费大量时间来弄清楚如何接收SelectedItems作为CanExecute参数。
You cannot bind to SelectedItems
because it is a read-only property.您不能绑定到
SelectedItems
,因为它是只读属性。 One fairly MVVM-friendly way to work around this is to bind to the IsSelected
property of DataGridRow
.解决此问题的一种相当 MVVM 友好的方法是绑定到
DataGridRow
的IsSelected
属性。
You can set up the binding like this:您可以像这样设置绑定:
<DataGrid ItemsSource="{Binding DocumentViewModels}"
SelectionMode="Extended">
<DataGrid.Resources>
<Style TargetType="DataGridRow">
<Setter Property="IsSelected"
Value="{Binding IsSelected}" />
</Style>
</DataGrid.Resources>
</DataGrid>
Then you need to create a DocumentViewModel
that inherits from ViewModelBase
(or whatever MVVM base class you are using) and has the properties of your Document
you want to present in the DataGrid, as well as an IsSelected
property.然后,您需要创建一个继承自
ViewModelBase
(或您正在使用的任何 MVVM 基类)的DocumentViewModel
,并具有您希望在 DataGrid 中显示的Document
的属性,以及一个IsSelected
属性。
Then, in your main view model, you create a List(Of DocumentViewModel)
called DocumentViewModels
to bind your DataGrid
to.然后,在您的主视图模型中,您创建一个名为
DocumentViewModels
的List(Of DocumentViewModel)
以将您的DataGrid
绑定到。 (Note: if you will be adding/removing items from the list, use an ObservableCollection(T)
instead.) (注意:如果您要从列表中添加/删除项目,请改用
ObservableCollection(T)
。)
Now, here's the tricky part.现在,这是棘手的部分。 You need to hook into the
PropertyChanged
event of each DocumentViewModel
in your list, like this:您需要挂钩列表中每个
DocumentViewModel
的PropertyChanged
事件,如下所示:
For Each documentViewModel As DocumentViewModel In DocumentViewModels
documentViewModel.PropertyChanged += DocumentViewModel_PropertyChanged
Next
This allows you to respond to changes in any DocumentViewModel
.这允许您响应任何
DocumentViewModel
中的更改。
Finally, in DocumentViewModel_PropertyChanged
, you can loop through your list (or use a Linq query) to grab the info for each item where IsSelected = True
.最后,在
DocumentViewModel_PropertyChanged
中,您可以遍历您的列表(或使用 Linq 查询)来获取IsSelected = True
的每个项目的信息。
With a bit of trickery you can extend the DataGrid to create a bindable version of the SelectedItems
property.通过一些技巧,您可以扩展 DataGrid 以创建
SelectedItems
属性的可绑定版本。 My solution requires the binding to have Mode=OneWayToSource
since I only want to read from the property anyway, but it might be possible to extend my solution to allow the property to be read-write.我的解决方案要求绑定具有
Mode=OneWayToSource
,因为无论如何我只想从该属性中读取,但可以扩展我的解决方案以允许该属性是可读写的。
I assume a similar technique could be used for ListBox, but I haven't tried it.我认为类似的技术可以用于 ListBox,但我还没有尝试过。
public class BindableMultiSelectDataGrid : DataGrid
{
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IList), typeof(BindableMultiSelectDataGrid), new PropertyMetadata(default(IList)));
public new IList SelectedItems
{
get { return (IList)GetValue(SelectedItemsProperty); }
set { throw new Exception("This property is read-only. To bind to it you must use 'Mode=OneWayToSource'."); }
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
SetValue(SelectedItemsProperty, base.SelectedItems);
}
}
I came here for the answer and I got a lot of great ones.我来这里是为了答案,我得到了很多很棒的答案。 I combined them all into an attached property, very similar to the one offered by Omar above, but in one class.
我将它们全部组合成一个附加属性,与上面 Omar 提供的非常相似,但在一个类中。 Handles INotifyCollectionChanged and switching the list out.
处理 INotifyCollectionChanged 并切换列表。 Doesn't leak events.
不泄漏事件。 I wrote it so it would be pretty simple code to follow.
我写了它,所以它是非常简单的代码。 Written in C#, handles listbox selectedItems and dataGrid selectedItems.
用 C# 编写,处理列表框 selectedItems 和 dataGrid selectedItems。
This works on both DataGrid and ListBox.这适用于 DataGrid 和 ListBox。
(I just learned how to use GitHub) GitHub https://github.com/ParrhesiaJoe/SelectedItemsAttachedWpf (我刚刚学会了如何使用 GitHub)GitHub https://github.com/ParrhesiaJoe/SelectedItemsAttachedWpf
To Use:使用:
<ListBox ItemsSource="{Binding MyList}" a:Ex.SelectedItems="{Binding ObservableList}"
SelectionMode="Extended"/>
<DataGrid ItemsSource="{Binding MyList}" a:Ex.SelectedItems="{Binding OtherObservableList}" />
And here's the code.这是代码。 There is a little sample on Git.
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); }
/// <summary>
/// Attaches a list or observable collection to the grid or listbox, syncing both lists (one way sync for simple lists).
/// </summary>
/// <param name="d">The DataGrid or ListBox</param>
/// <param name="e">The list to sync to.</param>
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 we're orphaned, disconnect lb/dg events.
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);
}
}
}
/// <summary>
/// Initially set the selected items to the items in the newly connected collection,
/// unless the new collection has no selected items and the listbox/grid does, in which case
/// the flow is reversed. The data holder sets the state. If both sides hold data, then the
/// bound IList wins and dominates the helpless wpf control.
/// </summary>
/// <param name="obs">The list to sync to</param>
/// <param name="selector">The grid or listbox</param>
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;
}
// Maybe other things will use the multiselector base... who knows =P
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!");
}
/// <summary>
/// When the listbox or grid fires a selectionChanged even, we update the attached list to
/// match it.
/// </summary>
/// <param name="sender">The listbox or grid</param>
/// <param name="e">Items added and removed.</param>
private static void OnSelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var dep = (DependencyObject)sender;
var items = GetSelectedItems(dep);
var col = items as INotifyCollectionChanged;
// Remove the events so we don't fire back and forth, then re-add them.
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;
}
/// <summary>
/// When the attached object implements INotifyCollectionChanged, the attached listbox
/// or grid will have its selectedItems adjusted by this handler.
/// </summary>
/// <param name="sender">The listbox or grid</param>
/// <param name="e">The added and removed items</param>
private static void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Push the changes to the selected item.
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;
}
}
}
Here is one simple solution.这是一个简单的解决方案。 This way you can pass/update any data to ViewModel
这样您就可以将任何数据传递/更新到 ViewModel
Designer.xaml设计器.xaml
<DataGrid Grid.Row="1" Name="dgvMain" SelectionChanged="DataGrid_SelectionChanged" />
Designer.cs设计器.cs
ViewModel mModel = null;
public Designer()
{
InitializeComponent();
mModel = new ViewModel();
this.DataContext = mModel;
}
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
mModel.SelectedItems = dgvMain.SelectedItems;
}
ViewModel.cs视图模型.cs
public class ViewModel
{
public IList SelectedItems { get; set; }
}
I have a solution for this using a workaround that fit my needs.我有一个解决方案,使用适合我需要的解决方法。
Make an EventToCommand
on the ListItemTemplate
on MouseUp
and as a CommandParameter
send the SelectedItems
collection在
MouseUp
上的ListItemTemplate
上创建一个EventToCommand
并作为CommandParameter
发送SelectedItems
集合
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseUp">
<helpers:EventToCommand Command="{Binding DataContext.SelectionChangedUpdate,
RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding ElementName=personsList, Path=SelectedItems}" />
</i:EventTrigger>
</i:Interaction.Triggers>
That way you can have a command in the viewmodel that handles this or saves the selected items for later use.这样,您可以在视图模型中拥有一个命令来处理此问题或保存所选项目以供以后使用。 Have fun coding
玩得开心编码
For me the easiest route was to populate the ViewModel property in the SelectionChanged event.对我来说,最简单的方法是在 SelectionChanged 事件中填充 ViewModel 属性。
private void MyDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
(DataContext as MyViewModel).SelectedItems.Clear();
(DataContext as MyViewModel).SelectedItems.AddRange(MyDataGrid.SelectedItems.OfType<ItemType>());
}
Binding directly do view model, little tricky version:直接绑定做视图模型,小窍门的版本:
1) Create ICommand: 1)创建ICommand:
public class GetSelectedItemsCommand : ICommand
{
public GetSelectedItemsCommand(Action<object> action)
{
_action = action;
}
private readonly Action<object> _action;
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action(parameter);
}
}
2) Create DataGrid 2) 创建数据网格
<DataGrid x:Name="DataGridOfDesperatePeople" SelectionMode="Extended">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction CommandParameter="{Binding ElementName=DataGridOfDesperatePeople, Path=SelectedItems}" Command="{Binding SelectedItemsCommand }" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
3) Create in View Model 3) 在视图模型中创建
public List<YourClassOfItemInTheGrid> SelectedItems { get; set; } = new List<YourClassOfItemInTheGrid>();
public ICommand SelectedItemsCommand
{
get
{
return new GetSelectedItemsCommand(list =>
{
SelectedItems.Clear();
IList items = (IList)list;
IEnumerable<YourClassOfItemInTheGrid> collection = items.Cast<YourClassOfItemInTheGrid>();
SelectedItems = collection.ToList();
});
}
}
I know this post is a little old and has been answered.我知道这篇文章有点老了,已经得到了回答。 But I came up with a non MVVM answer.
但我想出了一个非 MVVM 答案。 Its easy and works for me.
它很简单,对我有用。 Add another DataGrid, assuming your Selected Collection is SelectedResults:
添加另一个 DataGrid,假设您的 Selected Collection 是 SelectedResults:
<DataGrid x:Name="SelectedGridRows"
ItemsSource="{Binding SelectedResults,Mode=OneWayToSource}"
Visibility="Collapsed" >
And in code behind, add this to the Constructor:在后面的代码中,将其添加到构造函数中:
public ClassConstructor()
{
InitializeComponent();
OriginalGrid.SelectionChanged -= OriginalGrid_SelectionChanged;
OriginalGrid.SelectionChanged += OriginalGrid_SelectionChanged;
}
private void OriginalGrid_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
SelectedGridRows.ItemsSource = OriginalGrid.SelectedItems;
}
This will work:这将起作用:
MultiSelectorBehaviours.vb MultiSelectorBehaviours.vb
Imports System.Collections
Imports System.Windows
Imports System.Windows.Controls.Primitives
Imports System.Windows.Controls
Imports System
Public NotInheritable Class MultiSelectorBehaviours
Private Sub New()
End Sub
Public Shared ReadOnly SynchronizedSelectedItems As DependencyProperty = _
DependencyProperty.RegisterAttached("SynchronizedSelectedItems", GetType(IList), GetType(MultiSelectorBehaviours), New PropertyMetadata(Nothing, New PropertyChangedCallback(AddressOf OnSynchronizedSelectedItemsChanged)))
Private Shared ReadOnly SynchronizationManagerProperty As DependencyProperty = DependencyProperty.RegisterAttached("SynchronizationManager", GetType(SynchronizationManager), GetType(MultiSelectorBehaviours), New PropertyMetadata(Nothing))
''' <summary>
''' Gets the synchronized selected items.
''' </summary>
''' <param name="dependencyObject">The dependency object.</param>
''' <returns>The list that is acting as the sync list.</returns>
Public Shared Function GetSynchronizedSelectedItems(ByVal dependencyObject As DependencyObject) As IList
Return DirectCast(dependencyObject.GetValue(SynchronizedSelectedItems), IList)
End Function
''' <summary>
''' Sets the synchronized selected items.
''' </summary>
''' <param name="dependencyObject">The dependency object.</param>
''' <param name="value">The value to be set as synchronized items.</param>
Public Shared Sub SetSynchronizedSelectedItems(ByVal dependencyObject As DependencyObject, ByVal value As IList)
dependencyObject.SetValue(SynchronizedSelectedItems, value)
End Sub
Private Shared Function GetSynchronizationManager(ByVal dependencyObject As DependencyObject) As SynchronizationManager
Return DirectCast(dependencyObject.GetValue(SynchronizationManagerProperty), SynchronizationManager)
End Function
Private Shared Sub SetSynchronizationManager(ByVal dependencyObject As DependencyObject, ByVal value As SynchronizationManager)
dependencyObject.SetValue(SynchronizationManagerProperty, value)
End Sub
Private Shared Sub OnSynchronizedSelectedItemsChanged(ByVal dependencyObject As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
If e.OldValue IsNot Nothing Then
Dim synchronizer As SynchronizationManager = GetSynchronizationManager(dependencyObject)
synchronizer.StopSynchronizing()
SetSynchronizationManager(dependencyObject, Nothing)
End If
Dim list As IList = TryCast(e.NewValue, IList)
Dim selector As Selector = TryCast(dependencyObject, Selector)
' check that this property is an IList, and that it is being set on a ListBox
If list IsNot Nothing AndAlso selector IsNot Nothing Then
Dim synchronizer As SynchronizationManager = GetSynchronizationManager(dependencyObject)
If synchronizer Is Nothing Then
synchronizer = New SynchronizationManager(selector)
SetSynchronizationManager(dependencyObject, synchronizer)
End If
synchronizer.StartSynchronizingList()
End If
End Sub
''' <summary>
''' A synchronization manager.
''' </summary>
Private Class SynchronizationManager
Private ReadOnly _multiSelector As Selector
Private _synchronizer As TwoListSynchronizer
''' <summary>
''' Initializes a new instance of the <see cref="SynchronizationManager"/> class.
''' </summary>
''' <param name="selector">The selector.</param>
Friend Sub New(ByVal selector As Selector)
_multiSelector = selector
End Sub
''' <summary>
''' Starts synchronizing the list.
''' </summary>
Public Sub StartSynchronizingList()
Dim list As IList = GetSynchronizedSelectedItems(_multiSelector)
If list IsNot Nothing Then
_synchronizer = New TwoListSynchronizer(GetSelectedItemsCollection(_multiSelector), list)
_synchronizer.StartSynchronizing()
End If
End Sub
''' <summary>
''' Stops synchronizing the list.
''' </summary>
Public Sub StopSynchronizing()
_synchronizer.StopSynchronizing()
End Sub
Public Shared Function GetSelectedItemsCollection(ByVal selector As Selector) As IList
If TypeOf selector Is MultiSelector Then
Return TryCast(selector, MultiSelector).SelectedItems
ElseIf TypeOf selector Is ListBox Then
Return TryCast(selector, ListBox).SelectedItems
Else
Throw New InvalidOperationException("Target object has no SelectedItems property to bind.")
End If
End Function
End Class
End Class
IListItemConverter.vb IListItemConverter.vb
''' <summary>
''' Converts items in the Master list to Items in the target list, and back again.
''' </summary>
Public Interface IListItemConverter
''' <summary>
''' Converts the specified master list item.
''' </summary>
''' <param name="masterListItem">The master list item.</param>
''' <returns>The result of the conversion.</returns>
Function Convert(ByVal masterListItem As Object) As Object
''' <summary>
''' Converts the specified target list item.
''' </summary>
''' <param name="targetListItem">The target list item.</param>
''' <returns>The result of the conversion.</returns>
Function ConvertBack(ByVal targetListItem As Object) As Object
End Interface
TwoListSynchronizer.vb TwoListSynchronizer.vb
Imports System.Collections
Imports System.Collections.Specialized
Imports System.Linq
Imports System.Windows
''' <summary>
''' Keeps two lists synchronized.
''' </summary>
Public Class TwoListSynchronizer
Implements IWeakEventListener
Private Shared ReadOnly DefaultConverter As IListItemConverter = New DoNothingListItemConverter()
Private ReadOnly _masterList As IList
Private ReadOnly _masterTargetConverter As IListItemConverter
Private ReadOnly _targetList As IList
''' <summary>
''' Initializes a new instance of the <see cref="TwoListSynchronizer"/> class.
''' </summary>
''' <param name="masterList">The master list.</param>
''' <param name="targetList">The target list.</param>
''' <param name="masterTargetConverter">The master-target converter.</param>
Public Sub New(ByVal masterList As IList, ByVal targetList As IList, ByVal masterTargetConverter As IListItemConverter)
_masterList = masterList
_targetList = targetList
_masterTargetConverter = masterTargetConverter
End Sub
''' <summary>
''' Initializes a new instance of the <see cref="TwoListSynchronizer"/> class.
''' </summary>
''' <param name="masterList">The master list.</param>
''' <param name="targetList">The target list.</param>
Public Sub New(ByVal masterList As IList, ByVal targetList As IList)
Me.New(masterList, targetList, DefaultConverter)
End Sub
Private Delegate Sub ChangeListAction(ByVal list As IList, ByVal e As NotifyCollectionChangedEventArgs, ByVal converter As Converter(Of Object, Object))
''' <summary>
''' Starts synchronizing the lists.
''' </summary>
Public Sub StartSynchronizing()
ListenForChangeEvents(_masterList)
ListenForChangeEvents(_targetList)
' Update the Target list from the Master list
SetListValuesFromSource(_masterList, _targetList, AddressOf ConvertFromMasterToTarget)
' In some cases the target list might have its own view on which items should included:
' so update the master list from the target list
' (This is the case with a ListBox SelectedItems collection: only items from the ItemsSource can be included in SelectedItems)
If Not TargetAndMasterCollectionsAreEqual() Then
SetListValuesFromSource(_targetList, _masterList, AddressOf ConvertFromTargetToMaster)
End If
End Sub
''' <summary>
''' Stop synchronizing the lists.
''' </summary>
Public Sub StopSynchronizing()
StopListeningForChangeEvents(_masterList)
StopListeningForChangeEvents(_targetList)
End Sub
''' <summary>
''' Receives events from the centralized event manager.
''' </summary>
''' <param name="managerType">The type of the <see cref="T:System.Windows.WeakEventManager"/> calling this method.</param>
''' <param name="sender">Object that originated the event.</param>
''' <param name="e">Event data.</param>
''' <returns>
''' true if the listener handled the event. It is considered an error by the <see cref="T:System.Windows.WeakEventManager"/> handling in WPF to register a listener for an event that the listener does not handle. Regardless, the method should return false if it receives an event that it does not recognize or handle.
''' </returns>
Public Function ReceiveWeakEvent(ByVal managerType As Type, ByVal sender As Object, ByVal e As EventArgs) As Boolean Implements System.Windows.IWeakEventListener.ReceiveWeakEvent
HandleCollectionChanged(TryCast(sender, IList), TryCast(e, NotifyCollectionChangedEventArgs))
Return True
End Function
''' <summary>
''' Listens for change events on a list.
''' </summary>
''' <param name="list">The list to listen to.</param>
Protected Sub ListenForChangeEvents(ByVal list As IList)
If TypeOf list Is INotifyCollectionChanged Then
CollectionChangedEventManager.AddListener(TryCast(list, INotifyCollectionChanged), Me)
End If
End Sub
''' <summary>
''' Stops listening for change events.
''' </summary>
''' <param name="list">The list to stop listening to.</param>
Protected Sub StopListeningForChangeEvents(ByVal list As IList)
If TypeOf list Is INotifyCollectionChanged Then
CollectionChangedEventManager.RemoveListener(TryCast(list, INotifyCollectionChanged), Me)
End If
End Sub
Private Sub AddItems(ByVal list As IList, ByVal e As NotifyCollectionChangedEventArgs, ByVal converter As Converter(Of Object, Object))
Dim itemCount As Integer = e.NewItems.Count
For i As Integer = 0 To itemCount - 1
Dim insertionPoint As Integer = e.NewStartingIndex + i
If insertionPoint > list.Count Then
list.Add(converter(e.NewItems(i)))
Else
list.Insert(insertionPoint, converter(e.NewItems(i)))
End If
Next
End Sub
Private Function ConvertFromMasterToTarget(ByVal masterListItem As Object) As Object
Return If(_masterTargetConverter Is Nothing, masterListItem, _masterTargetConverter.Convert(masterListItem))
End Function
Private Function ConvertFromTargetToMaster(ByVal targetListItem As Object) As Object
Return If(_masterTargetConverter Is Nothing, targetListItem, _masterTargetConverter.ConvertBack(targetListItem))
End Function
Private Sub HandleCollectionChanged(ByVal sender As Object, ByVal e As NotifyCollectionChangedEventArgs)
Dim sourceList As IList = TryCast(sender, IList)
Select Case e.Action
Case NotifyCollectionChangedAction.Add
PerformActionOnAllLists(AddressOf AddItems, sourceList, e)
Exit Select
Case NotifyCollectionChangedAction.Move
PerformActionOnAllLists(AddressOf MoveItems, sourceList, e)
Exit Select
Case NotifyCollectionChangedAction.Remove
PerformActionOnAllLists(AddressOf RemoveItems, sourceList, e)
Exit Select
Case NotifyCollectionChangedAction.Replace
PerformActionOnAllLists(AddressOf ReplaceItems, sourceList, e)
Exit Select
Case NotifyCollectionChangedAction.Reset
UpdateListsFromSource(TryCast(sender, IList))
Exit Select
Case Else
Exit Select
End Select
End Sub
Private Sub MoveItems(ByVal list As IList, ByVal e As NotifyCollectionChangedEventArgs, ByVal converter As Converter(Of Object, Object))
RemoveItems(list, e, converter)
AddItems(list, e, converter)
End Sub
Private Sub PerformActionOnAllLists(ByVal action As ChangeListAction, ByVal sourceList As IList, ByVal collectionChangedArgs As NotifyCollectionChangedEventArgs)
If sourceList Is _masterList Then
PerformActionOnList(_targetList, action, collectionChangedArgs, AddressOf ConvertFromMasterToTarget)
Else
PerformActionOnList(_masterList, action, collectionChangedArgs, AddressOf ConvertFromTargetToMaster)
End If
End Sub
Private Sub PerformActionOnList(ByVal list As IList, ByVal action As ChangeListAction, ByVal collectionChangedArgs As NotifyCollectionChangedEventArgs, ByVal converter As Converter(Of Object, Object))
StopListeningForChangeEvents(list)
action(list, collectionChangedArgs, converter)
ListenForChangeEvents(list)
End Sub
Private Sub RemoveItems(ByVal list As IList, ByVal e As NotifyCollectionChangedEventArgs, ByVal converter As Converter(Of Object, Object))
Dim itemCount As Integer = e.OldItems.Count
' for the number of items being removed, remove the item from the Old Starting Index
' (this will cause following items to be shifted down to fill the hole).
For i As Integer = 0 To itemCount - 1
list.RemoveAt(e.OldStartingIndex)
Next
End Sub
Private Sub ReplaceItems(ByVal list As IList, ByVal e As NotifyCollectionChangedEventArgs, ByVal converter As Converter(Of Object, Object))
RemoveItems(list, e, converter)
AddItems(list, e, converter)
End Sub
Private Sub SetListValuesFromSource(ByVal sourceList As IList, ByVal targetList As IList, ByVal converter As Converter(Of Object, Object))
StopListeningForChangeEvents(targetList)
targetList.Clear()
For Each o As Object In sourceList
targetList.Add(converter(o))
Next
ListenForChangeEvents(targetList)
End Sub
Private Function TargetAndMasterCollectionsAreEqual() As Boolean
Return _masterList.Cast(Of Object)().SequenceEqual(_targetList.Cast(Of Object)().[Select](Function(item) ConvertFromTargetToMaster(item)))
End Function
''' <summary>
''' Makes sure that all synchronized lists have the same values as the source list.
''' </summary>
''' <param name="sourceList">The source list.</param>
Private Sub UpdateListsFromSource(ByVal sourceList As IList)
If sourceList Is _masterList Then
SetListValuesFromSource(_masterList, _targetList, AddressOf ConvertFromMasterToTarget)
Else
SetListValuesFromSource(_targetList, _masterList, AddressOf ConvertFromTargetToMaster)
End If
End Sub
''' <summary>
''' An implementation that does nothing in the conversions.
''' </summary>
Friend Class DoNothingListItemConverter
Implements IListItemConverter
''' <summary>
''' Converts the specified master list item.
''' </summary>
''' <param name="masterListItem">The master list item.</param>
''' <returns>The result of the conversion.</returns>
Public Function Convert(ByVal masterListItem As Object) As Object Implements IListItemConverter.Convert
Return masterListItem
End Function
''' <summary>
''' Converts the specified target list item.
''' </summary>
''' <param name="targetListItem">The target list item.</param>
''' <returns>The result of the conversion.</returns>
Public Function ConvertBack(ByVal targetListItem As Object) As Object Implements IListItemConverter.ConvertBack
Return targetListItem
End Function
End Class
End Class
Then for the XAML:然后对于 XAML:
<DataGrid ..... local:MultiSelectorBehaviours.SynchronizedSelectedItems="{Binding SelectedResults}" />
And finally the VM:最后是虚拟机:
Public ReadOnly Property SelectedResults As ObservableCollection(Of StatisticsResultModel)
Get
Return _objSelectedResults
End Get
End Property
Credit Goes to: http://blog.functionalfun.net/2009/02/how-to-databind-to-selecteditems.html信用转到: http ://blog.functionalfun.net/2009/02/how-to-databind-to-selecteditems.html
This issue has been labelled an "enhancement" in the WPF issues queue:此问题已在 WPF 问题队列中标记为“增强”:
https://github.com/dotnet/wpf/issues/3140 https://github.com/dotnet/wpf/issues/3140
Perhaps eventually a fix / update will be made and a solution incorporated out of the box.也许最终会进行修复/更新,并结合开箱即用的解决方案。
What about using a converter?使用转换器怎么样?
public class SelectedItemsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var dg = value as DataGrid;
return dg?.SelectedItems;
}
...
Use it in a DataGrid
's ContextMenu
like this:在
DataGrid
的ContextMenu
中使用它,如下所示:
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem
CommandParameter="{Binding Path=MySelectedItems, Converter={StaticResource selectedItemsConverter}, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
Command="{Binding MyDoSomethingWithMySelectedItemsCommand}"
Header="Do Something...">
</MenuItem>
The solution mentioned by devuxer devuxer提到的解决方案
<DataGrid.Resources>
<Style TargetType="DataGridRow">
<Setter Property="IsSelected"
Value="{Binding IsSelected}" />
</Style>
</DataGrid.Resources>
does not work when having large dataset.拥有大型数据集时不起作用。 You will need to disable row virtualization, and that is exactly the case where you need it...
您将需要禁用行虚拟化,而这正是您需要的情况......
EnableRowVirtualization="False" EnableRowVirtualization="假"
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.