2012-03-26 4 views
11

СМОТРИТЕ МОЙ ОТВЕТ НА НИЖНЕЙПривязать к SelectedItems из DataGrid или ListBox в MVVM

Просто делают некоторые легкое чтение на WPF, где мне нужно, чтобы связать SelectedItems из DataGrid, но я не могу придумать что-нибудь ощутимый. Мне просто нужны выбранные объекты.

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"> 

ответ

3

Это будет работать:

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

''' <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.В.Б

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 

Тогда для XAML:

<DataGrid ..... local:MultiSelectorBehaviours.SynchronizedSelectedItems="{Binding SelectedResults}" /> 

И, наконец, VM:

Public ReadOnly Property SelectedResults As ObservableCollection(Of StatisticsResultModel) 
    Get 
     Return _objSelectedResults 
    End Get 
End Property 

Заслуга: http://blog.functionalfun.net/2009/02/how-to-databind-to-selecteditems.html

22

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

Один довольно удобный для работы MVVM способ привязки к свойству IsSelectedDataGridRow.

Вы можете настроить связывание следующим образом:

<DataGrid ItemsSource="{Binding DocumentViewModels}" 
      SelectionMode="Extended"> 
    <DataGrid.Resources> 
     <Style TargetType="DataGridRow"> 
      <Setter Property="IsSelected" 
        Value="{Binding IsSelected}" /> 
     </Style> 
    </DataGrid.Resources> 
</DataGrid> 

Затем вам нужно создать DocumentViewModel, который наследуется от ViewModelBase (или независимо MVVM базового класса вы используете) и имеют свойство вашего Document вы хотите для представления в DataGrid, а также для свойства IsSelected.

Затем в модели вашего основного вида вы создаете List(Of DocumentViewModel) под названием DocumentViewModels, чтобы связать ваш DataGrid. (Примечание: если вы будете добавлять/удалять элементы из списка, используйте вместо этого ObservableCollection(T).)

Теперь вот сложная часть. Вам нужно подключить в PropertyChanged случае каждого DocumentViewModel в списке, как это:

For Each documentViewModel As DocumentViewModel In DocumentViewModels 
    documentViewModel.PropertyChanged += DocumentViewModel_PropertyChanged 
Next 

Это позволяет реагировать на изменения в любой DocumentViewModel.

Наконец, в DocumentViewModel_PropertyChanged вы можете просмотреть свой список (или использовать запрос Linq), чтобы получить информацию по каждому элементу, где IsSelected = True.

+0

Первое, что я попробовал, но получил это на компиляции: свойство «SelectedItems» читается - и не может быть установлен из разметки. –

+0

Ahh, я вижу проблему. Да, ты прав. 'SelectedItems' - свойство только для чтения. Вам нужно фактически изменить выбранные предметы или просто узнать, что они собой представляют? – devuxer

+0

На самом деле просто вычислил это с помощью CommandParameter. Я могу передать файлы из datagrid. Но да, мне просто нужно было знать, что они собой представляют. –

5

I знаю, что этот пост немного стар и получил ответ. Но я придумал ответ не MVVM. Его легко и работает для меня. Добавьте еще DataGrid, предполагается, что ваш подобранная коллекция является SelectedResults:

<DataGrid x:Name="SelectedGridRows" 
     ItemsSource="{Binding SelectedResults,Mode=OneWayToSource}" 
     Visibility="Collapsed" > 

И в код, добавьте это в конструкторе:

public ClassConstructor() 
    { 
     InitializeComponent(); 
     OriginalGrid.SelectionChanged -= OriginalGrid_SelectionChanged; 
     OriginalGrid.SelectionChanged += OriginalGrid_SelectionChanged; 
    } 
    private void OriginalGrid_SelectionChanged(object sender, 
     SelectionChangedEventArgs e) 
    { 
     SelectedGridRows.ItemsSource = OriginalGrid.SelectedItems; 
    } 
9

С немного хитростью вы можете продлить DataGrid создать Bindable версия SelectedItems. Мое решение требует, чтобы привязка имела Mode=OneWayToSource, так как я только хочу читать из свойства в любом случае, но может можно расширить мое решение, чтобы позволить устройству читать-писать.

Я предполагаю, что подобная техника может использоваться для 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); 
    } 
} 
+1

Я установил привязку к SelectedItems = "{Binding SelectedSamples, Mode = OneWayToSource}". Но в настройщике для моего свойства ViewModel значение SelectedSamples всегда равно null, хотя свойство SelectedItems имеет один или несколько элементов. Что я делаю не так? – blueshift

+1

@blueshift такой же выпуск здесь. Вам удалось заставить его работать? – Vereos

+1

Нет, я не смог заставить его работать. SelectedItems всегда равно null. – blueshift

3

У меня было решение проблемы с использованием обходного пути, которое соответствовало моим потребностям.

сделать событие для команды на listitemtemplate на MouseUp и как CommandParameter отправить коллекцию SelectedItems

<i:Interaction.Triggers> 
         <i:EventTrigger EventName="MouseUp"> 
          <helpers:EventToCommand Command="{Binding DataContext.SelectionChangedUpdate, 
                     RelativeSource={RelativeSource AncestorType=UserControl}}" 
                CommandParameter="{Binding ElementName=presonsList, Path=SelectedItems}" /> 
         </i:EventTrigger>       
        </i:Interaction.Triggers> 

Таким образом, вы можете иметь команду в ViewModel, который обрабатывает это или сохраняет выбранные элементы для последующего использования , Весело кодирования

21

Я могу заверить вас: SelectedItems действительно Привязываемое как XAML CommandParameter

После много рытье и прибегая к помощи, я наконец-то нашел простое решение этой общей проблемы.

Чтобы сделать его работу вы должны следовать ВСЕМ следующим правилам:

  1. После Ed Ball's suggestion», на вас XAML командах привязок данных, определить CommandParameter свойства ДО Командования недвижимости. Это очень трудоемкая ошибка.

    enter image description here

  2. Убедитесь, что ICommand «s CanExecute и Execute методы имеют параметр объекта типа. Таким образом вы можете предотвратить отключен исключений исключения, возникающих при привязке данных Тип CommandParameter не соответствует типу параметра вашего метода метода.

    частный BOOL OnDeleteSelectedItemsCanExecute (объект SelectedItems)
    {

    // Your goes heres 
    

    }

    частный BOOL OnDeleteSelectedItemsExecute (объект SelectedItems)
    {

    // Your goes heres 
    

    }

Например, вы можете отправить ListView/ListBox в SelectedItems недвижимость вам ICommand методы или ListView/ListBox это сам. Отлично, не так ли?

Надеется, что это мешает кому-то тратить огромное количество времени, я сделал, чтобы выяснить, как получить SelectedItems в CanExecute параметра.

+0

У меня есть «DataGrid», где, если пользователь обращается к '' Command''у ContextMenu '' 'MenuItem'' через' KeyBinding' InputBinding', чей 'CommandParameter = '{Binding ElementName = MyDataGrid , Path = SelectedItems} "', он передаст 'SelectedItems' в« ICommand ». Однако, «null» передается, если он доступен через «ContextMenu». Я пробовал 'CommandParameter =' '" {Binding SelectedItems} "', '" {Binding ElementName = MyDataGrid, Path = SelectedItems} "и' "{Binding RelativeSource = {RelativeSource Self}, Path = SelectedItems}" '. Да, я устанавливаю 'CommandParameter' перед' Command'. – Tom

+0

Также попробовал '' {Binding RelativeSource = {RelativeSource FindAncestor, AncestorType = {x: Тип DataGrid}}, Path = SelectedItems} "'. FYI, 'ContextMenu' определяется внутри строки через' ', а затем' ContextMenu'. – Tom

+3

Это работает, но стоит добавить, как бороться с объектом, который вы получаете. if (parameter! = null) { System.Collections.IList items = (System.) Параметр Collections.IList; var selection = items? .Cast (); } – cjmurph

4

Я пришел сюда для ответа, и у меня было много отличных. Я объединил их всех в пристроенное свойство, очень похожее на то, что было предложено Омаром выше, но в одном классе. Ручки INotifyCollectionChanged и переключение списка. Не происходит утечки событий. Я написал его, чтобы это было довольно простым кодом. Написанный в C#, обрабатывает список выбранных элементов и данных.

Это работает как с 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); } 

    /// <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; 
     } 
    } 
} 
+0

У меня есть вопрос об этом. Я реализовал его, но поскольку это DependencyProperty, как мне получить его в моей модели просмотра? В предоставленном коде это просто код. Но у меня уже есть доступ к SelectedItems в коде позади. – TheFaithfulLearner

-3

Раствор упоминается devuxer

<DataGrid.Resources> 
     <Style TargetType="DataGridRow"> 
      <Setter Property="IsSelected" 
        Value="{Binding IsSelected}" /> 
     </Style> 
    </DataGrid.Resources> 

не работает при наличии большого набора данных. Вам нужно будет отключить виртуализацию строк, и это именно тот случай, когда вам это нужно ...

EnableRowVirtualization = «False»

Смежные вопросы