I have a model which currently looks through a series of different log files and then makes an object for each item in those files and appends them to a list (ListOfLogs). Once the model is done parsing the log files it does a property changed event to notify the VM that the ListOfLogs is ready.
The Viewmodel then handles the property changed event and creates an ObservableCollection from the model's ListOfLogs. The view then binds to that observablecollection.
Now that I have switched from an ObservableCollection to a ICollectionView I get an invalid operation exception since the calling thread doesn't own ListOfLogs object. This makes me thing that the way I expose the List is not following the MVVM pattern
Added Code:
ViewModel.cs:
public class ViewModel : INotifyPropertyChanged {
#region Fields
#endregion // Fields
#region Properties
public Model myModel { get; private set; }
public ObservableCollection<MyObject> collectionView { get; set; }
#endregion // Properties
#region Constructor
public ViewModel() {
myModel = new Model();
myModel.PropertyChanged += propertyChanged;
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion // Constructor
#region Methods
private void propertyChanged(object sender, PropertyChangedEventArgs e) {
switch (e.PropertyName ) {
case "Objects":
// Is there a better way to do this
collectionView = new ObservableCollection<MyObject>(myModel.Objects);
//
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("collectionView"));
break;
default:
Console.WriteLine(string.Format("No case for {0}, ", e.PropertyName));
break;
}
}
Model.cs:
Edit: fixed mistake when invoking the property changed event
namespace TestApp1 {
public class Model : INotifyPropertyChanged {
#region Fields
private IList<MyObject> _Objects;
public event PropertyChangedEventHandler PropertyChanged;
#endregion // Fields
#region Properties
public IList<MyObject> Objects { get => _Objects ?? (_Objects = new List<MyObject>()); private set { if (Objects != value) _Objects = value; } }
#endregion // Properties
#region Constructor
public Model() {
}
#endregion // Constructor
#region Methods
public void LoadObjects() {
// Parse through files normally for now just junk works
Parallel.For(0, 10000, dostuff => {
var myOb = new MyObject(){ dt = DateTime.Now, message = "Message" };
lock (Objects) {
Objects.Add(myOb);
}
});
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Objects"));
}
#endregion // Methods
}
public class MyObject {
public DateTime dt { get; set; }
public string message { get; set; }
public string stuff1 { get; set; }
public string stuff2 { get; set; }
}
}
The problem is, that you are modifying the Objects list while passing it to the constructor of the observable collection. (https://referencesource.microsoft.com/#system/compmod/system/collections/objectmodel/observablecollection.cs,cfaa9abd8b214ecb in the constructor where "copyfrom")
The InvalidOperationException belongs to your Objects.Add() call in the Parallel.For.
private void CopyFrom(IEnumerable<T> collection)
{
IList<T> items = Items;
if (collection != null && items != null)
{
using (IEnumerator<T> enumerator = collection.GetEnumerator())
{
while (enumerator.MoveNext())
{
items.Add(enumerator.Current);
}
}
}
}
In the delegate of Parallel.For you are using a lock. You could use this as well for the property changed event:
lock(myModel.Objects)
{
collectionView = new ObservableCollection<MyObject>(myModel.Objects);
}
Or add the event raising to the lock in the Parallel.For delegate
lock (Objects)
{
Objects.Add(myOb);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Objects"));
}
Or you could just wait until all items are read and then raise one property changed event after completing the Parallel.For.
Related
My objective is to make a list that would fire an event when element within changes. Idea is to create a BindingList of entities that implement INotifyChanged to forward that event to ViewModel.
What i currently have:
public class ViewModel
{
public TagPresenter Tags {get;}
public ViewModel()
{
Tags = new TagPresenter();
Tags.TagCollection.ListChanged += (object o, ListChangedEventargs e) => { DataAccessor.UpdateTag(o[e.NewIndex]); };
foreach(var tag in DataAccessor.GetTags())
Tags.TagCollection.Add(new TagEntity(tag, Tags.TagCollection));
}
}
public class TagPresenter
{
public BindingList<object> TagCollection {get;}
public TagPresenter()
{
TagCollection = new BindingList<object>();
}
}
public class TagEntity : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged {get;}
public Command ChangeState {get;}
public TagEntity(string tag, BindingList<object> parent)
{
ChangeState = new Command(new Action(() => {
NotifyPropertyChanged("Property");
}));
}
public void NotifyPropertyChanged(string _property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(_property));
}
}
In this code, ListChanged event triggers when new entities are added into the list in foreach loop, but not when i trigger PropertyChanged of entity within BindingList (breakpoint within NotifyPropertyChanged method stops, but ListChanged event does not fire)
OK, figured it out, the problem was due to boxing\unboxing of TagEntity to and from object in BindingList. Once i've added abstract class TagBase that implemented INotifyChanged, and switched collection to BindingList it got to work as intended:
public class ViewModel
{
public TagPresenter Tags {get;}
public ViewModel()
{
Tags = new TagPresenter();
Tags.TagCollection.ListChanged += (object o, ListChangedEventargs e) => { DataAccessor.UpdateTag(o[e.NewIndex]); };
foreach(var tag in DataAccessor.GetTags())
Tags.TagCollection.Add(new TagEntity(tag, Tags.TagCollection));
}
}
public class TagPresenter
{
public BindingList<TagBase> TagCollection {get;}
public TagPresenter()
{
TagCollection = new BindingList<TagBase>();
}
}
public abstract class TagBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged {get;}
public void NotifyPropertyChanged(string _property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(_property));
}
}
public class TagEntity : TagBase
{
public Command ChangeState {get;}
public TagEntity(string tag, BindingList<TagBase> parent)
{
ChangeState = new Command(new Action(() => {
NotifyPropertyChanged("Property");
}));
}
}
I created a Class EventList inheriting List which fires an Event each time something is Added, Inserted or Removed:
public class EventList<T> : List<T>
{
public event ListChangedEventDelegate ListChanged;
public delegate void ListChangedEventDelegate();
public new void Add(T item)
{
base.Add(item);
if (ListChanged != null
&& ListChanged.GetInvocationList().Any())
{
ListChanged();
}
}
...
}
At the Moment I use it as a Property like this:
public EventList List
{
get { return m_List; }
set
{
m_List.ListChanged -= List_ListChanged;
m_List = value;
m_List.ListChanged += List_ListChanged;
List_ListChanged();
}
}
Now my Problem is, can I somehow handle if a new Object is referred to it or prevent that, so I do not have to do the event wiring stuff in the setter?
Of course, I can change the property to "private set" but I would like to be able to use the class as variable as well.
You seldom create a new instance of a collection class in a class. Instantiate it once and clear it instead of creating a new list. (and use the ObservableCollection since it already has the INotifyCollectionChanged interface inherited)
private readonly ObservableCollection<T> list;
public ctor() {
list = new ObservableCollection<T>();
list.CollectionChanged += listChanged;
}
public ObservableCollection<T> List { get { return list; } }
public void Clear() { list.Clear(); }
private void listChanged(object sender, NotifyCollectionChangedEventArgs args) {
// list changed
}
This way you only have to hook up events once, and can "reset it" by calling the clear method instead of checking for null or equality to the former list in the set accessor for the property.
With the changes in C#6 you can assign a get property from a constructor without the backing field (the backing field is implicit)
So the code above can be simplified to
public ctor() {
List = new ObservableCollection<T>();
List.CollectionChanged += OnListChanged;
}
public ObservableCollection<T> List { get; }
public void Clear()
{
List.Clear();
}
private void OnListChanged(object sender, NotifyCollectionChangedEventArgs args)
{
// react to list changed
}
ObservableCollection is a List with a CollectionChanged event
ObservableCollection.CollectionChanged Event
For how to wire up the event handler see answer from Patrick. +1
Not sure what you are looking for but I use this for a collection with one event that fires on add, remove, and change.
public class ObservableCollection<T>: INotifyPropertyChanged
{
private BindingList<T> ts = new BindingList<T>();
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged( String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public BindingList<T> Ts
{
get { return ts; }
set
{
if (value != ts)
{
Ts = value;
if (Ts != null)
{
ts.ListChanged += delegate(object sender, ListChangedEventArgs args)
{
OnListChanged(this);
};
}
NotifyPropertyChanged("Ts");
}
}
}
private static void OnListChanged(ObservableCollection<T> vm)
{
// this will fire on add, remove, and change
// if want to prevent an insert this in not the right spot for that
// the OPs use of word prevent is not clear
// -1 don't be a hater
vm.NotifyPropertyChanged("Ts");
}
public ObservableCollection()
{
ts.ListChanged += delegate(object sender, ListChangedEventArgs args)
{
OnListChanged(this);
};
}
}
If you do not want to or can not convert to an Observable Collection, try this:
public class EventList<T> : IList<T> /* NOTE: Changed your List<T> to IList<T> */
{
private List<T> list; // initialize this in your constructor.
public event ListChangedEventDelegate ListChanged;
public delegate void ListChangedEventDelegate();
private void notify()
{
if (ListChanged != null
&& ListChanged.GetInvocationList().Any())
{
ListChanged();
}
}
public new void Add(T item)
{
list.Add(item);
notify();
}
public List<T> Items {
get { return list; }
set {
list = value;
notify();
}
}
...
}
Now, for your property, you should be able to reduce your code to this:
public EventList List
{
get { return m_List.Items; }
set
{
//m_List.ListChanged -= List_ListChanged;
m_List.Items = value;
//m_List.ListChanged += List_ListChanged;
//List_ListChanged();
}
}
Why? Setting anything in the EventList.Items will call your private notify() routine.
I have a Solution for when someone calls the Generic method from IList.add(object). So that you also get notified.
using System;
using System.Collections;
using System.Collections.Generic;
namespace YourNamespace
{
public class ObjectDoesNotMatchTargetBaseTypeException : Exception
{
public ObjectDoesNotMatchTargetBaseTypeException(Type targetType, object actualObject)
: base(string.Format("Expected base type ({0}) does not match actual objects type ({1}).",
targetType, actualObject.GetType()))
{
}
}
/// <summary>
/// Allows you to react, when items were added or removed to a generic List.
/// </summary>
public abstract class NoisyList<TItemType> : List<TItemType>, IList
{
#region Public Methods
/******************************************/
int IList.Add(object item)
{
CheckTargetType(item);
Add((TItemType)item);
return Count - 1;
}
void IList.Remove(object item)
{
CheckTargetType(item);
Remove((TItemType)item);
}
public new void Add(TItemType item)
{
base.Add(item);
OnItemAdded(item);
}
public new bool Remove(TItemType item)
{
var result = base.Remove(item);
OnItemRemoved(item);
return result;
}
#endregion
# region Private Methods
/******************************************/
private static void CheckTargetType(object item)
{
var targetType = typeof(TItemType);
if (item.GetType().IsSubclassOf(targetType))
throw new ObjectDoesNotMatchTargetBaseTypeException(targetType, item);
}
#endregion
#region Abstract Methods
/******************************************/
protected abstract void OnItemAdded(TItemType addedItem);
protected abstract void OnItemRemoved(TItemType removedItem);
#endregion
}
}
If an ObservableCollection is not the solution for you, you can try that:
A) Implement a custom EventArgs that will contain the new Count attribute when an event will be fired.
public class ChangeListCountEventArgs : EventArgs
{
public int NewCount
{
get;
set;
}
public ChangeListCountEventArgs(int newCount)
{
NewCount = newCount;
}
}
B) Implement a custom List that inherits from List and redefine the Count attribute and the constructors according to your needs:
public class CustomList<T> : List<T>
{
public event EventHandler<ChangeListCountEventArgs> ListCountChanged;
public new int Count
{
get
{
ListCountChanged?.Invoke(this, new ChangeListCountEventArgs(base.Count));
return base.Count;
}
}
public CustomList()
{ }
public CustomList(List<T> list) : base(list)
{ }
public CustomList(CustomList<T> list) : base(list)
{ }
}
C) Finally subscribe to your event:
var myList = new CustomList<YourObject>();
myList.ListCountChanged += (obj, e) =>
{
// get the count thanks to e.NewCount
};
I have a small error, when I try to bind a ObservableCollection in the ViewModel. The problem is that it connects to a web api to get the list, but for some reason, it not quick enough.
I think it has something to do with async / await where it's not waiting for the list to get its data before the view is loaded.
Code:
public ObservableCollection<AvailableRoomModel> AvailableRooms { get; set; }
public ObservableCollection<AvailableRoomModel> List { get; set; }
The AvailableRooms is the correct list, and the List is just for testing.
public RoomsViewModel(IGetAvailableRoomsService getAvailableRoomsService)
{
//Injection
_getAvailableRoomsService = getAvailableRoomsService;
//Initialize
AvailableRooms = new ObservableCollection<AvailableRoomModel>();
//Get all rooms
GetAvailableRooms();
List = new ObservableCollection<AvailableRoomModel>();
List.Add(new AvailableRoomModel { Id = 1, RoomNumber = "101", Occupied = true });
List.Add(new AvailableRoomModel { Id = 2, RoomNumber = "102", Occupied = true });
List.Add(new AvailableRoomModel { Id = 3, RoomNumber = "103", Occupied = true });
}
public async void GetAvailableRooms()
{
try
{
AvailableRooms = await _getAvailableRoomsService.getRooms();
}
catch (Exception e)
{
//TODO
}
}
Have tested that if I bind my ItemsControl to the list with name = List it works (its fast enough) but it dosen't work when binding to AvailableRooms.
I really don't want a searchCommand in the view I can click, Just want to have the list populated before showing the view.
Any ideas?
The problem is async void. If you would like to await a method then its return type must be Task or Task<T>.
public async Task<IEnumerable<AvailableRoomModel>> GetAvailableRooms()
{
try
{
return await _getAvailableRoomsService.getRooms();
}
catch (Exception e)
{
//TODO
}
}
In the ctor you cannot await this method, so you can simply call this method like this:
public RoomsViewModel(IGetAvailableRoomsService getAvailableRoomsService)
{
//Injection
_getAvailableRoomsService = getAvailableRoomsService;
//Get all rooms
AvailableRooms =
new ObservableCollection<AvailableRoomModel>(
GetAvailableRooms().GetAwaiter().GetResult()
);
// ...
}
UPDATE:
You should also implement System.ComponentModel.INotifyPropertyChanged interface in your viewmodel and you have to use full properties to be able to raise the PropertyChanged event. The ObservableCollection<T> itself implements this interface but setting a property will not raise this event unless we raise it.
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class RoomsViewModel : INotifyPropertyChanged
{
/* constructor goes here from previous code block */
private ObservableCollection<AvailableRoomModel> availableRooms;
public ObservableCollection<AvailableRoomModel> AvailableRooms
{
get { return availableRooms; }
set { availableRooms = value; OnPropertyChanged(); }
}
private ObservableCollection<AvailableRoomModel> list;
public ObservableCollection<AvailableRoomModel> List
{
get { return list; }
set { list = value; OnPropertyChanged(); }
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
I don't understand why when I update a object, my bound controls do not update.
The data displays fine initially, but when I want to refresh the data displayed in the UI nothing happens when I update the object. The object updates fine. The ViewModel does use INotifyPropertyChanged on all fields.
However if I update individual items directly, I can update my UI. As commented below.
I guess I've made a school boy error somewhere here?
UPDATE: I've added the model to the question. While I understand the answers, I don't understand how to implement it. Attempted to implement a collection changed event without success. Can I have some pointers please?
public partial class CisArrivalsPanel : UserControl
{
private ApiDataArrivalsDepartures _theArrivalsDepartures;
public CisArrivalsPanel()
{
InitializeComponent();
_theArrivalsDepartures = new ApiDataArrivalsDepartures();
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Kings Cross");
this.DataContext = _theArrivalsDepartures;
ListBoxArr.ItemsSource = _theArrivalsDepartures.StationMovementList;
}
void Reload()
{
//This does not update the UI**
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Paddington");
//However this (when uncommented, and I comment out the above line) does update the UI**
//_theArrivalsDepartures.StationMovementList[0].OriginName = "test";
//_theArrivalsDepartures.StationMovementList[0].Platform = "0";
//_theArrivalsDepartures.StationMovementList[0].BestArrivalEstimateMins = "999";
//_theArrivalsDepartures.StationName = "test";
}
private void StationHeader_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Reload();
Debug.WriteLine(_theArrivalsDepartures.StationName);
foreach (var a in _theArrivalsDepartures.StationMovementList)
{
Debug.WriteLine(a.OriginName);
Debug.WriteLine(a.BestArrivalEstimateMins);
}
}
}
EDIT : Added Model
public class ApiDataArrivalsDepartures : INotifyPropertyChanged
{
private string _stationName;
[JsonProperty(PropertyName = "station_name")]
public string StationName {
get
{
return _stationName;
}
set
{
_stationName = value;
NotifyPropertyChanged("StationName");
}
}
private List<StationListOfMovements> _stationMovementList;
public List<StationListOfMovements> StationMovementList
{
get
{
return _stationMovementList;
}
set
{
_stationMovementList = value;
NotifyPropertyChanged("StationMovementList");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class StationListOfMovements : INotifyPropertyChanged
{
private string _originName;
[JsonProperty(PropertyName = "origin_name")]
public string OriginName {
get
{
return _originName;
}
set
{
_originName = value;
NotifyPropertyChanged("OriginName");
}
}
[JsonProperty(PropertyName = "destination_name")]
public string DestinationName { get; set; }
private string _platform;
[JsonProperty(PropertyName = "Platform")]
public string Platform {
get
{
return _platform;
}
set
{
_platform = value;
NotifyPropertyChanged("Platform");
}
}
private string _bestArrivalEstimateMins;
[JsonProperty(PropertyName = "best_arrival_estimate_mins")]
public string BestArrivalEstimateMins {
get
{
return _bestArrivalEstimateMins;
}
set
{
_bestArrivalEstimateMins = value;
NotifyPropertyChanged("BestArrivalEstimateMins");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
There are two pieces here pertaining to your collection (technically three):
If you want a new collection to propagate, the collection property has to raise PropertyChanged (sounds like it does)
If you want add/remove on the collection to propagate, you need to use a collection that implements INotifyCollectionChanged. ObservableCollection is a good choice.
If you want changes to the items in the container to propagate, then those items need to implement INotifyPropertyChanged and raise the PropertyChanged event.
Make sure all those are covered, and the changes should appear on the UI as you expect.
You should update the DataContext and ItemsSource too.
void Reload()
{
//This does not update the UI**
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Paddington");
DataContext = theArrivalsDepartures;
ListBoxArr.ItemsSource = _theArrivalsDepartures.StationMovementList;
}
Use for the collection ObservableCollection , this class notify the ui when change to the collection occurred
your reload function works because the there is PropertyChanged on all the fields include this one
it notify the ui and reload the correct collection
I have an ObservableCollection of items bound to a listbox as the ItemsSource.
Some of these items are also located in another collection on the same ViewModel (call it CollectionTwo).
I want to be able to take the count of the item in Collection2 and display it for the respective item in CollectionOne. When CollectionTwo properties change (ie the Count), it must also be reflected back to CollectionOne.
I would guess the best way to do this in MVVM is to wrap items in CollectionOne with a viewmodel class with an extra Count property on it. Can someone point me to a good example of this? Or perhaps another method to tackle this problem that won't hugely weigh down my ItemsSource performance.
Thanks!
You can use inheritance to create a custom collection along these lines...
public class MyCollection<T> : ObservableCollection<T>, INotifyPropertyChanged
{
// implementation goes here...
//
private int _myCount;
public int MyCount
{
[DebuggerStepThrough]
get { return _myCount; }
[DebuggerStepThrough]
set
{
if (value != _myCount)
{
_myCount = value;
OnPropertyChanged("MyCount");
}
}
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
var handler = System.Threading.Interlocked.CompareExchange(ref PropertyChanged, null, null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
This is a class that wraps an Observable Collection and puts a custom property in it. The property participates in change notification, but that depends upon your design.
To wire it up, you can do something like this...
public MyCollection<string> Collection1 { get; set; }
public MyCollection<string> Collection2 { get; set; }
public void Initialise()
{
Collection1 = new MyCollection<string> { MyCount = 0 };
Collection2 = new MyCollection<string> { MyCount = 0 };
Collection2.CollectionChanged += (s, a) =>
{
// do something here
};
}
You can also do something like...
Collection1.PropertyChanged += // your delegate goes here