Problem: Data changes but ListView does not update
I have a ListView whose ItemsSource is set to
<ListView ItemsSource="{Binding ContactsGrouped}"
On click of a button I update the query to only return records that contain the letters "Je". I can see that the right thing is being returned, and that ContactsGrouped is being updated, but the UI does not change.
public ObservableCollection<Grouping<string, Contact>> ContactsGrouped { get; set; }
where Grouping looks like this:
public class Grouping<K, T> : ObservableCollection<T>
{
public K Key { get; private set; }
public Grouping ( K key, IEnumerable<T> items )
{
Key = key;
foreach ( var item in items )
this.Items.Add( item );
}
}
Given that I'm using ObservableCollections, I'd expect the list to redraw. Am I missing something obvious?
I presume the Grouping class is utilised from a ViewModel. In which case that ViewModel has to implement the INotifyPropertyChanged interface such as below:
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged ([CallerMemberName]string propertyName = null)
{
if (PropertyChanged != null) {
PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
}
}
#endregion
As long as you call the OnPropertyChnaged method on setting the property then you will get the results of the binding.
Turns out that while implementing INotifyPropertyChanged still won't update the list when filtering it. However, factoring out the code that populates the list in the VM and then calling that code in the OnTextChanged method (followed by a call to resetting the ItemsSource) does the trick.
public void OnTextChanged ( object sender, TextChangedEventArgs e ) {
vm.PopulateContacts( vm.CurrentDataService );
ContactListView.ItemsSource = vm.ContactsGrouped;
}
The PopulateContacts method looks like this (abridged)...
// setup
// Get the data
var sorted =
from contact in contacts
orderby contact.FullName
group contact by contact.FirstInitial
into contactGroup
select new Grouping<string, Contact> ( contactGroup.Key, contactGroup );
contactsGrouped = new ObservableCollection<Grouping<string, Contact>> ( sorted );
That works, and is reasonably clean and testable
Related
Inside viewmodel, I defined a ObservableCollection and bind it with the data grid;
AllData = CollectionViewSource.GetDefaultView(_allData);
But now I need to group the data in the view but not via the column header. I need to group by the content in the datagrid:
For example, I have column A,B,C,D,E,F, If ColumnA.Content contains keyword1, it is Group1, else if ColumnB and ColumnC are not empty, it is group2, else if ColumnD contains keyword2, it is group3...
How can I grouping like this?
Thanks!
Eva
Grouping relies on columns in your collectionview. The simplest way to approach this ( and simple is particularly good if you're a newbie ) is to add another public property to whatever the class is you have in _allData.
Let's assume for a moment this is RowVM ( short for row viewmodel ).
Hence _allData is an ObservableCollection.
We want to group on something so add another public property to RowVM. It seems this will be an int so you want something like:
public class RowVM : BaseViewModel
{
public int GroupColumn { get; set; } = 0;
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
}
It is preferable for anything you bind to implement inotifypropertychanged ( seem msdn documentation if this is a new idea ). https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-property-change-notification This is the case even if your code doesn't raise property changed anywhere.
You will want to do this with every viewmodel so you want a base class does that stuff and you don't repeat your code.
Hence I inherit from a BaseViewModel.
Here is an example:
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
That's just got auto getter and setters. Maybe this should be a propfull with a private backer and raise property changed when the value changes from the setter.
You need to populate that somehow so you, obviously, want a loop of some sort.
You could do that using linq and .ForEach() executing some code in the foreach or a for loop or a foreach loop.
Whichever you choose.
You want a bit of code in there which implements your logic and sets the value
Something like:
foreach (var row in _allData)
{
if (row.A.Contains(keyword1))
{
row.GroupColumn = 1;
continue;
}
if(string.IsNullOrEmpty(row.B) || string.IsNullOrEmpty(row.B))
{
row.GroupColumn = 3;
continue;
}
row.GroupColumn = 2;
}
I have a raddataform control which uses ObservableCollection as input source and auto generates fields.I have implemented Insert and Edit Logic in the person class itself which implements IEditableObject and INotifyPropertyChanged through BeginEdit and EndEdit methods. but public void Delete() method wont work there.also I learned that ObservableCollection has CollectionChanged event which has NotifyChangedCollectionAction.Remove .So how can I implement delete(remove) logic on ObservableCollection so that it can delete corresponding field using linq?
Here's code :
public class EmployeeDataContext
{
private ICollectionView employees = null;
public ICollectionView Employees
{
get
{
if (this.employees == null)
{
ObservableCollection<Person> newEmployees = new ObservableCollection<Person>();
DataClassesDataContext db = new DataClassesDataContext();
var query = from c in db.EPersons
select c;
foreach (var q in query)
{
newEmployees.Add(new Person((DateTime)q.EStartingDate, q.EFirstName,q.ELastName, (Person.OccupationPositions) q.EOccupation,q.EPhoneNumber, (int)q.ESalary));
}
//newEmployees.CollectionChanged += (sender, args) =>
// {
// if (args.Action == NotifyCollectionChangedAction.Remove)
// }
return this.employees;
}
}
}
Extract the DataConext object from your event handler's evenargs on your codebehind for BeginEdit and EndEdit.
Then call your viewmodel's Delete method referencing the DataContext retieved.
I'm not 100% sure that I fully understand your question, but if you're asking how you can add a custom Remove method to the ObservableCollection<T> class, then you can use Extension Methods. Perhaps something like this:
public static class ExtensionMethods
{
public static bool Remove<T>(this ObservableCollection<T> collection)
{
var someObject;
// custom logic here
return collection.Remove(someObject);
}
}
How to auto-notify ListView that its bound property has changed, the MVVM's way ?
Relevant code-behind:
public partial class MainWindow : Window
{
public DataModel StoreHouse { get; set; }
public ObservableCollection<Units> Devices { get { return StoreHouse.Units; } }
/*
... rest of the code ...
*/
}
XAML binding:
<ListView Name="UnitsListView" ItemsSource="{Binding Devices}">
When I do this:
StoreHouse = newDeserializedStoreHouse
The Units property is no longer valid. Now I can use DependencyProperty, and do this:
StoreHouse = newDeserializedStoreHouse
Units = StoreHouse.Units;
But it's not MVVM-ish... is there a way to do it automatically?
If the Storehouse and the Units are dependent, they should probably be in the same Viewmodel.
You could then just put the ViewModel in the DataContext of the View and bind to both the Storehouse and the Units by speficying the correct binding paths.
Replacing the storehouse is then a change to the ViewModel that could also update the Units or you could set up a completely new ViewModel and assign it to the DataContext.
Use INotifyPropertyChanged for your properties, e.g. like this:
private ObservableCollection<Thing> _things;
public ObservableCollection<Thing> Things
{
get { return _things; }
private set
{
if ( _things != value )
{
_things = value;
OnPropertyChanged();
}
}
}
public PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged( [CallerMemberName] string propertyName = "" )
{
var evt = PropertyChanged;
if ( evt != null)
{
evt( this, new PropertyChangedEventArgs( propertyName ) );
}
}
Do note the use of CallerMemberName on the propertyName parameter; the C# compiler will replace that parameter value with the name of the member in which you call the method, e.g. Things in our example. This is useful to avoid hard-coded strings, which introduce the risk of forgetting to change them if you change the property name. This is available in C# 5 on .NET 4.5; if you use an older C# version, you'll have to use hard-coded strings. (or do magic with expressions, but that's a lot more complex to do)
Many MVVM frameworks have a base class to implement INotifyPropertyChanged easily.
I have a ListBox on my UI that is bound to a property of ObservableCollection. I set a new instance of the ObservableCollection into the property in the view model's constructor and I can add items to it with a button on the form. These are visible in the list.
All is good.
However, if I reinitialize the property with new in the button callback, it breaks the binding and the UI no longer shows what is in the collection.
I assumed the binding would continue to look up the values of the property, but its presumably linked to a reference which is destroyed by the new.
Have I got this right? Can anyone expand on how this is linked up? Is there a way to rebind it when my view model has no knowledge of the view?
Make sure you are raising a PropertyChangedEvent after you reintialize your collection. Raising this event will allow the view to handle changes to the property with the model having no knowledge of the view.
class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
private ObservableCollection<string> _list = new ObservableCollection<string>();
public ObservableCollection<string> List
{
get { return _list; }
set
{
_list = value;
NotifyPropertyChanged("List");
}
}
public Model()
{
List.Add("why");
List.Add("not");
List.Add("these?");
}
}
I think based on your description that what you need to do is refactor the property that exposes your ObservableCollection so that it raises a PropertyChanged event also when it is assigned a new value. Example:
public ObservableCollection<int> Integers
{
get { return this.integers; }
set {
if (this.integers != value)
{
this.integers = value;
RaisePropertyChanged("Integers");
}
}
}
Supposing you've implemented INotifyPropertyChanged on your ViewModel, you can raise the property changed event on your ObservableCollection whenever you assign a new value to it.
public ObservableCollection<string> MyList { get; set; }
public void SomeMethod()
{
MyList = new ObservableCollection<string>();
RaisePropertyChanged("MyList");
}
Updates to the ObservableCollection are handled by hooking in to the CollectionChanged event so when you create a new ObservableCollection your observer is still looking at the old collection.
Two simple suggestions would be either to implement INotifyPropertyChanged on the class that contains the ObservableCollection and raising the PropertyChanged event in the setter of the collection property (don't forget to unhook from the old one first in your observer if it's your own code).
private ObservableCollection<string> _myCollection = new ObservableCollection<string>();
public ObservableCollection<string> MyCollection
{
get { return _myCollection; }
set
{
if(_myCollection == value)
return;
_myCollection = value;
RaisePropertyChanged("MyCollection");
}
}
A second, and the option I generally prefer is to just clear and repopulate the collection with your new data when it arrives.
public void HandleCollectionData(IEnumerable<string> incomingData)
{
MyCollection.Clear();
foreach(var item in incomingData)
{
MyCollection.Add(item);
}
}
<ListBox x:Name="MainList" HorizontalAlignment="Left" Height="468" Margin="10,10,0,0" VerticalAlignment="Top" Width="100" ItemsSource="{Binding Items,Mode=TwoWay}" DisplayMemberPath="Name"/>
[Serializable()]
public class MYcontainer : INotifyPropertyChanged,ISerializable
{
private List<MYClass> _items = new List<MYClass>();
public List<MYClass> Items
{
get{ return _items;}
set { this._items =value;
OnPropertyChanged("Items");
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
When I add an item to "Items" the UI doesn't update, the binding is working fine, since if I closed the window and opened it again, the new items appear correctly.
What am I doing wrong? I know if I used ObservableCollection it will work fine, but shouldn't it work with List<>? I already have in another window a string[] property and it update fine.
If you don't want to ues ObservableCollection you will have to implement INotifyCollectionChanged.
public partial class MainWindow : Window, INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
public MainWindow()
{
InitializeComponent();
}
public void NotifyCollectionChanged(NotifyCollectionChangedAction action)
{
if (CollectionChanged != null)
{
CollectionChanged(this, new NotifyCollectionChangedEventArgs(action));
}
}
}
However ObservableCollection does all this for you, adding all the same logic to your List<T> would just create a custom ObservableCollection, I see no point in this when MS has alraedy made this for you
It will currently only update if you replace the entire list with a new List<MyClass>. Replacing 1 item won't trigger the OnPropertyChanged event.
Use an ObservableCollection<MyClass> instead of a List<MyClass>. It's built specifically to handle this issue and notifies WPF whenever the items in the collection change.
It's very comparable to list in other respects so the changes to your code should be minimal (Both List and ObservableCollection implement the ICollection<T> interface, so most of the methods are shared).