Bindable ReadOnly List - c#

I have a Ticket class containing a collection of TicketLine objects. I want to bind this collection to a DataGridView but without letting anything but the Ticket class add and remove items from it.
So far I have used a BindingList and implementet INotifyPropertyChanged in TicketLine, but this exposes Add and Remove methods on the list itself.
How do I this collection to a DataGridView without exposing other Add/Remove methods than those in the Ticket class?

What I can think of is to implement IBindingList interface using decorator pattern by delegating all calls to wrapped read/write BindingList. The only exceptions are:
AllowEdit/Add/Remove members which return false.
Add/Remove methods which throw InvalidOperationException (or NotSupportedException)
That's how read-only aspect is assured.
Once you create this read-only wrapper, you pass it to DataGridView. Provided that it respects the contract (I assume it does :)) it should disallow modifying the underlying list.
Once I faced the same problem and the solution was too troublesome to implement. Mainly because of loss of generics and the amount of work it required. I hope it helps, though.

You could hide the list and only expose an IEnumerable property:
public class Ticket : INotifyPropertyChanged
{
private List<TicketLine> ticketLines;
public IEnumerable<TicketLine> TicketLines
{
get { return ticketLines.AsReadOnly(); }
}
public void Add(TicketLine ticketLine)
{
ticketLines.Add(ticketLine);
OnPropertyChanged("TicketLines");
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Related

Should I have one ViewModel class implementing INotifyPropertyChanged for each Model class?

Basically, I have a model with various classes that load my data in different collections, including collections of collections (e.g. Cart has a collection of Bundle, which has collections of Product) . If my understanding of MVVM is correct, I would need to make one ViewModel class that implements INotifyPropertyChanged (directly or through inheritance from a base class) for each of my model classes. Though I must admit it seems to me that it implies a lot of duplicated code, just, having the ViewModel associate each property of the model class with a OnPropertyChanged call.
Just like is shown in this article for instance.
Am I getting this right ?
I'm currently trying to understand the basics of MVVM, so I try to fully implement it in my programs without any additional framework (MVVM Light and others).
You don't need a ViewModel-Class for each Model-Class you have. Your Model-Classes should implement the INotifyPropertyChanged-Interface.
You need ViewModels to interact with your Views. In the ViewModels you can have instances of your Model-Classes.
Btw.: To avoid writing the code for INotifyPropertyChanged every time in each ViewModel and Model i've created an abstract base class where everything is derived from. This class looks like:
public abstract class NotifyBase : INotifyPropertyChanged
{
private readonly Dictionary<string, object> mapping;
protected NotifyBase()
{
mapping = new Dictionary<string, object>();
}
protected void Set<T>(T value, [CallerMemberName] string propertyName = "")
{
mapping[propertyName] = value;
OnPropertyChanged(propertyName);
}
protected T Get<T>([CallerMemberName] string propertyName = "")
{
if(mapping.ContainsKey(propertyName))
return (T)mapping[propertyName];
return default(T);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemeberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if(handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Model List<T> and ViewModel ObservableCollection<T> Duplicate Data?

What is the best practice for having a List<T> in the business layer that needs to be displayed on a UI? I currently use databinding with an ObservableCollection<T> in the viewmodel that duplicates the data of the List<T>. The obvious problem of this is when the List<T> is modified in the business layer the ObservableCollection<T> needs to be recreated so the changes of the List<T> are reflected in the UI. This can't be the best way.
I also will not accept using an ObservableCollection<T> in the business layer as an answer.
Thanks!
If you insist on having List<T> and separate events notifying about list modification, then duplication is the only sensible way.
If you have ListChanged event with no details on what was actually changed, you can avoid duplication and just wrap the list in a proxy collection implementing INotifyCollectionChanged interface which will fire appropriate CollectionChanged events in NotifyCollectionChangedAction.Reset mode.
If you have granular ItemChanged, ItemAdded etc. events, then you're effectively duplicating ObservableCollection<T> functionality. In this case, you can wrap your application in a proxy collection implementing INotifyCollectionChanged interface, but which understands your architecture and translates events into appropriate NotifyCollectionChangedAction.
Having ObservableCollection<T> in business layer isn't a bad idea at all. It is a specialized collection which provides common interface to notify about item changes, not some class designed specifically for WinForms or WPF or whatever.
You can implement the INotifyCollectionChanged interface, but if you want to use it in a way that you can hold on your implementation the collection in case implementing a class of your own also holding an implementation of IEnumerable will do a lot of the work for you a for instance is what follows, this is the base class i use for holding all the collections that will be updated, on this implementation there is also an ordering consideration in the variable _ordering:
public abstract class BaseINotifyCollectionChanged<T, K> : INotifyCollectionChanged, IEnumerable<T>
{
Func<T, K> _ordering;
bool _ascending;
public BaseINotifyCollectionChanged()
{
}
public BaseINotifyCollectionChanged(Func<T, K> ordering, bool ascending = true)
{
_ordering = ordering;
_ascending = ascending;
OnCollectionChanged();
}
protected abstract IList<T> GetCollection();
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected void OnCollectionChanged()
{
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void RaiseCollectionChanged()
{
OnCollectionChanged();
}
public IEnumerator<T> GetEnumerator()
{
return _ordering == null ? GetCollection().GetEnumerator() : _ascending ? GetCollection().OrderBy<T, K>(_ordering).GetEnumerator() :
GetCollection().OrderByDescending<T, K>(_ordering).GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _ordering == null ? GetCollection().GetEnumerator() : _ascending ? GetCollection().OrderBy<T, K>(_ordering).GetEnumerator() :
GetCollection().OrderByDescending<T, K>(_ordering).GetEnumerator();
}
}
}
When you have this implementation you can use as you wish and through out all the collections you need on your app, working on some dry for yourself here is one example of the use you can give to this abstract class:
public class Categories : BaseINotifyCollectionChanged<Category, string>
{
long _onCategoryRoot;
public void SetOnCategoryRoot(long categoryId)
{
_onCategoryRoot = categoryId;
RaiseCollectionChanged();
}
protected override IList<Category> GetCollection()
{
Category category = new Category();
return _onRoot ? category.GetRootCategories() : category.GetSubCategoriesOnRoot(_onCategoryRoot);
}
}
When you set a _onCategoryRoot in the class the collection you will be displaying will be updated via the RaiseCollectionChanged() method, so you need to add in your viewmodel a property with the class Categories and set the binding in the XAML.

ObservableCollection along with a repository

I am having trouble with grasping the concept of a ObservableCollection inside MVVM. For start I would like to point out that I am doing this in a Windows 8/Metro App, not WPF or Silverlight.
According to microsoft documentation, this collection has the following usefulness:
"Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed." From what I understand this helps you a lot when binding is involved. On the net I found a lot of simple examples, by creating a ObservableCollection on runtime and then working on it, but I didn't find out what is the proper way of using this collection with a repository.
Let' say I have the following repository interface that is an implementation for a ORM database backend, or a raw ADO.NET implementation
public interface IRepository<T>
{
ObservableCollection<T> GetAll();
void Create();
void Update();
void Delete();
T GetByKey(object key);
}
and a simple ViewModel that use the repository as a model
public class ViewModel
{
private ObservableCollection<Dummy> _obsListDummy;
private RelayCommand _addCommand,_deleteCommand,_updateCommand;
private IRepository<Dummy> _repositoryDummy;
public class ViewModel()
{
_repositoryDummy=Factory.GetRepository<Dummy>();
}
public ObservableCollection<Dummy> ObsListDummy
{
get
{
return _repositoryDummy.GetAll();
}
}
public RelayCommand AddCommand
{
get
{
if (_addCommand == null)
{
_addCommand = new RelayCommand(p => DoAdd();
//DoAdd method shows a popup for input dummy and then closes;
);
}
return _myCommand;
}
}
........
}
My view would be a simple XAML with a grid, also Dummy object has INotifyPropertyChanged implemented.
Right now with this implementation after adding or updating or deleting, the ObservableCollection isn't refreshing, I know I could have put IEnumerable instead, but I dont'see an elegant solution of how would make repository to sync with the ObservableCollection that is in the model, other than subscrbing to CollectionChanged and there you treat all the states, but to it seems that I would repeat myself along with the logic that I do in the repository. And to make matters even worse, let's say I would like to get some push notification from my repository, towards the ObservableCollection.
I hope I was understand about my problem.
Thanks in advance.
You should implement INotifyPropertyChanged on your ViewModel and your ObsListDummy property should inform the ViewModel about changes applied to the collection. So it should look like this:
public class ViewModel: INotifyPropertyChanged
{
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
private ObservableCollection<Dummy> _dummyCollection;
public ObservableCollection<Dummy> DummyCollection
{
get { return _dummyCollection; }
set
{
// Set the value and then inform the ViewModel about change with OnPropertyChanged
_dummyCollection = value;
OnPropertyChanged("DummyCollection");
}
}
}
This whole INotifyPropertyChanged interface and implementation includes some dirty work like declaring event and creating a helper method to raise the event so I would suggest you to use some libraries for that like MVVM Light.
You should use a member of type ObservableCollection to store your Dummy ViewModels. In your Initialize method you read the dummies from the repository, create Dummy ViewModels and put those in the ObservableCollection. Now your view will get updated, when you use Binding to ObsListDummy (and add / remove from that collection, also note that Binding only works with public properties).
Right now, you just have a new ObservableCollection on each read, no events involved, so your View will never know about a change.
Further your ViewModel shall implement INotifyPropertyChanged.

Monitoring other classes with one class

I have one class named DataClass. This Class is responsible to saving information in database, and In this class there are some methods for saving and reading from database, Except this class I have other classes called HTMLEditor, QueryBuilder , EmailSending, InforDetails.
I need to listen to other classes by my data class , any time their information are changed then my Dataclass would be notified to save these information.
I know there is one design pattern is called observer design pattern , with this design pattern, other classes(observers) are listening to one class(subject),any time the status of subject is changed then other observers are notified.
What should I do for this problem? Is there any design pattern for this situation?
I think the interface you seek if INotifyPropertyChanged.
Microsoft Documentation: INotifyPropertyChanged
The implementation is very simple.
In every property set you do:
public bool MyProperty
{
get { return myField; }
set
{
if (myField != value)
{
myField= value;
NotifyPropertyChanged();
}
}
}
And the method and events:
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Every observer only need to register to that event and they get a feedback when a property changed and which one did.
As extra, some control like PropertyGrid automatically register themselves when you feed them an object that implement that interface.
The INotifyPropertyChanged interface could be what you're after:
See here: http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx
You basically subscribe to an event within your other classes and those classes raise the event when a property changes.
Also, this SO question has an answer that is quite cool: Automatically INotifyPropertyChanged

do something before collection changes in observablecollection in wpf

I am not sure what i am trying to achieve is actually achievable or not.
I have an observablecollection with me and its collectionchanged event is already been handled. What i want to do is I want to make some changes in the existing list of objects in the observablecollection just before the collectionchanged event of the observablecollection gets fired. In other words i want to do something to the existing list of objects in the observablecollection before anyone adds or removes any object from the observablecollection. Something like handling the collectionchanging event but unfortunately there is not such event in observablecollection. I hope i have been clear enough.
Since you need to take action before the user changes the collection, I believe your CollectionChangedEvent is happening too late (the collection has already changed).
Instead, consider creating your own collection class which derives from ObservableCollection and then override the Add(), Insert(), and Remove() methods to do your additional processing before calling the base class implementation. You should be able to find examples of that on the web.
Here is some sample code to get you started. It derives from Collection:
public class MyCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
public MyCollection(Collection<T> list)
: base(list)
{
}
public MyCollection()
: base()
{
}
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected void NotifyChanged(NotifyCollectionChangedEventArgs args)
{
NotifyCollectionChangedEventHandler handler = CollectionChanged;
if (handler != null)
{
handler(this, args);
}
}
#endregion
public new void Add(T item)
{
// Do some additional processing here!
base.Add(item);
this.NotifyChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, base.Count-1));
this.OnPropertyChanged("Count");
}
}
You have been clear enough and the simple answer is: There is no such event and it is not possible.
The only solution I can think of is to derive from ObservableCollection<T> and implement that functionality yourself, i.e. in your implementation of Add you would first raise the CollectionChanging event and then call the Add method of the base class. You would do the same for all other relevant methods.
Having said all that, I am not really sure, this is the correct way to do it. Can you provide a reason why you would need this functionality?
Actually, the collection changed event in ObservableCollection is fired when (among other things) :
You add an item to the ObservableCollection.
You remove an item from the ObservableCollection.
You clear the ObservableCollection.
When I say "you", that means that if CollectionChanged Event occurs that means that "YOU" (understand : something in you application) has added, removed or cleared the list.
That being said, I guess you just have to find where those actions take place and put your code here...
You could create your own implementation of INotifyCollectionChanged that wraps the collection, listens to the event, changes the collection as appropriate and then sends the event along.
But when you change the collection, another event is raised, so you would have to make sure you're handling those events properly, probably by swallowing them
public class WantDoSomethingBeforeChangeGuy
{
internal WantDoSomethingBeforeChangeGuy()
{
Members = new ImplMembers(this);
}
public ImplMembers Members { get; }
private class ImplMembers : ObservableCollection<Artist>
{
private readonly WantDoSomethingBeforeChangeGuy _owner;
public ImplMembers(WantDoSomethingBeforeChangeGuy owner)
{
_owner = owner;
}
protected override void ClearItems()
{
foreach (var item in this)
{
item.DoSomething(_owner);
}
base.ClearItems(); }
}
}

Categories

Resources