This problem has been keeping me busy for half a day now and I start to lose my sanity:
I'm using Items for UI Logic stuff. There are "parent" Items, that can contain ObservableCollections of other Items. (Both inherit from the same ItemBase, picture nodes with nodes, sort of recursive)
For not having to recreate Observer logic on each "parent" item class, I wanted to add the functionality to the common baseclass, called ItemBase. The idea is, that the parent can just register its ObservableCollections and the baseclass takes care of the event routing and all. The problem is, that I can't seem to find a way to save a reference to these ObservableCollections (of different types with the same baseclass) for the way that generics work.
Here's the code:
public abstract class ItemBase : ViewModelBase
{
private List<ObservableItemCollection<ItemBase>> _trackedChildItemsList = new List<ObservableItemCollection<ItemBase>>();
public event EventHandler<ItemPropertyChangedEventArgs> ChildItemPropertyChanged;
public event EventHandler<IsDirtyChangedEventArgs> ChildItemIsDirtyChanged;
public override bool IsDirty
{
get { return base.IsDirty || AreAnyChildItemsDirty; }
set { base.IsDirty = value; }
}
private bool AreAnyChildItemsDirty
{
get
{
return _trackedChildItemsList.Any(i => i.Any(l => l.IsDirty));
}
}
protected void RegisterItemCollection<T>(ObservableItemCollection<T> collection)
where T : ItemBase
{
_trackedChildItemsList.Add(collection); // intellisense underlines 'collection'; cannot convert from 'ObservableItemCollection<T>' to ObservableItemCollection<ItemBase>:
collection.ItemPropertyChanged += Collection_ItemPropertyChanged;
collection.ItemIsDirtyChanged += Collection_ItemIsDirtyChanged;
}
public override void Dispose()
{
foreach (ObservableItemCollection<ItemBase> collection in _trackedChildItemsList)
{
collection.ItemPropertyChanged -= Collection_ItemPropertyChanged;
collection.ItemIsDirtyChanged -= Collection_ItemIsDirtyChanged;
}
base.Dispose();
}
private void Collection_ItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e)
{
OnChildItemPropertyChanged(e);
}
protected virtual void OnChildItemPropertyChanged(ItemPropertyChangedEventArgs e)
{
ChildItemPropertyChanged?.Invoke(this, e);
}
private void Collection_ItemIsDirtyChanged(object sender, IsDirtyChangedEventArgs e)
{
OnItemIsDirtyChanged(e);
}
protected virtual void OnItemIsDirtyChanged(IsDirtyChangedEventArgs e)
{
ChildItemIsDirtyChanged?.Invoke(this, e);
}
}
As you can see, I'm using a derived, custom type of the ObservableCollection, namely ObservableItemCollection, which takes care of the ItemPropertyChanged and ItemIsDirtyChanged invokation for the collection itself. This allows one to catch those events from the outside.
Now, instead of having that 'catching the events' logic in each parent item itself (duplicated), I wanted it to be in a centralized spot, namely the baseclass.
Now the main problem is, that upon registering the ObservableItemCollections, I cannot possibly keep a reference to them since there's no common base. ObservableItemCollection<CustomItem> does not inherit from ObservableItemCollection<ItemBase>, since its a collection. I tried solving the whole thing with generics, however, the above is as far as I got. It fails to compile where i wrote the 'cannot convert from 'ObservableItemCollection' to ObservableItemCollection' comment.
I understand why it fails to compile, however, I can't seem to find a workaround/working solution.
I absolutely need a direct reference to the collections (casted as my custom type ObservableItemCollection), else the whole thingy won't work. You can see in the code that I'm accessing both the events of the collection itself, as well as properties of the ItemBase.
Either way, I can't seem to find a common base for the collections. I tried using dynamics and reflection based casting, Interfaces, a Custom generic ParentItem type, neither worked (i might have overlooked something) and even if it did, it would be rather ugly.
Is it really not possible to achieve what I want with a limited amount of hacking things together? I can't believe that I didn't find a good solution after all the time I've invested in this.
Additional info:
In the parent item i have the following ObservableCollections:
public ObservableItemCollection<SomeItem1> Collection1 { get; set; } = new ObservableItemCollection<SomeItem1>();
public ObservableItemCollection<SomeItem2> Collection2 { get; set; } = new ObservableItemCollection<SomeItem2>();
Where both item types inherit from ItemBase. Then i call the base method RegisterItemCollection in the parent item constructor like so:
RegisterItemCollection(Collection1);
RegisterItemCollection(Collection2);
WPF collection controls have the same problem: How do you define a property which can hold a reference to any kind of generic collection? Answer: Make the property a reference to a non-generic interface that all the collections implement. This is a very general question, and it's the reason why non-generic System.Collections.IEnumerable and System.Collections.IList are still in heavy use throughout the .NET framework, all these years after generics were introduced.
Nothing you're doing in RegisterItemCollection(), IsDirty, or Dispose() needs to care about the type of item in the collection. So take whatever methods and properties you need that code to interact with, and put it all in a non-generic interface or base class. Your base class is already generic (ObservableCollection<T>, I presume), so use an interface.
public interface IObservableItemCollection
{
event EventHandler<ItemPropertyChangedEventArgs> ItemPropertyChanged;
event EventHandler<IsDirtyChangedEventArgs> ItemIsDirtyChanged;
bool IsDirty { get; }
}
public interface IDirtyable
{
// I'm pretty sure you'll want this event here, and I think you'll want your collection to
// implement IDirtyable too.
//event EventHandler<IsDirtyChangedEventArgs> IsDirtyChanged;
bool IsDirty { get; }
}
public class ObservableItemCollection<T>
: ObservableCollection<T>, IObservableItemCollection
where T : IDirtyable
{
public bool IsDirty => this.Any(item => item.IsDirty);
public event EventHandler<ItemPropertyChangedEventArgs> ItemPropertyChanged;
public event EventHandler<IsDirtyChangedEventArgs> ItemIsDirtyChanged;
}
public class ViewModelBase : IDisposable, IDirtyable
{
public virtual bool IsDirty => true;
public virtual void Dispose()
{
}
}
public class ItemBase : ViewModelBase
{
private List<IObservableItemCollection> _trackedChildItemsList = new List<IObservableItemCollection>();
public override bool IsDirty
{
get
{
return base.IsDirty || _trackedChildItemsList.Any(coll => coll.IsDirty);
}
}
protected void RegisterItemCollection<T>(ObservableItemCollection<T> collection)
where T : ItemBase
{
_trackedChildItemsList.Add(collection);
collection.ItemPropertyChanged += Collection_ItemPropertyChanged;
collection.ItemIsDirtyChanged += Collection_ItemIsDirtyChanged;
}
public override void Dispose()
{
foreach (IObservableItemCollection collection in _trackedChildItemsList)
{
collection.ItemPropertyChanged -= Collection_ItemPropertyChanged;
collection.ItemIsDirtyChanged -= Collection_ItemIsDirtyChanged;
}
base.Dispose();
}
private void Collection_ItemIsDirtyChanged(object sender, IsDirtyChangedEventArgs e)
{
}
private void Collection_ItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e)
{
}
}
public class ItemPropertyChangedEventArgs : EventArgs
{
}
public class IsDirtyChangedEventArgs : EventArgs
{
}
You could also do this by making _trackedChildItemsList a collection of IDisposable, and have the collections clear their own event handlers, but a class clearing its own event handlers is pretty gruesome. Shun reflection when conventional OOP can be used to do the job in a readable and maintainable way. And you'd still have to think of something for IsDirty.
You can not do this since if you could you could do something like
class A {}
class B : A { }
class C : A { }
var list = new List<List<A>>();
var sublist_b = new List<B>();
sublist_b.Add(new B());
list.Add(sublist_b);
var sublist = list.Single();
sublist.Add(new C()); // <- now a List<B> contains an object that ist not if type B or derived B
I would suggest that you only use ObservableItemCollection<ItemBase> to hold your objects.
Related
I've got a class with a list of properties, and the properties themselves have a list of properties. I need the "grandchild"- property to be able to request data from it's parent's parent (sorry for the confusing terms - there's no inheritance here). To clarify:
class ClassA
{
list<ClassB> Children
var SomeOtherProperty
}
class ClassB
{
list<ClassC> Grandchildren
}
class ClassC
{
var GetSomeOtherProperty()
{
...
}
}
The data may change during run time so I can't just pass it once and be done with it, I gotta be able to get it dynamically.
I could always pass the parent all the way down to the grandchild in the ctors, but I was taught it's a bad practice, so I'd rather avoid it.
I've been reading about passing data back/downwards via events using mutable EventArguments- I'm wondering if that'd be an OK solution for what I've got (I'll need to do it twice each time- sort of chase the tail of the first eventarg). Are there any pitfalls I need to be aware of?
Do I have any other options for this situation?
Thanks!
If possible, you can use the design pattern composite. First, create a base class 'note' with a list of notes for the children and a virtual method 'DoAction'. Then derive all other classes from this class and override the method with own implementation.
Now you can build up a tree of notes and do a traversal on all of it childs. For each of them, call 'DoAction'.
Hope you get the idea...
Since classes A, B & C are not liked via inheritance, therefore, I would like to avoid any direct wiring between these classes as it will make the design a little complicated as the system would evolve.
Moreover, since class B has nothing to do with the data, I dont want it to get effected by it.
Therefore, I would like to take out the communicate via a separate route to keep the classes loosely coupled -
here is a sample code to explain my idea -
public interface IPublisher
{
event EventHandler OperationOccurred;
}
class ClassA : IPublisher
{
List<ClassB> Children;
event EventHandler OperationOccurred;
public ClassA()
{
BroadCaster.Instance.RegisterPublisher(this);
}
protected virtual void OnOperationOccurred()
{
if (OperationOccurred != null)
OperationOccurred(this, new EventArgs());
}
}
class ClassB
{
List<ClassC> Grandchildren;
}
class ClassC
{
public ClassC()
{
BroadCaster.Instance.BroadCastNotificaiton += Instance_OperationOccurred;
}
void Instance_OperationOccurred(object sender, EventArgs e)
{
throw new NotImplementedException();
}
}
/// <summary>
/// A singleton class ... Like a single braodcast tower just one in the city
/// </summary>
public sealed class BroadCaster
{
public static BroadCaster Instance { get; private set; }
//Static constructor
static BroadCaster()
{
Instance = new BroadCaster();
}
// private constructor
private BroadCaster(){}
public event EventHandler BroadCastNotificaiton;
public void RegisterPublisher(IPublisher publisher)
{
publisher.OperationOccurred += Publisher_OperationOccurred;
}
void Publisher_OperationOccurred(object sender, EventArgs e)
{
if (this.BroadCastNotificaiton != null)
this.BroadCastNotificaiton(sender, e);
}
}
I have implemented broadcaster a singleton object, but all I want to convey is a single point of communication.
Hope it helps.
Quite some time ago, I noticed that the Windows Forms editor of Visual Studio does not support events which contain generic type parameters. For example, an event like
public event EventHandler<ListEventArgs<int>> MyStrangeEvent { add { ... } remove { ... } }
where
public class ListEventArgs<T> : EventArgs { List<T> args; }
does not even show up in the event list in the property manager of Visual Studio. Now, this is a somewhat artificial example that could easily be modified to work in Visual Studio by rewriting the classes and their events. However, I am currently working on a project where I cannot change some classes for compatibility reasons. The only thing I can do is to change the events of my user control. The events of this control currently look like this:
public event EventHandler<Plane<GDISurface>.DrawingErrorEventArgs> DrawingError { add { _Plane.DrawingError += value; } remove { _Plane.DrawingError -= value; } }
Note that the underlying Plane class (represented by the _Plane instance which is a protected field) cannot be changed. Its DrawingError event and its EventArgs type are declared in the Plane class like this:
public class Plane<T> where T : ISurface
{
...
public event EventHandler<DrawingErrorEventArgs> DrawingError = null;
...
public class DrawingErrorEventArgs : EventArgs { ... /* Uses T */ ... }
}
Of course, the Windows Forms editor of Visual Studio does not show any of the events of my user control. I have been looking for a number of workarounds to get them shown again, but have not been able to find a workaround that actually works. Here are some things that I tried:
Created a MyPlane class which inherits from Plane and used that instead: public event EventHandler<MyPlane.DrawingErrorEventArgs> DrawingError .... For reasons unknown to me, the events still don't show up in the editor. Perhaps this is due to the parameters of the event, some of which still are generic. Find a minimal working example below.
Created a helper class which defines implicit conversion operators between EventHandler<Plane<GDISurface>.DrawingErrorEventArgs> and EventHandler<GDIPlane.DrawingErrorEventArgs> where GDIPlane is just a dummy class which inherits from Plane<GDISurface>. This does work to some extent, but duplicates event calls since the conversion creates new event handlers which are passed down to _Plane which cannot be removed/unregistered properly.
Tried to inherit from EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>, which obviously does not work since EventHandler<T> is sealed.
Are there any other ways to make my events visible again in the Windows Forms editor?
Best regards
Andreas
EDIT: Minimal working example for 1:
public interface ISurface { }
public class GDISurface : ISurface { }
public class Plane<T> where T : ISurface
{
public event EventHandler<DrawingErrorEventArgs> DrawingError = null;
public class DrawingErrorEventArgs : EventArgs { T stuff; }
}
public class TestControl : UserControl
{
public class GDIPlane : Plane<GDISurface> { }
GDIPlane _Plane = null;
public event EventHandler<GDIPlane.DrawingErrorEventArgs> DrawingError { add { _Plane.DrawingError += value; } remove { _Plane.DrawingError -= value; } }
}
DrawingError does not show up in the list of events in the property manager when clicking on a TestControl instance.
EDIT2: This is the original problem (without any workarounds) where the DrawingError event does of TestControl does not show up either:
public interface ISurface { }
public class GDISurface : ISurface { }
public class Plane<T> where T : ISurface
{
public event EventHandler<DrawingErrorEventArgs> DrawingError = null;
public class DrawingErrorEventArgs : EventArgs { T stuff; }
}
public class TestControl : UserControl
{
Plane<GDISurface> _Plane = null;
public event EventHandler<Plane<GDISurface>.DrawingErrorEventArgs> DrawingError { add { _Plane.DrawingError += value; } remove { _Plane.DrawingError -= value; } }
}
This is behavior specific to Visual Studio, and the cause is rooted in the fact that EventHandler<> does not specify covariance on its 'TEventArgs' (it would impose seemingly silly restrictions) and the tools do not perform enough introspection of your code to suss out an appropriate type (even though you've left a trail of type data in constructing the control.) Thus, it seems as though VS does not support generic event properties. You may consider filing a feature request on Microsoft Connect, I wouldn't suggest filing it as a bug as they may label it "by design" and close it.
As a general rule, if you need generic type parameters on your events and you need design time support for them (which are different implementation concerns), you're looking at wrapping them in a presentation-specific facade (e.g. "extra layer of code to facilitate design-time needs".)
Personally, I would reduce the generic typing you have in play now, it seems a bit excessive and if you don't understand covariance/contravariance in generic types it might put you in a tight spot at some point, such as now.
However, to work around your problem:
Consider using a custom event args class which could transport data in a non-generic property, and also use a non-generic EventHandler event/property. Understanding the 'type' of the event is then shifted away from generic type parameters and made the responsibility of your non-generic event args instead. If the 'class' of the event args is insufficient, you can add a property to convey the event type (or data type) so that receiving code can properly interpret it (assuming, of course, that it does not already know by some other means.):
public class DataEventArgs : EventArgs
{
//public string EventTypeOrPurpose { get; set; }
public object Data { get; set; }
}
This is most often only used to ferry data through an event chain, and it is usually implemented as follows:
public class DataEventArgs<T> : EventArgs
{
public T Data { get; set; }
}
Unfortunately, this also has a covariance problem, to resolve it you would actually want something more like this:
public interface IDataArgs<out T>
{
T Data { get; }
}
public class DataEventArgs<T> : EventArgs, IDataArgs<T>
{
public DataEventArgs<T>(T data)
{
_data = data;
}
private T _data;
public T Data { get { return _data; } }
}
Even so, these generic versions still don't work around Visual Studio's limitations, this is merely more proper alternative forms of what you already have shown us.
UPDATE: As requested, here is what a "purpose built facade" might look like in the most basic sense. Note that the usercontrol functions as a facade layer in this case as the eventhandler it exposes delegates to the underlying object model. There is no direct access to underlying object model from the user control (from consumer/designer perspective.)
Please note the reference tracking for event handlers is not necessary unless you dispose of these user controls throughout the lifetime of the app (it is only done to ensure proper delegate removal based on the delegate provided, which is wrapped in a closure/delegate, as you see below.)
Also worth noting I did not test-run this code beyond verifying that the designer shows DrawingError in the property grid when dropped onto a form.
namespace SampleCase3
{
public interface ISurface { }
public class GDISurface : ISurface { }
public class Plane<T> where T : ISurface
{
public event EventHandler<DrawingErrorEventArgs> DrawingError;
public class DrawingErrorEventArgs : EventArgs { T stuff; }
}
public class TestControl : UserControl
{
private Plane<GDISurface> _Plane = new Plane<GDISurface>(); // requires initialization for my own testing
public TestControl()
{
}
// i am adding this map *only* so that the removal of an event handler can be done properly
private Dictionary<EventHandler, EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>> _cleanupMap = new Dictionary<EventHandler, EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>>();
public event EventHandler DrawingError
{
add
{
var nonGenericHandler = value;
var genericHandler = (EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>)delegate(object sender, Plane<GDISurface>.DrawingErrorEventArgs e)
{
nonGenericHandler(sender, e);
};
_Plane.DrawingError += genericHandler;
_cleanupMap[nonGenericHandler] = genericHandler;
}
remove
{
var nonGenericHandler = value;
var genericHandler = default(EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>);
if (_cleanupMap.TryGetValue(nonGenericHandler, out genericHandler))
{
_Plane.DrawingError -= genericHandler;
_cleanupMap.Remove(nonGenericHandler);
}
}
}
}
}
To complement the above, here is what a non-generic event handler would now look like:
private void testControl1_DrawingError(object sender, EventArgs e)
{
var genericDrawingErrorEventArgs = e as Plane<GDISurface>.DrawingErrorEventArgs;
if (genericDrawingErrorEventArgs != null)
{
// TODO:
}
}
Note that the consumer here has to have knowledge of the type for e to perform conversion. The use of the as operator will bypass ancestry checks under the assumption that the conversion should succeed.
Something like this is as close as you're going to get. Yes it is ugly by most of our standards, but if you absolutely 'need' design-time support on top of these components and you cannot change Plane<T> (which would be more appropriate) then this, or something close to this, is the only viable workaround.
HTH
I am working on an LOB application in C# using a WinForms tabbed MDI interface. I have various forms with DataGridViews to allow the user to select an object they are interested in, which they can then view/edit in a new form.
Each of my main business objects inherit from Entity, which is defined as below:
public abstract class Entity
{
public static event Action Saved;
internal virtual void OnSaved()
{
if (Saved != null)
{
Saved();
}
}
}
I then have the objects that populate the grid (these are actually auto-generated classes from Linq-to-SQL, although I can replicate the problem with normal classes):
class Class1 : Entity
{
//Stuff
}
class Class2 : Entity
{
//Stuff
}
I want to know when an object of a given class is modified, but i don't care which instance (hence the static action) so that i can refresh the grid and perform other activities.
The problem comes when the event is fired from a derived class instance - it fires for all other derived classes too. For example:
Class1.Saved += new Action(s1);
Class2.Saved += new Action(s2);
private void TestIt()
{
Class2 o2 = new Class2();
o2.OnSaved();
}
This would fire s1 and s2, but I only want the specific one to be fired (i.e. s2). What is the best way to do this? I have quite a few classes that need this behviour and would like to avoid having to add any code to each class if possible.
Update:
Thank you for all your responses, they have been very helpful.
I have opted for a slightly different option, which I admit seems quite hacky, but works well for my purposes. This involves passing the type with the action and letting a handler filter and call relevant operations.
Entity Class:
public abstract class Entity
{
public static event Action<Type> Saved;
internal void OnSaved()
{
private Action<Type> SavedCopy = Saved;
if (SavedCopy != null)
SavedCopy(this.GetType());
}
}
Hook up handler:
Entity.Saved += new Action<Type>(Handler);
Example Handler method (this will vary from form to form):
void Handler(Type obj)
{
if (obj==typeof(Class1))
UpdateGrid();
else if (obj==typeof(Class2))
UpdateBasicInfo();
else if (obj == typeof(Class3))
DoAnotherThing();
}
Using generics could be a work around; each generic class gets a copy of the static fields.
public abstract class Entity<T>
{
public static event Action Saved = delegate { };
internal virtual void OnSaved()
{
Saved();
}
}
class Class1 : Entity<Class1>
{
//Stuff
}
class Class2 : Entity<Class2>
{
//Stuff
}
I'm not sure doing it like this is a good idea, but you could specify the type when you subscribe and when you save the data:
public abstract class Entity
{
private static Dictionary<Type, Action> Subscribers
= new Dictionary<Type, Action>();
internal virtual void OnSaved()
{
OnSaved(GetType());
}
private OnSaved(Type type)
{
Action subscribed;
Subscribers.TryGetValue(type, out subscribed);
if (subscribed != null)
subscribed();
}
public Subscribe(Type type, Action action)
{
Action subscribed;
Subscribers.TryGetValue(type, out subscribed);
Subscribers[type] = subscribed + action;
}
public Unsubscribe(Type type, Action action)
{
Action subscribed;
Subscribers.TryGetValue(type, out subscribed);
Subscribers[type] = subscribed - action;
}
}
Keep in mind that this code is not thread-safe, so if you want to use it from different threads at the same time, you need to add locking.
You will need to have an event per type, because can't determine for which type the delegate is registered when the event is defined on the base type.
public abstract class Entity
{
internal abstract void OnSaved();
}
class Class1 : Entity
{
public static event Action Saved = () => { };
internal override void OnSaved()
{
this.Saved();
}
//Stuff
}
class Class2 : Entity
{
public static event Action Saved = () => { };
internal override void OnSaved()
{
this.Saved();
}
//Stuff
}
Why does it have to be static? Make it an instance event instead.
public event Action Saved;
You have to hook it up for each instance instead of just once per class (or, in your current case, once), but it will separate the events.
So I've got these classes that expose a collection of child objects.
I don't want other classes adding or removing objects from collections because I need to wire into events in the child objects, so as they get added or removed I want to be able to do additional processing. But I really love the ease of manipulating generics internally.
Did I mention this is a WPF app so I need INotifySupport?
The best I can come up with is something like this.
public class foo : INotifyPropertyChanged
{
protected List<ChildFoo> _Children = new List<ChildFoo>();
public foo()
{
}
public void AddChild(ChildFoo newChild)
{
DoAttachLogic(newChild);
_Children.Add(newChild);
NotifyPropertyChange("Children");
}
public void RemoveChild(ChildFoo oldChild)
{
DoRemoveLogic(oldChild);
_Children.Remove(oldChild);
NotifyPropertyChange("Children");
}
public ChildFoo[] Children
{
get
{
return _Children.ToArray();
}
}
}
Are there serious flaws with this design that I'm not seeing?
Every time the Children property is accessed we get the overhead of converting list to an array.
Any advice on this would be great.
This is what I do for normal code:
Public Readonly Property Childern As ObjectModel.ReadOnlyCollection(Of Child)
Get
Return New ObjectModel.ReadOnlyCollection(Of Child)(_ChildernList)
End Get
End Property
For WPF code I would just expose a subclass of ObservableCollection.
You should use ObservableCollection as field in your class, you then have full access to modify collection. Then expose this as ReadonlyObservableCollection via property.
And if you dont change collection itself (eg. nochildren = new ObservableCollection(), you should make field readonly), then you dont need any kind of notifyPropertyChanged on this property, because it doesnt change and collection itself handles those events for its children.
public class Child
{
public int Value { get; set; }
}
class MyClassWithReadonlyCollection
{
private readonly ObservableCollection<Child> _children = new ObservableCollection<Child>();
public MyClassWithReadonlyCollection()
{
_children.Add(new Child());
}
//No need to NotifyPropertyChange, because property doesnt change and collection handles this internaly
public ReadOnlyObservableCollection<Child> Children { get { return new ReadOnlyObservableCollection<Child>(_children); } }
}
I changed the "add child" and "remove child" to protected since you are saying you don't want other classes modifying your collection. I changed your List to ObservableCollection so you can recieve collection changed notifications. Since you are using an IList there is no need to call ToArray(), just access directly.
try this:
public class foo : INotifyPropertyChanged
{
protected ObservableCollection<ChildFoo> _Children = new ObservableCollection<ChildFoo>();
public foo() { }
protected void AddChild(ChildFoo oldChild)
{
DoAttachLogic(newChild);
_Children.Add(newChild);
NotifyPropertyChange("Children");
}
protected void RemoveChild(ChildFoo oldChild)
{
DoRemoveLogic(oldChild);
_Children.Remove(oldChild);
NotifyPropertyChange("Children");
}
public ChildFoo this[int n]
{
get
{
return _Children[n];
}
}
}
You could subclass BindingList and set AllowNew/AllowRemove to false. In your Child Add/Remove methods, you can set it to true, make the changes, then set it back to false. (Of course, you need to hide set access to AllowNew/AllowRemove from outside callers as well).
Another option - subclass Observable collection and override the InsertItem, RemoveItem, etc methods to behave as AddChild/RemoveChild would behave. Then callers can still access it in familiar ways, but not bypass your custom logic.
Subclassing an existing collection class is probably going to be easier (for you and the consumer) than wrapping a collection in another class.
I've got a base class for all my custom entity collections, a simple version of it is this:
[Serializable]
public class CollectionBase<T> : List<T> where T : IEntity
{
public bool IsDirty {get;}
public new void Add(T item)
{
this.SetDirty();
base.Add(item);
item.MadeDirty += new EventHandler(item_MadeDirty);
}
// Other standard list methods overridden here
...
public void SetDirty() { } // Mark the collection as dirty
private void item_MadeDirty(object sender, EventArgs e)
{
this.SetDirty();
}
}
The collections sit within a serialized class that sits in Session (i.e. Customer class in session has a collection of Order entities). The problem is that my entity base class's MadeDirty event is as follows:
[field: NonSerialized()]
public event EventHandler MadeDirty;
Unfortunately, I cannot just remove the NonSerialized attribute on the event, because this causes issues in the session state server when deployed to my application server.
Is there any way that I can capture a deserialized completion event on the CollectionBase so I can iterate through all the items and re-assign the MadeDirty event on each deserialization from the Session? I.e.
private void OnDeserialized(object sender, EventArgs e)
{
foreach (T item in this)
{
item.MadeDirty+= new EventHandler(item_MadeDirty);
}
}
But, I can't imagine this is the first time anyone's come across this issue, so is there a better alternative?
Yes, you can add a method with a special attribute and the right signature to your class:
[OnDeserializedAttribute()]
private void RunThisMethod(StreamingContext context)
{
// post-serialize your class
}
Try http://msdn.microsoft.com/en-us/library/system.runtime.serialization.onserializingattribute.aspx and the equivalent OnDeserializedAttribute