Tree structure with a nested observablecollection of children - c#

I have a class that looks like the following
class ModelCollection : ObservableCollection<MyModel>
{
public ModelCollection ()
{
this.CollectionChanged += (s, e) =>
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
//do something
}
if(e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
{
//do something
}
};
}
//etc..
}
and the class MyModel
public class MyModel
{
public int id {get;set;}
public string Name {get;set;}
public MyModel Parent {get;set;}
public ModelCollection Descendants {get;set;}
public ModelCollection Children {get;set;}
//etc..
}
and in general i have a collection that have the root MyModel which has Children of type MyModel and those children have children of that type and it can reach up to 20 levels of nesting, so its basicly a tree structure.
as you can see in the ModelCollection i have an event listener, my question is is it ok to have this amount of event listeners considering that i have thousands of MyModel items and multiple nested levles per item, if not is there a better suggestion?

You can use so match event listeners, but then you have to maintain it. In addition it can cause memory leaks.
Another option is to use INotifyPropertyChanged on all of your model objects, for example:
public class MyModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int id { get {...} set { OnPropertyChanged("id"); } }
public string Name { get {...} set { OnPropertyChanged("Name"); } }
public MyModel Parent { get {...} set { OnPropertyChanged("Parent"); } }
public ModelCollection Descendants { get {...} set { OnPropertyChanged("Descendants"); } }
public ModelCollection Children { get {...} set { OnPropertyChanged("Children"); } }
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then you can use hierarchical data context and similar WPF's features which can utilize the INotifyPropertyChanged implementation without any work from your side.
Another option is to inherit DependencyObject and use DependencyProperties instead of simple properties, which WPF can utilize in similar ways. However I think DependencyObject may be an overkill in cases of non-UI elements, and INotifyPropertyChanged is preferred in such cases.
Update: I noticed you didn't mention that you need the ObservalbleCollection for UI reasons, so my comment about WPF may be redundant. Anyway I think that using INotifyPropertyChanged is anyway a good way to manage such things, and I think it is also the standard one.

Related

Nested view model in WPF

Still trying to learn MVVM and WPF here.
I'm trying to create a complex view model EditArticleViewModel. It has some code that is repeated for similar controls and so I've moved the repeating code into another class. Then, I've added several instances of that other class into EditArticleViewModel.
I will set an instance of EditArticleViewModel as my window's DataContext. And I will bind to things like Categories.Items and Subcategories.SelectedItem.
public class CategoryView
{
public ObservableCollection<object> Items { /* */ }
public object SelectedItem { /* ... */ }
}
public class SubcategoryView
{
public ObservableCollection<object> Items { /* */ }
public object SelectedItem { /* ... */ }
}
public class EditArticleViewModel : INotifyPropertyChanged
{
public CategoryView Categories { get; private set; }
public SubcategoryView Subcategories { get; private set; }
public EditArticleViewModel()
{
Categories = new CategoryView();
SubcategoryView Subcategories new SubcategoryView();
}
// Additional properties and methods here
}
As you can see, my EditArticleViewModel class implements INotifyPropertyChanged so that I can notify the visual elements when something has changed.
My question is about how I notify visual elements about changes within CategoryView and SubcategoryView. Is there a way to notify the window about changes within these classes directly? Or must I raise an event from each class and have EditArticleViewModel handle that event in order to send the appropriate notification?
Any tips appreciated.
There should only be one ViewModel per View, with an extend that primary ViewModel can contain other "ViewModels".
So when you set DataContext to your primary ViewModel all the content of it will be have a subscription to NotifyPropertyChanged event, thus implementing INotifyPropertyChanged interface in other derived ViewModel will be notified.
I would suggest implementing a base class with INotifyPropertyChanged interface which you could derive from in your other ViewModels.
By having this alteration you should solve the problem you are having:
public class ObservableViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string propName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
public class CategoryView : ObservableViewModelBase
{
public ObservableCollection<object> Items { /* */ }
public object SelectedItem { /* ... */ }
}
public class SubcategoryView : ObservableViewModelBase
{
public ObservableCollection<object> Items { /* */ }
public object SelectedItem { /* ... */ }
}
public class EditArticleView : ObservableViewModelBase
{
public CategoryView Categories { get; set; } = new CategoryView();
public SubcategoryView Subcategories { get; set; } = new SubcategoryView();
}
Regarding ObservableCollection. It will notify view to change only when you add/remove items but it does not notify when content is changed. To update view on item content change you should have something like that:
public class GridRowItemViewModel : ObservableViewModelBase // From previous example.
{
private string _sampleProp;
public string SampleProp
{
get
{
return _sampleProp;
}
set
{
_sampleProp = value;
OnPropertyChanged();
}
}
}
And thus your Main ViewModel should look something like this:
public class MainViewModel : ObservableViewModelBase // This is your DataContext.
{
public ObservableCollection<GridRowItemViewModel> GridCollection { get; set; }
}
EDIT: You cannot bind to fields, WPF does not resolve fields. It can only handle properties. So by creating plain fields of child ViewModels you are getting no where. Change these into properties and you will be able to access its content in the View by the property name.

