I am busy creating a user control that has some basic charting/graph functions. In essence I want to have an "Items" dependency property to which the user of the control can bind. The control will then display all the items and updates made to the source.
What I have done so far was to create an "Items" DP in my user control, code behind.
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items",
typeof(ObservableCollection<Polyline>),
typeof(RPGraph),
new FrameworkPropertyMetadata(
new ObservableCollection<Polyline>(),
new PropertyChangedCallback(OnItemsChanged)));
public ObservableCollection<Polyline> Items
{
get { return (ObservableCollection<Polyline>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static void OnItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
My first stumbling block was that "OnItemsChanged" didn't get called when my collection changed. After a couple of hours I found a stackoverflow post explaining why (ObservableCollection dependency property does not update when item in collection is deleted). Following this advice solved one part of my problem. Now I could Add new items (Polylines) to the ObservableCollection list. But what if I added an extra point or modified a point in the Polyline. Armed with the knowledge of the previous problem I found the Points.Changed event. I then subscribed to it and placed the update code in there.
This finally works, but man there must be a better or more elegant way of achieving this (as stated at the top), which I think all boils down to not using ObservableCollection? Any advice?
Below is the working OnItemChanged method (excuse the draft code, I just wanted to get it working :-) :
public static void OnItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as RPGraph;
foreach (Polyline poly in thisControl.Items)
thisControl.manager.Items.Add(poly.Points.ToArray());
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged)e.OldValue;
// Unsubscribe from CollectionChanged on the old collection
coll.CollectionChanged -= Items_CollectionChanged;
}
if (e.NewValue != null)
{
var coll = (ObservableCollection<Polyline>)e.NewValue;
// Subscribe to CollectionChanged on the new collection
coll.CollectionChanged += (o, t) => {
ObservableCollection<Polyline> items = o as ObservableCollection<Polyline>;
thisControl.manager.Items.Add(items[t.NewStartingIndex].Points.ToArray());
foreach (Polyline poly in items)
{
poly.Points.Changed += (n, m) => {
for (int i = 0; i < thisControl.manager.Items.Count; i++)
thisControl.manager.Items[i] = thisControl.Items[i].Points.ToArray();
thisControl.manager.DrawGraph(thisControl.graphView);
};
}
thisControl.manager.DrawGraph(thisControl.graphView);
};
}
thisControl.manager.DrawGraph(thisControl.graphView);
}
You are completely right, an ObservableCollection does not notify when any of its items changes its property value.
You could extend the functionality of ObservableCollection adding notifications for these cases.
It may look like this:
public sealed class ObservableNotifiableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public event ItemPropertyChangedEventHandler ItemPropertyChanged;
public event EventHandler CollectionCleared;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
base.OnCollectionChanged(args);
if (args.NewItems != null)
{
foreach (INotifyPropertyChanged item in args.NewItems)
{
item.PropertyChanged += this.OnItemPropertyChanged;
}
}
if (args.OldItems != null)
{
foreach (INotifyPropertyChanged item in args.OldItems)
{
item.PropertyChanged -= this.OnItemPropertyChanged;
}
}
}
protected override void ClearItems()
{
foreach (INotifyPropertyChanged item in this.Items)
{
item.PropertyChanged -= this.OnItemPropertyChanged;
}
base.ClearItems();
this.OnCollectionCleared();
}
private void OnCollectionCleared()
{
EventHandler eventHandler = this.CollectionCleared;
if (eventHandler != null)
{
eventHandler(this, EventArgs.Empty);
}
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs args)
{
ItemPropertyChangedEventHandler eventHandler = this.ItemPropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new ItemPropertyChangedEventArgs(sender, args.PropertyName));
}
}
}
Then you can subscribe to the ItemPropertyChanged event and do your stuff.
Related
I have the following static ObservableCollection that gets updated using linq. Why the event is not firing ?
public static class myViewModel
{
private static ObservableCollection<ObjA> CollectionA = new ObservableCollection<ObjA>();
private static ObservableCollection<ObjB> CollectionB = new ObservableCollection<ObjB>();
static myViewModel()
{
CollectionA.CollectionChanged += new NotifyCollectionChangedEventHandler(myHandler);
CollectionA = new ObservableCollection(CollectionB.Select(abc=> new ObjA(abc, True));
}
private static void myHandler(object sender, NotifyCollectionChangedEventArgs e)
{
//To do
throw new NotImplementedException();
}
private static void updateCollection()
{
foreach (var x in CollectionA)
{
CollectionA.field=5;
}
}
}
Step one: Give CollectionA an event handler.
CollectionA.CollectionChanged += new NotifyCollectionChangedEventHandler(myHandler);
Step Two: Discard CollectionA and replace it with a different collection that has no handler.
CollectionA = new ObservableCollection(CollectionB.Select(abc=> new ObjA(abc, true));
See what you did there?
CollectionA returns a reference to a collection object. You aren't adding items to that collection object. You are replacing that collection object with a different collection object.
Add the items to the existing collection instead:
CollectionA.CollectionChanged += new NotifyCollectionChangedEventHandler(myHandler);
foreach (var x in CollectionB.Select(abc=> new ObjA(abc, true)))
{
CollectionA.Add(x);
}
If you really want to replace the collection all at once, you need to add the handler to the new collection:
CollectionA = new ObservableCollection(CollectionB.Select(abc=> new ObjA(abc, true));
CollectionA.CollectionChanged += myHandler;
If myHandler has the correct parameters and return type, you don't need new NotifyCollectionChangedEventHandler.
The usual way to handle this type of thing is to make CollectionA a property which adds the handler itself:
private static ObservableCollection<ObjA> _collectionA;
public static ObservableCollection<ObjA> CollectionA {
get { return _collectionA; }
set {
if (_collectionA != value)
{
// Remove handler from old collection, if any
if (_collectionA != null)
{
_collectionA.CollectionChanged -= myHandler;
}
_collectionA = value;
if (_collectionA != null)
{
_collectionA.CollectionChanged += myHandler;
// Whatever myHandler does on new items, you probably want to do
// that here for each item in the new collection.
}
}
}
}
static myViewModel()
{
// Now, whenever you replace CollectionA, the setter will add the changed
// handler automatically and you don't have to think about it.
CollectionA = new ObservableCollection(CollectionB.Select(abc=> new(abc, True));
}
Update
Now, what what are we doing with the items? Maybe we want to know when their properties change. ObservableCollection won't do that for us, but we can wire it up ourselves.
It's fun to think about ways to refactor this code in a more conveniently reusable way.
private static ObservableCollection<ObjA> _collectionA;
public static ObservableCollection<ObjA> CollectionA
{
get { return _collectionA; }
set
{
if (_collectionA != value)
{
// Remove handler from old collection, if any
if (_collectionA != null)
{
_collectionA.CollectionChanged -= myHandler;
}
// 1. Remove property changed handlers from old collection items (if old collection not null)
// 2. Add property changed to new collection items (if new collection not null)
AddAndRemovePropertyChangedHandlers(_collectionA, value, ObjA_PropertyChanged);
_collectionA = value;
if (_collectionA != null)
{
_collectionA.CollectionChanged += myHandler;
}
}
}
}
// NotifyCollectionChangedEventArgs gives us non-generic IList rather than IEnumerable
// but all we're doing is foreach, so make it as general as possible.
protected static void AddAndRemovePropertyChangedHandlers(
System.Collections.IEnumerable oldItems,
System.Collections.IEnumerable newItems,
PropertyChangedEventHandler handler)
{
if (oldItems != null)
{
// Some items may not implement INotifyPropertyChanged.
foreach (INotifyPropertyChanged oldItem in oldItems.Cast<Object>()
.Where(item => item is INotifyPropertyChanged))
{
oldItem.PropertyChanged -= handler;
}
}
if (newItems != null)
{
foreach (INotifyPropertyChanged newItem in newItems.Cast<Object>()
.Where(item => item is INotifyPropertyChanged))
{
newItem.PropertyChanged += handler;
}
}
}
private static void ObjA_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
private static void myHandler(object sender,
System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// If e.Action is Reset, you don't get the items that were removed. Oh well.
AddAndRemovePropertyChangedHandlers(e.OldItems, e.NewItems, ObjA_PropertyChanged);
}
I have myType1 with one dependency property string Text. I crated myType2 that contains dependency property ObservableCollection<myType1> Items. I also have graphical representation of Items. When i press button, it setsmyType1.Text to null. When Item.Text from Items is null I want to delete this item. I try to do this via `
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
ObservableCollection<StringDP> ocdp = e.NewValue as ObservableCollection<StringDP>;
foreach (var sdp in ocdp)
{
if (sdp == null)
{
ocdp.Remove(sdp);
}
}
dependencyObject.SetValue(e.Property, ocdp);
}
but it's not raises when Item.Text is setted to null. What am i doing wrong. Thank you!
Update
According to documentation ObservableCollection doesn't raise CollectionChanged event when item's property is changed. I solved my problem by inheritance from ObservableCollection.
`public class ElObservableCollection<T>: ObservableCollection<T> where T: INotifyPropertyChanged
{
public ElObservableCollection(): base()
{
CollectionChanged += OnCollectionChanged;
}
public virtual void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (var item in Items)
{
item.PropertyChanged += OnItemChanged;
}
}
if (e.OldItems != null)
{
foreach (var item in Items)
{
item.PropertyChanged -= OnItemChanged;
}
}
}
private void OnItemChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "TextProperty" && sender is StringDP)
{
StringDP sdp = sender as StringDP;
if (sdp.Text == null)
{
this.Remove((T) sender);
}
}
}
public ElObservableCollection(List<T> list)
: base(list)
{
CollectionChanged += OnCollectionChanged;
}
public ElObservableCollection(IEnumerable<T> collection)
: base(collection)
{
CollectionChanged += OnCollectionChanged;
}
}`
Create a property inside myType1 that references myType2, this way if the text property inside myType1 is set to null, it can remove itself.
According to documentation ObservableCollection doesn't raise CollectionChanged event when item's property is changed. I solved my problem by inheritance from ObservableCollection.
public class ElObservableCollection<T>: ObservableCollection<T> where T: INotifyPropertyChanged
{
public ElObservableCollection(): base()
{
CollectionChanged += OnCollectionChanged;
}
public virtual void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (var item in Items)
{
item.PropertyChanged += OnItemChanged;
}
}
if (e.OldItems != null)
{
foreach (var item in Items)
{
item.PropertyChanged -= OnItemChanged;
}
}
}
private void OnItemChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "TextProperty" && sender is StringDP)
{
StringDP sdp = sender as StringDP;
if (sdp.Text == null)
{
this.Remove((T) sender);
}
}
}
public ElObservableCollection(List<T> list)
: base(list)
{
CollectionChanged += OnCollectionChanged;
}
public ElObservableCollection(IEnumerable<T> collection)
: base(collection)
{
CollectionChanged += OnCollectionChanged;
}
}
In every constructor I subscribe on CollectionChanged event.
In CollectionChanged handler method i subscribe on PropertyChanged of each item.
In PropertyChanged handler method i write needed behavior.
Guess it will be helpful for somebody.
I want to select programatically multiple items in my ListBox. So, to be as mvvm friendly as possible, I create a custom control inherited from ListBox. In this custom control I've made a dependency property allowing items selection changes. Here is the code of the OnPropertyChanged part :
private static void OnSetSelectionToPropertyChanged
(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
InitializableListBox list = d as InitializableListBox;
Dictionary<int, string> toSelect = e.NewValue as Dictionary<int, string>;
if (toSelect == null)
return;
list.SetSelectedItems(toSelect);
}
The selection works great, but this solution does not raise the OnSelectionChanged event
So I try also :
private static void OnSetSelectionToPropertyChanged
(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
InitializableListBox list = d as InitializableListBox;
Dictionary<int, string> toSelect = e.NewValue as Dictionary<int, string>;
if (toSelect == null)
return;
SelectionChangedEventArgs e_selChanged;
List<Object> removed = new List<object>();
List<Object> added = new List<object>();
//Clear the SelectedItems list
while(list.SelectedItems.Count > 0)
{
removed.Add(list.SelectedItems[0]);
list.SelectedItems.RemoveAt(0);
}
//Add each selected items
foreach (var item in toSelect)
{
list.SelectedItems.Add(item);
added.Add(list.SelectedItems[list.SelectedItems.Count - 1]);
}
//Raise the SelectionChanged event
e_selChanged = new SelectionChangedEventArgs(SelectionChangedEvent,removed,added);
list.OnSelectionChanged(e_selChanged);
}
But this was not better. I think I'm not dealing the right way with the event so if you could help me, that would be great.
Thanks in advance.
EDIT
I have found another solution (more a hack actually) than #NETscape. I don't think it's better, but it seems to work pretty well, and it's maybe easier.
The trick is to made a dependency property wich allow you to access the SelectedItems property (wich is readonly and unbindable on the normal ListBox). Here is the code of my custom ListBox :
public class InitializableListBox : ListBox
{
public InitializableListBox()
{
SelectionChanged += CustomSelectionChanged;
}
private void CustomSelectionChanged(object sender, SelectionChangedEventArgs e)
{
InitializableListBox s = sender as InitializableListBox;
if (s == null)
return;
s.CustomSelectedItems = s.SelectedItems;
}
#region CustomSelectedItems DependyProperty
public static DependencyProperty CustomSelectedItemsProperty =
DependencyProperty.RegisterAttached("CustomSelectedItems",
typeof(System.Collections.IList),typeof(InitializableListBox),
new PropertyMetadata(null, OnCustomSelectedItemsPropertyChanged));
public System.Collections.IList CustomSelectedItems
{
get
{
return (System.Collections.IList)GetValue(CustomSelectedItemsProperty);
}
set
{
SetValue(CustomSelectedItemsProperty, value);
}
}
private static void OnCustomSelectedItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
InitializableListBox list = d as InitializableListBox;
System.Collections.IList toSelect = e.NewValue as System.Collections.IList;
if (toSelect == null)
return;
list.SetSelectedItems(toSelect);
}
#endregion
}
See this
If you set your ItemsSource to a collection, and the objects in the collection implement INPC, you can set the Style on ListViewItem to use the bound objects IsSelected property.
See this answer to understand what I mean.
Let's say you have ItemsSource="{Binding Items}", then you can do something like:
Items.Where(item => item.IsSelected == true);
to return your list of items that are selected.
You could also do Items[0].IsSelected = true; to programmatically select an item.
In short, you shouldn't have to use a custom control to implement multiple selection on a ListView.
EDIT
I experienced the virtualization problem when I was implementing Telerik's RadGridView. I used a behavior to counter this problem and it seems to have worked:
public class RadGridViewExt : Behavior<RadGridView>
{
private RadGridView Grid
{
get
{
return AssociatedObject as RadGridView;
}
}
public INotifyCollectionChanged SelectedItems
{
get { return (INotifyCollectionChanged)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(INotifyCollectionChanged), typeof(RadGridViewExt), new PropertyMetadata(OnSelectedItemsPropertyChanged));
private static void OnSelectedItemsPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
{
var collection = args.NewValue as INotifyCollectionChanged;
if (collection != null)
{
collection.CollectionChanged += ((RadGridViewExt)target).ContextSelectedItemsCollectionChanged;
}
}
protected override void OnAttached()
{
base.OnAttached();
Grid.SelectedItems.CollectionChanged += GridSelectedItemsCollectionChanged;
}
void ContextSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UnsubscribeFromEvents();
Transfer(SelectedItems as IList, AssociatedObject.SelectedItems);
SubscribeToEvents();
}
void GridSelectedItemsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
UnsubscribeFromEvents();
Transfer(AssociatedObject.SelectedItems, SelectedItems as IList);
SubscribeToEvents();
}
private void SubscribeToEvents()
{
AssociatedObject.SelectedItems.CollectionChanged += GridSelectedItemsCollectionChanged;
if (SelectedItems != null)
{
SelectedItems.CollectionChanged += ContextSelectedItemsCollectionChanged;
}
}
private void UnsubscribeFromEvents()
{
AssociatedObject.SelectedItems.CollectionChanged -= GridSelectedItemsCollectionChanged;
if (SelectedItems != null)
{
SelectedItems.CollectionChanged -= ContextSelectedItemsCollectionChanged;
}
}
public static void Transfer(IList source, IList target)
{
if (source == null || target == null)
return;
target.Clear();
foreach (var o in source)
{
target.Add(o);
}
}
}
Inside RadGridView control:
<i:Interaction.Behaviors>
<local:RadGridViewExt SelectedItems="{Binding SelectedItems}" />
</i:Interaction.Behaviors>
where
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
and remember to add reference to Interactivity assembly.
ListBox has a property called SelectionMode. You can set it to Multiple to enable multiple selection.
<ListBox x:Name="MyListBox" SelectionMode="Multiple"/>
You can use SelectedItems property to get all the selected items afterwards.
I'm using a TrulyObservableCollection as a datasource in a WPF DataGrid. My class implements the PropertyChange event properly (I get notification when a property changes). The CollectionChanged event gets triggered as well. However, my issue lies in the connection between the PropertyChanged event and CollectionChanged event. I can see in the PropertyChanged event which item is being changed (in this case the sender object), however I can't seem to find a way to see which one is changed from within the CollectionChanged event. The sender object is the whole collection. What's the best way to see which item has changed in the CollectionChanged event? The relevant code snippets are below. Thank you for your help, and let me know if there needs to be some clarification.
Code for setting up the collection:
private void populateBret()
{
bretList = new TrulyObservableCollection<BestServiceLibrary.bretItem>(BestClass.BestService.getBretList().ToList());
bretList.CollectionChanged += bretList_CollectionChanged;
dgBretList.ItemsSource = bretList;
dgBretList.Items.Refresh();
}
void bretList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//Do stuff here with the specific item that has changed
}
Class that is used in the collection:
public class bretItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _blID;
public string _blGroup;
[DataMember]
public int blID
{
get { return _blID; }
set
{
_blID = value;
OnPropertyChanged("blID");
}
}
[DataMember]
public string blGroup
{
get { return _blGroup; }
set
{
_blGroup = value;
OnPropertyChanged("blGroup");
}
}
protected void OnPropertyChanged (String name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
TrulyObservableCollection class
public class TrulyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public TrulyObservableCollection()
: base()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged);
}
public TrulyObservableCollection(List<T> list)
: base(list)
{
foreach (var item in list)
{
item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged);
}
void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
(item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(a);
}
}
EDIT:
In the item_PropertyChanged event the NotifyCollectionChangedEventArgs are set with NotifyCollectionChangedAction.Reset. This causes the OldItems and NewItems to be null, therefore I can't get the changed item in that case. I can't use .Add as the Datagrid is updated with an additional item. I can't appear to get .Replace to work either to get the changed item.
How about this:
In your ViewModel that contains the ObservableCollection of bretItem, the ViewModel subscribes to the CollectionChanged event of the ObservableCollection.
This will prevent the need of a new class TrulyObservableCollection derived from ObservableCollection that is coupled to the items within its collection.
Within the handler in your ViewModel, you can add and remove the PropertyChanged event handler as you are now. Since it is now your ViewModel that is being informed of the changes to objects within the collection, you can take the appropriate action.
public class BretListViewModel
{
private void populateBret()
{
bretList = new ObservableCollection<BestServiceLibrary.bretItem>(BestClass.BestService.getBretList().ToList());
bretList.CollectionChanged += bretList_CollectionChanged;
}
void bretList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
(item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var bret = sender as bretItem;
//Update the database now!
//One note:
//The ObservableCollection raises its change event as each item changes.
//You should consider a method of batching the changes (probably using an ICommand)
}
}
A Thing of Note:
As an aside, it looks like you are breaking the MVVM pattern based upon this snippet:
dgBretList.ItemsSource = bretList;
dgBretList.Items.Refresh();
You probably should consider loading your ViewModel and binding your View to it instead of coding logic in the code-behind of your View.
It's not appropriate to use the collection changed event in this way because it's only meant to be fired when adding/removing items from the collection. Which is why you've hit a wall. You're also in danger of breaking the Liskov substitution principle with this approach.
It's probably better to implement the INotifyPropertyChanged interface on your collection class and fire that event when one of your items fires its property changed event.
I developed my UI on MVVM pattern and now stuck on getting SelectedItems. Could you please modify my XAML and provide sample how do I get them insde ViewModel class.
<xcdg:DataGridControl Name="ResultGrid" ItemsSource="{Binding Results}" Height="295" HorizontalAlignment="Left" Margin="6,25,0,0" VerticalAlignment="Top" Width="1041" ReadOnly="True">
<xcdg:DataGridControl.View>
<xcdg:TableflowView UseDefaultHeadersFooters="False">
<xcdg:TableflowView.FixedHeaders>
<DataTemplate>
<xcdg:ColumnManagerRow />
</DataTemplate>
</xcdg:TableflowView.FixedHeaders>
</xcdg:TableflowView>
</xcdg:DataGridControl.View>
</xcdg:DataGridControl>
You can use Attached behaviors to get/set SelectedItems to datagrid.
I was facing similar issue in Metro apps, So had to write it myself.
Below is the link
http://www.codeproject.com/Articles/412417/Managing-Multiple-selection-in-View-Model-NET-Metr
Though i had written for metro apps, the same solution can be adapted in WPF/Silverlight.
public class MultiSelectBehavior : Behavior<ListViewBase>
{
#region SelectedItems Attached Property
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
"SelectedItems",
typeof(ObservableCollection<object>),
typeof(MultiSelectBehavior),
new PropertyMetadata(new ObservableCollection<object>(), PropertyChangedCallback));
#endregion
#region private
private bool _selectionChangedInProgress; // Flag to avoid infinite loop if same viewmodel is shared by multiple controls
#endregion
public MultiSelectBehavior()
{
SelectedItems = new ObservableCollection<object>();
}
public ObservableCollection<object> SelectedItems
{
get { return (ObservableCollection<object>)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += OnSelectionChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.SelectionChanged -= OnSelectionChanged;
}
private static void PropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
NotifyCollectionChangedEventHandler handler = (s, e) => SelectedItemsChanged(sender, e);
if (args.OldValue is ObservableCollection<object>)
{
(args.OldValue as ObservableCollection<object>).CollectionChanged -= handler;
}
if (args.NewValue is ObservableCollection<object>)
{
(args.NewValue as ObservableCollection<object>).CollectionChanged += handler;
}
}
private static void SelectedItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (sender is MultiSelectBehavior)
{
var listViewBase = (sender as MultiSelectBehavior).AssociatedObject;
var listSelectedItems = listViewBase.SelectedItems;
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
if (listSelectedItems.Contains(item))
{
listSelectedItems.Remove(item);
}
}
}
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
if (!listSelectedItems.Contains(item))
{
listSelectedItems.Add(item);
}
}
}
}
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_selectionChangedInProgress) return;
_selectionChangedInProgress = true;
foreach (var item in e.RemovedItems)
{
if (SelectedItems.Contains(item))
{
SelectedItems.Remove(item);
}
}
foreach (var item in e.AddedItems)
{
if (!SelectedItems.Contains(item))
{
SelectedItems.Add(item);
}
}
_selectionChangedInProgress = false;
}
}
There is probably more to do if you want a multiselection and you want to get those selected items. Do you want to store the selected items and when some action is performed (button clicked or something like that) you want to use those selectedItems and do something with them?
There is a good example on that available here:
Get SelectedItems From DataGrid Using MVVM
It states it is designed for Silverlight, but it will work in WPF with MVVM too.
Perhaps this is a more straightforward approach:
Get Selected items in a WPF datagrid
Creating an attached behavior for wiring up every Read Only Collection or non Dependency property would take a significant amount of work. A simple solution is to pass the reference to the view model using the view.
Private ReadOnly Property ViewModel As MyViewModel
Get
Return DirectCast(DataContext, MyViewModel)
End Get
End Property
Private Sub MyView_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
If ViewModel.SelectedItems Is Nothing Then
ViewModel.SelectedItems = MyDataGrid.SelectedItems
End If
End Sub