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.
Related
i have a list property like that
protected IList<System.Windows.Media.Color> colors;
public IList<System.Windows.Media.Color> Colors
{
get { return colors; }
set { colors = value; }
}
and i have a function
protected void updateBuffers()
which needs to be called each time a property user change the property, for example
Colors.Add(...)
...
Colors.Clear(...)
is there an elegent simple way to do that?
You could create a new object that created additional properties, using the List as a base class:
public class CustomList<T> : List<T>
{
public new void Add(T item) {
base.Add(item);
this.UpdateBuffers();
}
}
The 'new' keyword is required to completely overwrite the existing implementation of Add, which isn't marked as virtual in the base class.
Thanks to Hans Passant and LarsTech for their feedback in the comments.
You cannot with a plain list.
You can however create your own class implementing IList<T> or inherit from Collection<T>.
Or you can use an ObservableCollection<T> or any built-in class implementing INotifyCollectionChanged
see MSDN for documentation.
Moreover, ObservableCollection will allow you to use your collection in bindings if you are using them for populating UI in WPF or Windows 8 applications.
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));
}
}
}
This is the weirdest thing I've ever faced. As in Windows 8 MS removed filtering and sorting from CollectionViewSource, I've had to build my own, called CollectionView<T>. CollectionView has a View property of type IObservableCollection<T>, a custom interface I made just to keep things abstracted. Its definition is pretty simple
public interface IObservableCollection<T> : IReadOnlyList<T>, INotifyCollectionChanged
{
}
Then, I have my internal class which implements this interface:
internal class FilteredSortedCollection<T> : IObservableCollection<T>
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
public void RaiseCollectionChanged(NotifyCollectionChangedEventArgs args)
{
var copy = CollectionChanged;
if (copy != null)
copy(this, args);
}
public Func<IEnumerator<T>> RequestEnumerator { get; set; }
public Func<int> RequestCount { get; set; }
public Func<int, T> RequestItem { get; set; }
public IEnumerator<T> GetEnumerator()
{
return RequestEnumerator();
}
public int Count { get { return RequestCount(); } }
public T this[int index] { get { return RequestItem(index); } }
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Things worked until here. CollectionView filters and orders properly, and the View works as expected. Except when I bind it to a ListView.ItemsSource property it just behaves as if it didn't implemented INotifyCollectionChanged. Nobody listens to the CollectionChanged event (checked with the debugger) and the UI doesn't update with new elements added. But if I add some items and then set the ItemsSource property, the UI updates. Just as if it were a normal, non-observable list.
Does anybody know what can be happening here? I've tried deleting the IObservableCollection interface, so FilteredSortedCollection just implemented IReadOnlyList<T> and INotifyCollectionChanged directly, but it didn't worked.
Your collection needs to implement IList. I just ran into the same issue where I had implemented IList which worked great in my Windows Phone app, but when I attempted to use the view model for Windows 8 application it wasn't honoring the changed events.
I added the implementation of IList to my class and now everything works as expected
I have a composite property called Items of type List to an order class. On the GUI the user fills out some fields like Name, Description, Price, Quantity, etc... and then clicks the Add Item button which of course adds the item to the order's list of items. What I'd like to do is create a method that checks the item's IsComplete property which does a check to ensure the required properties are set so that way someone can't just call order.Items.Add(item) if it isn't complete. If it's not I'd like an exception to be thrown if the item's IsComplete property returns false... What would be an easy way to go about this?
This can be achieved by sub-classing List<T> into a derived class, and then overriding the Add method, like so.
public class MyItemCollection : List<MyItem>
{
public override void Add(MyItem item)
{
if (item.IsComplete)
{
base.Add(item);
}
else
{
throw new InvalidOperationException("Unable to add an incomplete item");
}
}
}
Your order class would then have the property MyItemCollection rather than List<T>, like so:
public class Order
{
public MyItemCollection Items { get; set; }
}
You can also use ObservableCollection<T>: http://msdn.microsoft.com/en-us/library/ms668604.aspx
It implements INotifyCollectionChanged: http://msdn.microsoft.com/en-us/library/System.Collections.Specialized.INotifyCollectionChanged.aspx
Since the method Add(T) is not virtual you can't override, it.
ObservableCollection allow to throw an event when an element was added but not to undo this add.
You can implement the interface IList<T> with a List<T> storred internaly and add the desired verification in the method Add(T item) before calling the _list.Add(item) like in the exemple below :
public class MyItemCollection : IList<MyItem>
{
private List<MyItem> _list;
public MyItemCollection()
{
_list = new List<MyItem>();
}
public void Add(MyItem item)
{
if (item.IsComplete)
{
_list.Add(item);
}
else
{
throw new InvalidOperationException("Unable to add an incomplete item");
}
}
//Then you have to implement all the IList interface members...
}
The only problem with this solution is that it require to write a lot of boilerplate code.
If only one class is responsible of the manipulation of your List, you can also decide to implement a method AddToMyItemCollection(MyItem item) in the responsible class. It is even a good practive as it's respect the GRASP pattern protected variation (Instance.getC() is preferable to Instance.getA().getB().getC())
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(); }
}
}