inheritance base class implements interface INotifyPropertyChanged can child class make use of it

I have a datagrid which is bound to an ObservableCollection. I would like to know when a property is changed. I have a similar datagrid in the same application where I have this working. However this datagrid is bound to a class that inherits from another class.
So a simple code snippet shown below.
In the child do I have to implement the INotifyPropertyChanged interface although that seems bit of a pain to me and not really making use of inheritance. Can I simply just make the OnPropertyChanged public or is that wrong?
Base Class
class Animal : INotifyPropertyChanged
{
public int Age
{
get
{ return _age;}
set
{
_age = value;
OnPropertyChanged("Age");
}
}
int _age
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Child Class
class Dog : Animal
{
public bool Fleas
{
get
{ return _fleas;}
set
{
_fleas = value;
}
}
int _fleas
}
protected would probably be what you want, but generally, yes.
One thing you often find, for example in MVVM frameworks such as Caliburn.Micro, is having an abstract class like PropertyChangedBase that has nothing but that simple implementation of INotifyPropertyChanged.
public abstract class PropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The downside of this approach is that all classes that want to make use of this ultimately have to inherit from PropertyChangedBase, which may not always be desired or possible.

A method that fires when a class or its properties are accessed [duplicate]

C# - .net 3.5
I have a family of classes that inherit from the same base class.
I want a method in the base class to be invoked any time a property in a derrived class is accessed (get or set). However, I don't want to write code in each and every property to call the base class... instead, I am hoping there is a declarative way to "sink" this activity into the base class.
Adding some spice to the requirement, I do need to determine the name of the property that was accessed, the property value and its type.
I imagine the solution would be a clever combination of a delegate, generics, and reflection. I can envision creating some type of array of delegate assignments at runtime, but iterating over the MemberInfo in the constructor would impact performance more than I'd like. Again, I'm hoping there is a more direct "declarative" way to do this.
Any ideas are most appreciated!
You can't do it automatically, but you can pretty much get 95% for free. This is a classic case for aspect-oriented programming. Check out PostSharp, which has the OnFieldAccessAspect class. Here's how you might solve your problem:
[Serializable]
public class FieldLogger : OnFieldAccessAspect {
public override void OnGetValue(FieldAccessEventArgs eventArgs) {
Console.WriteLine(eventArgs.InstanceTag);
Console.WriteLine("got value!");
base.OnGetValue(eventArgs);
}
public override void OnSetValue(FieldAccessEventArgs eventArgs) {
int i = (int?)eventArgs.InstanceTag ?? 0;
eventArgs.InstanceTag = i + 1;
Console.WriteLine("value set!");
base.OnSetValue(eventArgs);
}
public override InstanceTagRequest GetInstanceTagRequest() {
return new InstanceTagRequest("logger", new Guid("4f8a4963-82bf-4d32-8775-42cc3cd119bd"), false);
}
}
Now, anything that inherits from FieldLogger will get the same behavior. Presto!
I don't believe this is possible to do declaratively, i have never seen it done that way. What you can do though is implement the INotifyPropertyChanged interface on your base class, and have the implementation of the interface in the base class. Something like this:
public class A : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected virtual void RaiseOnPropertyChanged(object sender, string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(sender, new PropertyChangedEventArgs(propertyName);
}
public A()
{
this.PropertyChanged += new PropertyChangedEventHandler(A_PropertyChanged);
}
void A_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//centralised code here that deals with the changed property
}
}
public class B : A
{
public string MyProperty
{
get { return _myProperty; }
set
{
_myProperty = value;
RaiseOnPropertyChanged(this, "MyProperty");
}
}
public string _myProperty = null;
}

Multiple viewmodels sharing a service with notification

