Answered by:
Sorting of ListView in WPF MVVM

Question
-
Hi,
I have written generic listview sorting logic in ViewModel using Dependency property. Refer link http://www.thomaslevesque.com/2009/03/27/wpf-automatically-sort-a-gridview-when-a-column-header-is-clicked/ I have written collectionChanged event on listView.Items and able to sort the list when collection changed but it doesn't called when collection updated.
Now for this I have written propertyChanged event on item. But here now problem is on propertyChanged event I am not able get the listView or ItemCollection. So not able to sort the listView on PropertyChanged. I am not able to access listView from ViewModel. So how can I achieve this?
Please help me as I am stuck here since last 2 days.. Your help is extremely valuable...
So as per my below class structure when there is a property changed, I can only access Class1 and not the view or ViewCollection. Please help...
Class1 : INotifyPropertyChanged
{
Properties here with property changed event raised...
}
Class2 : ObservableCollection<Class1>
{
<<Create instance of Class1 >>
Adding instance in collection... and get sorted as collectionChanged fire here..
<<After some logic , some of the property changed here so need to resort listView>>
}
<<Class2 is binded to ListView ItemSource>>
Thanks,
Ayaz Shaikh
Friday, October 31, 2014 12:43 PM
Answers
-
If you want to sort the ObservableCollection, or perform any other action, when a property of any of the items in gets changed, you could handle the CollectionChanged event of the ObservableCollection and hook up an event handler to all Class1 objects that gets added to it:
public class Class2 : ObservableCollection<Class1> { public Class2() : base() { this.CollectionChanged += OnCollectionChanged; } public Class2(IEnumerable<Class1> col) : base(col) { this.CollectionChanged += OnCollectionChanged; } public Class2(List<Class1> col) : base(col) { this.CollectionChanged += OnCollectionChanged; } private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) { foreach (object c1 in e.NewItems) { (c1 as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged); } } if (e.OldItems != null) { foreach (object c1 in e.OldItems) { (c1 as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged); } } } private void item_PropertyChanged(object sender, PropertyChangedEventArgs e) { //do the sorting in here.... } }
In the above sample class, the item_PropertyChanged event handler in the ObservableCollection class (Class2) will get invoked whenever a property value is changed in any of its Class1 and the object raises the PropertyChanged event.
How to sort the ObservableCollection is a different question. Please refer to the following threads for more information:
http://stackoverflow.com/questions/19363710/sorting-observablecollection
http://stackoverflow.com/questions/1945461/how-do-i-sort-an-observable-collectionPlease remember to mark helpful posts as answer and/or helpful and please don't post several questions in the same thread. Remember that a new question deserves a new thread.
- Marked as answer by MohammedAyaz Shaikh Wednesday, November 5, 2014 5:46 AM
Friday, October 31, 2014 1:45 PM -
Sorry, you make no sense at all.
>>So now in this case how will I get Class2 details in item_PropertyChanged event?
For the third time: Since item_PropertyChanged is defined in Class2 you can get as many details of this class as you want...
Any you didn't mention anything about some CommonUtilityClass class in your original question.
If your sorting logic is implemented in another class CommonUtilityClass, you could create an instance of this one, call a method on it or do whatever in the item_PropertyChanged event handler:
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e) { CommonUtilityClass sorter = new CommonUtilityClass(); sorter.Sort(this); //pass Class2 instance to some method of CommonUtilityClass or do whatever here.... }
You could for example pass the current instance of Class2 (this) to a method or its constructor when you create it.
Please remember to mark helpful posts as answer and/or helpful.
- Marked as answer by MohammedAyaz Shaikh Wednesday, November 5, 2014 5:46 AM
Friday, October 31, 2014 5:28 PM
All replies
-
If you want to sort the ObservableCollection, or perform any other action, when a property of any of the items in gets changed, you could handle the CollectionChanged event of the ObservableCollection and hook up an event handler to all Class1 objects that gets added to it:
public class Class2 : ObservableCollection<Class1> { public Class2() : base() { this.CollectionChanged += OnCollectionChanged; } public Class2(IEnumerable<Class1> col) : base(col) { this.CollectionChanged += OnCollectionChanged; } public Class2(List<Class1> col) : base(col) { this.CollectionChanged += OnCollectionChanged; } private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) { foreach (object c1 in e.NewItems) { (c1 as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged); } } if (e.OldItems != null) { foreach (object c1 in e.OldItems) { (c1 as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged); } } } private void item_PropertyChanged(object sender, PropertyChangedEventArgs e) { //do the sorting in here.... } }
In the above sample class, the item_PropertyChanged event handler in the ObservableCollection class (Class2) will get invoked whenever a property value is changed in any of its Class1 and the object raises the PropertyChanged event.
How to sort the ObservableCollection is a different question. Please refer to the following threads for more information:
http://stackoverflow.com/questions/19363710/sorting-observablecollection
http://stackoverflow.com/questions/1945461/how-do-i-sort-an-observable-collectionPlease remember to mark helpful posts as answer and/or helpful and please don't post several questions in the same thread. Remember that a new question deserves a new thread.
- Marked as answer by MohammedAyaz Shaikh Wednesday, November 5, 2014 5:46 AM
Friday, October 31, 2014 1:45 PM -
Hi,
Try the below link. You need to use ICollectionView to sort.
http://www.abhisheksur.com/2010/08/woring-with-icollectionviewsource-in.html
http://www.wpf-tutorial.com/listview-control/listview-sorting/
- Proposed as answer by noorbakhsh Friday, October 31, 2014 5:50 PM
Friday, October 31, 2014 1:48 PM -
Hi Magnus,
Thanks for the reply. I have used the same logic which you have mentioned in your code.
But here problem is I am not getting ItemCollection in item_PropertyChanged event so not able to sort the collection. I am only able to access the details of Class1 but not the Class2.
So here question is how can I get the Class2 ItemCollection in item_PropertyChanged event? Here I am stuck...
Thanks,
Ayaz Shaikh
Friday, October 31, 2014 2:06 PM -
If you bind to a ListCollectionView rather than directly to the ObservableCollection then you can use sortdescriptors on the ListCollectionView.
For example.
PeopleView.SortDescriptions.Clear(); PeopleView.SortDescriptions.Add(new SortDescription("OrganizationLevel", ListSortDirection.Ascending)); );
Then use the .Refresh() method on that ListCollectionView.
You can detect additions to the collection by handling the CollectionChanged event of the ObservableCollection:
void People_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { // Do your stuff }
You might find my article on CollectionView tips useful:
http://social.technet.microsoft.com/wiki/contents/articles/26673.wpf-collectionview-tips.aspx
Friday, October 31, 2014 2:19 PM -
>>So here question is how can I get the Class2 ItemCollection in item_PropertyChanged event? Here I am stuck...
Please read my reply again. The item_PropertyChanged event handler in Class2 will be invoked whenever the PropertyChanged event is raised in any Class1 object that has been added to the Class2 collection and you can certainly access any properties and methods of Class2 in the item_PropertyChanged handler:
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e) { //this method in Class2 will be invoked when any Class1 object raises the PropertyChanged event...you can access any member of Class2 in here... }
Of course you can't access or do something with the Class2 collection in the setter of a Class1 property.
Friday, October 31, 2014 3:57 PM -
Hi Magnus,
Thanks for explanation. I understand item_PropertyChanged will be invoked when any Class1 object raises PropertyChanged event.
But as per your code item_PropertyChanged is in Class2 and in my case I have written separate CommonUtilityClass in which I have written all the code related to Sorting. So in that I won't be able to retrieve details of Class2 from the sender object of item_PropertyChanged event as it will have Class1 details.
So in my case, Class1 having properties, Class2 having ObservationCollection of Class1 and separate CommonUtilityClass having sorting logic.
So now in this case how will I get Class2 details in item_PropertyChanged event? Hope you are getting me what I have explained...
Thanks,
Ayaz Shaikh
Friday, October 31, 2014 5:13 PM -
Sorry, you make no sense at all.
>>So now in this case how will I get Class2 details in item_PropertyChanged event?
For the third time: Since item_PropertyChanged is defined in Class2 you can get as many details of this class as you want...
Any you didn't mention anything about some CommonUtilityClass class in your original question.
If your sorting logic is implemented in another class CommonUtilityClass, you could create an instance of this one, call a method on it or do whatever in the item_PropertyChanged event handler:
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e) { CommonUtilityClass sorter = new CommonUtilityClass(); sorter.Sort(this); //pass Class2 instance to some method of CommonUtilityClass or do whatever here.... }
You could for example pass the current instance of Class2 (this) to a method or its constructor when you create it.
Please remember to mark helpful posts as answer and/or helpful.
- Marked as answer by MohammedAyaz Shaikh Wednesday, November 5, 2014 5:46 AM
Friday, October 31, 2014 5:28 PM -
Hi Magnus,
The solution you have given works perfectly fine, if collectionChanged and propertyChange events are in Class2.
Let me put my code here. Now if you refer below code, CollectionChanged and PropertyChanged both events are in CommonUtility.cs file. I bind CollectionChanged event in AutoSort dependency property. Whatever ListView will have AutoSort property, it's collectionChanged event will be bind here and so PropertyChanged as well.
So here in below code snippet, I won't have access to Class2 in propertyChanged event.
So is there anyway to get Class2 here in propertyChanged event?
=======================================================================
Myview.xaml
<ListView x:Uid="ListView1" x:Name="ListView1" ItemsSource="{Binding ListView1Data}" SelectionMode="Single" cm:GridViewSort.AutoSort="True">
<ListView.View>
<GridView x:Uid="GridView_2">
<GridViewColumn x:Uid="GridViewColumn_12" Header="Id" DisplayMemberBinding="{Binding Col1Data, Mode=OneWay}" Width="100" cm:GridViewSort.PropertyName="Col1Data"/>
.....
</GridView>
</ListView.View>
</ListView>================================================================
CommonUtility.cs file
public class GridViewSort
{static GridViewColumnHeader lastHeaderClicked;
#region Attached propertiespublic static bool GetAutoSort(DependencyObject obj)
{
return (bool)obj.GetValue(AutoSortProperty);
}public static void SetAutoSort(DependencyObject obj, bool value)
{
obj.SetValue(AutoSortProperty, value);
}// Using a DependencyProperty as the backing store for AutoSort. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AutoSortProperty =
DependencyProperty.RegisterAttached(
"AutoSort",
typeof(bool),
typeof(GridViewSort),
new UIPropertyMetadata(
false,
(o, e) =>
{
ListView listView = o as ListView;
if (listView != null)
{
if (GetCommand(listView) == null) // Don't change click handler if a command is set
{
bool oldValue = (bool)e.OldValue;
bool newValue = (bool)e.NewValue;
if (oldValue && !newValue)
{
listView.RemoveHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
}
if (!oldValue && newValue)
{
listView.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
}
}
}
((INotifyCollectionChanged)listView.Items).CollectionChanged += new NotifyCollectionChangedEventHandler(GridViewSort_CollectionChanged);}
)
);public static string GetPropertyName(DependencyObject obj)
{
return (string)obj.GetValue(PropertyNameProperty);
}public static void SetPropertyName(DependencyObject obj, string value)
{
obj.SetValue(PropertyNameProperty, value);
}// Using a DependencyProperty as the backing store for PropertyName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PropertyNameProperty =
DependencyProperty.RegisterAttached(
"PropertyName",
typeof(string),
typeof(GridViewSort),
new UIPropertyMetadata(null)
);
static void GridViewSort_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{if (e.Action == NotifyCollectionChangedAction.Add)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(GridViewSort_PropertyChanged);
}
}
SortOnCollectionUpdate(listView);
}
}static void GridViewSort_PropertyChanged(object sender, PropertyChangedEventArgs e)
{<<How can I get listView here ??>>
This is a generic propertyChanged event for collection of all the listview having sorting feature...}
#endregion#region Column header click event handler
private static void ColumnHeader_Click(object sender, RoutedEventArgs e)
{
GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
if (headerClicked != null && headerClicked.Column != null)
{
string propertyName = GetPropertyName(headerClicked.Column);
if (!string.IsNullOrEmpty(propertyName))
{
ListView listView = VisualTreeUtilities.GetAncestor<ListView>(headerClicked);
if (listView != null)
{
ICommand command = GetCommand(listView);
if (command != null)
{
if (command.CanExecute(propertyName))
{
command.Execute(propertyName);
}
}
else if (GetAutoSort(listView))
{
ApplySort(listView.Items, propertyName, headerClicked);
lastHeaderClicked = headerClicked;
}
}
}
}
}#endregion
#region Helper methods
public static void ApplySort(ICollectionView view, string propertyName, GridViewColumnHeader headerClicked)
{
ListSortDirection direction = ListSortDirection.Ascending;
if (view.SortDescriptions.Count > 0)
{
SortDescription currentSort = view.SortDescriptions[0];
if (lastHeaderClicked != headerClicked)
{
lastHeaderClicked.Column.HeaderTemplate = null;
}if (currentSort.PropertyName == propertyName)
{
if (currentSort.Direction == ListSortDirection.Ascending)
{
direction = ListSortDirection.Descending;
headerClicked.Column.HeaderTemplate =
Application.Current.Resources["HeaderTemplateArrowDown"] as DataTemplate;
}
else
{
direction = ListSortDirection.Ascending;
headerClicked.Column.HeaderTemplate =
Application.Current.Resources["HeaderTemplateArrowUp"] as DataTemplate;
}
}
else
{
headerClicked.Column.HeaderTemplate =
Application.Current.Resources["HeaderTemplateArrowUp"] as DataTemplate;
}
view.SortDescriptions.Clear();
}
else
{
headerClicked.Column.HeaderTemplate =
Application.Current.Resources["HeaderTemplateArrowUp"] as DataTemplate;
}
if (!string.IsNullOrEmpty(propertyName))
{
view.SortDescriptions.Add(new SortDescription(propertyName, direction));
}
}public static void SortOnCollectionUpdate(ICollectionView view)
{
view.Refresh();}
#endregion
}- Edited by MohammedAyaz Shaikh Saturday, November 1, 2014 9:33 AM
Saturday, November 1, 2014 9:30 AM -
There is a code formatting button for a reason. Please use it. However, if you want any more help on this you should upload a minimal reproducable code example of your issue to OneDrive and post the link to it here.
But can't you just save a reference to the ListView in a private field like this?:
private static ListView _theListView; // Using a DependencyProperty as the backing store for AutoSort. This enables animation, styling, binding, etc... public static readonly DependencyProperty AutoSortProperty = DependencyProperty.RegisterAttached( "AutoSort", typeof(bool), typeof(GridViewSort), new UIPropertyMetadata( false, (o, e) => { _lisView = o as ListView; if (_lisView != null) { if (GetCommand(_lisView) == null) // Don't change click handler if a command is set { bool oldValue = (bool)e.OldValue; bool newValue = (bool)e.NewValue; if (oldValue && !newValue) { _lisView.RemoveHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click)); } if (!oldValue && newValue) { _lisView.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnHeader_Click)); } } } ((INotifyCollectionChanged)_lisView.Items).CollectionChanged += new NotifyCollectionChangedEventHandler(GridViewSort_CollectionChanged); } ) ); static void GridViewSort_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Add) { if (e.NewItems != null) { foreach (Object item in e.NewItems) { (item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(GridViewSort_PropertyChanged); } } SortOnCollectionUpdate(_listView); } } static void GridViewSort_PropertyChanged(object sender, PropertyChangedEventArgs e) { //access _listView here.... }
Then you will be able to access it in the GridViewSort_PropertyChanged event handler.
Please remember to mark helpful posts as answer and/or helpful.
Saturday, November 1, 2014 10:05 AM -
hi Magnus,
But in my application there are multiple listview uses the same CommonUtility.cs feature. So how would I know propertyChanged is fired for which listview?
There are three listview in my application. For all of three listview, CollectionChanged and propertyChanged in CommonUtility.cs will be called.
So to implement this, do I need to maintain list KeyValue pair of listView reference here and use the appropriate listView from the KeyValue pair list when propertyChanged get fired?
Thanks,
Ayaz Shaikh
Saturday, November 1, 2014 10:43 AM -
It's a bit tricky following your code and it's intent.
If you want a common utility class which is going to handle propertychanged and then do something then you need to hand it whatever it needs to manipulate.
I suggest you pass in a reference to a ListCollectionView.
Then each viewmodel will new up it's own utility, hand in a ListCollectionView and the utility wires up a collectionchanged, sets the sortdescriptors on whichever ListCollectionView each instance gets.
class MyUtilityClass { public ListCollectionView lvw {get; set;} public ObservableCollection<object> oc {get;set;} MyUtilityClass() { oc.CollectionChanged += The_CollectionChanged; } void The_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { // Do stuff lvw.Refresh(); } }
In a Viewmodel:
private MyUtilityClass myUtilty; ...... myUtility = new MyUtilityClass {lvw = aListViewCollection, oc = anObservableCollection};
You might also have to hand in a lookup dictionary to satisfy the issue in your other thread.
- Edited by Andy ONeill Saturday, November 1, 2014 1:23 PM
Saturday, November 1, 2014 10:58 AM -
Hi Andy,
I am not getting your point. Please elaborate by chunk of code if possible.
Thanks,
Ayaz Shaikh
Saturday, November 1, 2014 1:11 PM -
I just added some code to my post - then I realised you'd asked for some.
Could you take a look at it and see if that's any clearer?
.
In this code sample here:
https://gallery.technet.microsoft.com/WPF-RowNo-in-Datagrid-5102958e
I use a different way to tell the viewmodel the user asked the datagrid to sort.
It communicates from code behind to the viewmodel via messenger
http://social.technet.microsoft.com/wiki/contents/articles/26070.communicating-between-classes.aspx
You could perhaps use a similar technique for the view to tell a utility to sort.
If you have issues passing events across into the utility.
Saturday, November 1, 2014 1:31 PM -
Hi Andy/Magnus,
Thanks for your multiple suggestions. It really helped me to find best possible solution as per my situation.
Andy, Option which you have suggested above will be more complex. So just to keep it simple I have followed the suggestion given by Magnus which I Mark as answer.
Thanks,
Ayaz Shaikh
Wednesday, November 5, 2014 5:49 AM