Question: I have two viewmodels that share a service with a list. My question is how to setup the notification so that both viewmodels know when this list is changed. Description below and code of where i'm at.
I found this example HERE that looks right for what I'm trying to do, but I have a question regarding how to be notified in my viewmodels of a change in my service. I'll add some code I've mocked up to see if I'm on the right track. I'm using WPF/MVVM Light.
First part is a service with a interface that will have a list of data, in this example I'm using a string list. I want a property in both viewmodels to have access to this list of data, and be notified when it's changed. I think what's throwing me is the interface IOC into my viewmodels. I'm understanding more and more why this is good, but I'm still wrapping my mind around it and I'm not sure how to setup the notification when the list is changed in the service. If my service was not injected I might have setup a event or property that my viewmodel property would access get/set but injecting my service does not expose my public fields, just methods. This is new to me so it's very likely i'm not understanding this correctly or missing something.
I used a List in my service instead of a ObservableCollection based on some reading I've done warning against using the ObservableCollection here. Thanks you for any help.
public class MyService : IMyService
{
private List<string> myList = new List<string>();
public List<string> getMyList()
{
return this.myList;
}
public void setMyList(List<string> value)
{
this.myList = value;
}
public void addValue(string value)
{
this.myList.Add(value);
}
public void insertValue(int index, string value)
{
this.myList.Insert(index, value);
}
}
public class MyViewModelOne : ViewModelBase
{
private readonly IMyService myService;
public MyViewModelOne(IMyService myService)
{
this.myService = myService;
}
public List<string> MyProperty // control item source will bind to this
{
get
{
return this.myService.getSource();
}
}
public void setSomeValue(value)
{
this.myService.addValue(value);
}
}
public class MyViewModelTwo : ViewModelBase
{
private readonly IMyService myService;
public MyViewModelTwo(IMyService myService)
{
this.myService = myService;
}
public List<string> MyProperty // control item source will bind to this
{
get
{
return this.myService.getSource();
}
}
public void setSomeValue(value)
{
this.myService.addValue(value);
}
}
From what I understood about your problem, what you need essentially is that your INotifyPropertyChanged implementation at your service level and the list my list to be an ObservableCollection being injected from the service.
Now if there is a notification change it would be directly on the service and hence no explicit need of handling.
Your binding could look like "{Binding MyService.MyProperty}"
I got this working two different ways, I went with the first example because I think it's easier to follow in the code.
This came up because I had a control in my mainview with related code that was growing and I realized I wanted the same control/behavior in a separate view that would use the same data/control for a different purpose.
I did not want to duplicate this control/template/code in two places so I made it into a User Control. I then nest the user control in my views. The user control has it's own VM. The main view updates the service with new data, and the nested control listens on a event to know when there is new data.
Still very new to MVVM thinking so please feel free to point out in issues with either of these examples.
Example using a service with eventhandler.
public interface IMyInterface
{
event EventHandler OnSomeEvent;
void addSomeData(string value);
void getSomeData();
}
public class MyInterface: IMyInterface
{
public event EventHandler OnSomeEvent = delegate { };
public void addSomeData(string value)
{
// do stuff
OnSomeEvent();
}
public string getSomeData()
{
return "some data";
}
}
// Main ViewModel
public class ViewModelOne : ViewModelBase
{
IMyInterface myInterface;
public NotifyViewModel(IMyInterface myInterface)
{
this.myInterface = myInterface;
this.myInterface.OnItemSourceChanged += myInterface_OnSomeEvent;
}
void testEvent()
{
this.myInterface.addSomeData("test data");
}
}
// My nested user control
public class ViewModelTwo : ViewModelBase
{
IMyInterface myInterface;
public NotifyViewModel(IMyInterface myInterface)
{
this.myInterface = myInterface;
this.myInterface.OnItemSourceChanged += myInterface_OnSomeEvent;
}
void myInterface_OnSomeEvent(object sender, System.EventArgs e)
{
// do stuff
}
}
Example using MVVM Light Messaging
public class EventDataSource
{
public string MyItemSource { get; set; }
public EventDataSource()
{
MyItemSource = string.Empty;
}
}
// Message class
public class MyDataSourceMessage : MessageBase
{
public EventDataSource MyItemSource { get; set; }
public MyDataSourceMessage(EventDataSource myItemSource)
{
MyItemSource = myItemSource;
}
}
// Main ViewModel
public class ViewModelOne : ViewModelBase
{
public NotifyViewModel() {}
void testMessage()
{
EventDataSource msg = new EventDataSource() { MyItemSource = "magic message!"};
Messenger.Default.Send(new MyDataSourceMessage(msg as EventDataSource));
}
}
// My nested user control
public class ViewModelTwo : ViewModelBase
{
public NotifyViewModel()
{
Messenger.Default.Register<MyDataSourceMessage>(this, (action) => ReceiveMessage(action));
}
private ObservableCollection<string> myProperty = new ObservableCollection<string>();
public ObservableCollection<string> MyProperty
{
get { return myProperty; }
set
{
myProperty: = value;
RaisePropertyChanged(() => MyProperty);
}
}
void ReceiveMessage(MyDataSourceMessage action)
{
// do something with the data
MyProperty.Add(action.DGItemSource.ItemSource);
}
}
Ok let me try to shed some light on this. First of all, change notification is not meant to pass information between view models, it is meant to notify the view itself that the a property of the view model has changed.
There are a few methods for view models to issue change notifications to views:
INotifyPropertyChanged interface
INotifyCollectionChanged interface
A custom event with the name of the property suffixed with Changed (e.g. an event called MyPropChanged for a property called MyProp)
Having said all that, it is still possible for one view model to subscribe to the events generated by the above methods, and if you really need to, you may of course.
EDIT:
Check this link for a description on item number 3 above.

A concise way to define properties for MVVM data binding in C# WPF

Is there a concise way to define properties in a ViewModel for data binding in C# WPF? The following property definition is very verbose, especially when there are lots of properties:
private bool mSomeProperty;
public bool SomeProperty
{
get { return this.mSomeProperty; }
set
{
if (value != this.mSomeProperty)
{
this.mSomeProperty = value;
OnPropertyChanged(new PropertyChangedEventArgs("SomeProperty"));
}
}
}
In C#, I like to make a base class and put some helper methods on it. Then I make my ViewModels descend from it. This is from memory, but it's something like this:
public class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T backingField, T newValue,
string propertyName)
{
if (Equals(backingField, newValue))
return;
backingField = newValue;
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
And, in usage:
public class MyClass : Observable
{
private bool m_someProperty;
public bool SomeProperty
{
get { return m_someProperty; }
set { SetProperty(ref m_someProperty, value, "SomeProperty"); }
}
}
You might find some ideas here:
Implementing and usage of INotifyPropertyChanged
https://github.com/jbe2277/waf/wiki/Implementing-and-usage-of-INotifyPropertyChanged
You could always use DependencyProperties and propdp your heart out...
If you're using the Delphi Prism language (Pascal-based .NET language), you just add the notify keyword, and the compiler automatically implements INotifyPropertyChanged and writes all the boilerplate code for you:
property SomeProperty: bool; notify;
I think you won't get the property declaration much smaller than that, at least not with "normal" ways. You could use an castle interceptor to take care of the event raisal.
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChanged;
public void FirePropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(propertyName);
}
public virtual string MyProperty { get; set; }
public virtual string MyProperty2 { get; set; }
}
The interceptor would need to fire the property changed event:
public class PropertyChangedInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
//Pseudo code
if (invocation.Method is Property Setter)
{
Call FirePropertyChanged of invocation.InvocationTarget ;
}
}
}
You just need to make sure that your view model's properties are virtual and your view model is always an proxy and not the view model itself.
Best Regards
You can consider PostSharp which will allow you to decorate the property (which you could then redefine as an auto property) and inject into the setter the line that fires the NotifyPropertyChanged event. An added benefit is you get rid of the string that is the same as the property name.
It's not ideal because you need that tool and it won't be immediately obvious if you are looking at the code later, but it definitely makes it neater.
I was hoping MSFT would add such an attribute to C# 4 but I guess we'll have to wait for C#5 if at all.
This answer is very similar to Joe White's answer above, but the code is a little terser, and possibly a little more maintainable since the names of the properties don't have to be passed as a quoted string.
By using the CallerMemberName functionality, there are two ways to do this. One is to create an abstract class that contains the IPropertyNotifyChanged code and the other approach is to include the IPropertyNotifyChanged support code in the actual class. I generally use both depending on the context.
Here is what the results look like when using an abstract class:
public abstract class PropertyNotifier: INotifyPropertyChanged
{
#region INotifyPropertyChanged support code
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T storage, T value,
[CallerMemberName] String propertyName = null)
{
bool result = false;
if (!Object.Equals(storage, value))
{
storage = value;
OnPropertyChanged(propertyName);
result = true;
}
return result;
}
#endregion INotifyPropertyChanged support code
}
And in usage:
public interface IPerson
{
String FirstName { get; set }
String LastName { get; set }
}
public class Person : PropertyNotifier, IPerson
{
private string _FirstName;
private string _LastName;
public String FirstName
{
get => _FirstName;
set => SetProperty(ref _FirstName, value);
}
public String LastName
{
get => _LastName;
set => SetProperty(ref _LastName, value);
}
}
Hope this helps!

Categories