C# how to implement databinding without Control? - c#

Is there a simple way to implement databinding when neither of both classes is of type Control?
In my case, I would like to bind a variable to a property of a custom ToolStripButton.
EDIT for clarification: when binding to a Control, I can use Control's DataBindings collection. However, I am searching for a way to bind properties regardless of the source and target Type.
EDIT: using winforms

You can probably do this by using Truss.
Truss provides WPF-style databinding for any class that implements INotifyPropertyChanged. It gives you a bit more flexibility in this, since it doesn't restrict the classes to being derived from a specific base class.

I use this Implemetation of IBindableComponent on the ToolStripButton, found here. The BindableToolStripButton allows you to use Databinding like with a normal Control.
[ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.ToolStrip | ToolStripItemDesignerAvailability.StatusStrip)]
public class BindableToolStripButton : ToolStripButton, IBindableComponent
{
public BindableToolStripButton()
: base() { }
public BindableToolStripButton(String text)
: base(text) { }
public BindableToolStripButton(System.Drawing.Image image)
: base(image) { }
public BindableToolStripButton(String text, System.Drawing.Image image)
: base(text, image) { }
public BindableToolStripButton(String text, System.Drawing.Image image, EventHandler onClick)
: base(text, image, onClick) { }
public BindableToolStripButton(String text, System.Drawing.Image image, EventHandler onClick, String name)
: base(text, image, onClick, name) { }
#region IBindableComponent Members
private BindingContext bindingContext;
private ControlBindingsCollection dataBindings;
[Browsable(false)]
public BindingContext BindingContext
{
get
{
if (bindingContext == null)
{
bindingContext = new BindingContext();
}
return bindingContext;
}
set
{
bindingContext = value;
}
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ControlBindingsCollection DataBindings
{
get
{
if (dataBindings == null)
{
dataBindings = new ControlBindingsCollection(this);
}
return dataBindings;
}
}
#endregion
}
Assuming you have a class MyClass implementing INotifyPropertyChanged, use it just like you would when binding to a control property:
bindableToolStripButton1.DataBindings.Add("Enabled", myClass1, "MyBooleanProperty");

Use dependency properties (your property in your ToolStripButton should be) and create a property for your variable in your other class and create a binding and set it to the property of your ToolstripButton.
I guess that's about the easiest way to do it.
EDIT: That's only for WPF...
Else implement INotifyPropertyChanged and when your variable changes, it should automatically change in your ToolStripButton.

For similar behaviour like Controls being bound to object properties, for any Type you can implement the same interfaces.
Based on that thought, you can subclass ToolStripButton (or desired Type to have bindings) and implement IBindableComponent for it. This works for all kinds of source and target Types as long as they're not sealed. For example, your tool strip button:
public class BindableToolStripButton : ToolStripButton, IBindableComponent {
//...
This will cause the BindableToolStripButton to have its own .DataBindings property whereas the base ToolStripButton class doesn't have such a propery.
You would need to follow through on filling out implementation details using examples seen here from Microsoft for ISite, IBindableComponent, IComponent and any inherited interfaces.
Then you would add Binding instances to any instance of BindableToolStripButton.
(Note: I only have fragements so will make my first community wiki post - and we'll see how that goes... )

I written some basic databinding stuff through reflection. It works on any object and doesn't need to implement something special (no INotifyPropertyChanged, it just works) it is part of my editor at http://github.com/filipkunc/opengl-editor-cocoa look at HotChocolate/Bindings (like re-implementation of Cocoa KVC, KVO into .NET) folder. You can see it in action in HotChocolateTest project.

There is another quick and simple solution which consists in creating properties in the Form, and bind them:
public MyForm : Form
{
...
public bool CanDelete
{
get { return deleteToolStripButton.Enabled; }
set { deleteToolStripButton.Enabled = value; }
}
public MyForm()
{
...
this.DataBindings.Add("CanDelete", this.MyModel, "DeleteAllowed",
false, DataSourceUpdateMode.Never);
...
}
}
Assuming that MyModel contains a DeleteAllowed property which notifies its changes.

Related

Updating UI when a model property changes in an ObservableCollection?

I have a view that has a group of images I get from a web service
I receive them in a list of this class:
public class ImageModel
{
public int Id { get; set; }
public string Name { get; set; }
public string imageUrl { get; set; }
}
under each image I show an up-vote button, so I added another bool property to the model above:
public bool UpVoted { get; set; }
the ListView that shows these images is bound to an ObservableCollection<ImageModel > , I want to change the voting icon through a converter that convert the value of UpVoted to the corresponding icon, when the user click the voting icon: a command execute this method:
private void OnVoting(ImageModel image)
{
Images.Single(x => x.id == image.id).UpVoted = !image.UpVoted;
}
the problem is that the UI is not updated, and to make sure that I understood the problem I turned the model to a View model and made the required changes to the UpVoted property (I'm using MVVM light library)
bool upVoted;
public bool UpVoted
{
get { return upVoted; }
set
{
Set(ref upVoted, value);
}
}
and it works now,
so I need to bind the UpVoted to the UI, so it's updated whenever it changed
first
your model class must inherit from MvxNotifyPropertyChanged
public class ImageModel : MvxNotifyPropertyChanged
{
public int Id { get; set; }
public string Name { get; set; }
private bool upVoted ;
public bool UpVoted
{
get { return upVoted ; }
set { upVoted = value; RaisePropertyChanged(() => UpVoted ); }
}
}
then with MvxValueConverter you ready to go
Mustafa's answer mentions a class that is specific to MvvmCross library.
Another alternative is TinyMvvm.
If you wish to write your own MVVM (or understand how MVVM works),
the general pattern is to implement INotifyPropertyChanged: Implement Property Change Notification, which I discuss here.
A convenient way to implement INotifyPropertyChanged, is to make a base class that does that implementation, then inherit from that base class. You can use the code in that sample as your base class. Or use a slightly different implementation, that avoids having to manually pass the property name as a string:
using System.ComponentModel;
using System.Runtime.CompilerServices;
// Use this as base class for all your "view model" classes.
// And possibly for your (domain) model classes.
// E.g.: "public class MyLoginViewModel : HasNotifyPropertyChanged".
// OR "public class MyLoginModel : HasNotifyPropertyChanged".
// Give it whatever name you want, for ViewModels I suggest "ViewModelBase".
public class HasNotifyPropertyChanged : INotifyPropertyChanged
{
// --- This is pattern to use to implement each property. ---
// This works for any property type: int, Color, etc.
// What's different from a standard c# property, is the "SetProperty" call.
// You will often write an IValueConverter (elsewhere) to use in XAML to convert from string to your property type,
// or from your property type to a type needed in your UI.
// Comment out this example property if you don't need it.
/// <summary>
/// Set to "true" at end of your initialization.
/// Then can use Property Trigger on Ready value=true in XAML to do something when your instance is ready for use.
/// For example, load something from web, then trigger to update UI.
/// </summary>
private bool _ready;
public bool Ready
{
get => _ready;
set => SetProperty(ref _ready, value);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
if (property == null || !property.Equals(value))
{
property = value;
RaisePropertyChanged(propertyName);
}
}
protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Again, an alternative to the above code is to use an existing MVVM library.
For another alternative, that doesn't require writing "SetProperty(..)" or "OnPropertyChanged(..)" in all of your property setters, google for info about using Fody/PropertyChanged. Then you wouldn't need any of the above code; your class would simply inherit from INotifyPropertyChanged. (And in app startup, you call a method that "injects" the needed logic into all properties of all INotifyPropertyChanged classes.)
Acknowledgement: The code pattern in example above is based on one of the open source libraries. It might be from TinyMvvm.
you do not say which sort of container that you are using but not all controls are set to support two way notification by default. so you may have to add a
Mode=TwoWay
to get notifications from the back end that data has changed. Or as the previous answer by Mustafa indicated you may need to verify that your class is implementing the InotifyPropertyChanged event with mvvm light.

Pass properties of parent control to children controls

I am developing a set of custom controls for a specific application. I want to define properties which is universal over the set of controls for appearance purposes, for argument's sake let's make it CustomCtrl.AccentColor
I want to define that same property for my Windows form i.e. Form1.AccentColor and when I change it, all the custom controls' AccentColor should change, exactly like when I change the ForeColor of my form, all labels' and buttons' etc ForeColor changes with it.
Is it at all possible to do this or do I have to settle for the effort of looping through all custom controls and changing it one-by-one?
Short Answer
Since you can have a common base class for all your controls as you mentioned in comments, as an option you can create a base class and then add some properties with behavior like ambient properties (like Font) to the base control class.
Detailed Answer
An ambient property is a property on a control that, if not set, is retrieved from the parent control.
In our implementation, we get the value from parent Form using FindForm method. So in the implementation, when getting the property value, we check if the value equals to default value and if the parent from has the same property, we return the property value of the parent form, otherwise we return the property value of the control itself.
After adding XXXX property, in this scenario we also should implement ShouldSerializeXXXX and ResetXXXX methods to let the designer when serialize the property and how to reset value when you right click on property and choose reset.
MyBaseControl
using System.Drawing;
using System.Windows.Forms;
public class MyBaseControl : Control
{
public MyBaseControl()
{
accentColor = Color.Empty;
}
private Color accentColor;
public Color AccentColor
{
get
{
if (accentColor == Color.Empty && ParentFormHasAccentColor())
return GetParentFormAccentColor();
return accentColor;
}
set
{
if (accentColor != value)
accentColor = value;
}
}
private bool ParentFormHasAccentColor()
{
return this.FindForm() != null &&
this.FindForm().GetType().GetProperty("AccentColor") != null;
}
private Color GetParentFormAccentColor()
{
if (ParentFormHasAccentColor())
return (Color)this.FindForm().GetType()
.GetProperty("AccentColor").GetValue(this.FindForm());
else
return Color.Red;
}
private bool ShouldSerializeAccentColor()
{
return this.AccentColor != GetParentFormAccentColor();
}
private void ResetAccentColor()
{
this.AccentColor = GetParentFormAccentColor();
}
}
MyBaseForm
public class BaseForm : Form
{
[DefaultValue("Red")]
public Color AccentColor { get; set; }
public BaseForm()
{
this.AccentColor = Color.Red;
}
}
Form1
public partial class Form1 : BaseForm
{
public Form1()
{
InitializeComponent();
}
}
i think you can create inherited class from Control class and define your common properties on there then inheriting your custom controls from that class and use parent property to access container (like Form) and get property value from it

Workaround for generic event handler in Windows Forms

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

C# UserControl multiple inheritance

I would like to create my own user controls which implement more properties and methods than the ones given from .NET Framework. First of all I would like to have a custom UserControl class, which I would call MyUserControl and of course will inherit from UserControl:
public class MyUserControl : UserControl {
public MyUserControl() : base() {
}
...
}
After that I would like to have my own MyTestBox, which will inherit from TextBox.
public class MyTextBox : TextBox {
public MyTextBox() : base() {
}
...
}
My problem now is that I want MyTextBox to inherit from MyUserControl also, because I have properties and methods implemented there, that I need in MyTextBox also.
The only solution I could think of is to make MyTextBox inherit just from MyUserControl and not from TextBox, but add a TextBox in it in the constructor:
public class MyTextBox : MyUserControl {
public MyTextBox() : base() {
Add(new TextBox());
}
...
}
but then I would have to re-implement every single property and method of TextBox in MyTextBox. Is there a better way to achieve this?
Separate out all the common code into a separate class and use delegation to handle it.
ie. something like this:
public class MyUserControl : UserControl
{
private MyExtraControlCode _Extras;
public MyUserControl()
{
_Extras = new MyExtraControlCode(this);
}
public int GetInt32Value()
{
return _Extras.GetInt32Value();
}
}
public class MyTextBox : TextBox
{
private MyExtraControlCode _Extras;
public MyTextBox()
{
_Extras = new MyExtraControlCode(this);
}
public int GetInt32Value()
{
return _Extras.GetInt32Value();
}
}
or something similar.
Not as straightforward, but multiple inheritance is just not supported in .NET.
After looking at the problem in more detail, I finally reached to this conclusion: There are 3 ways to achieve what I want, but all of them require some extra implementation.
First of all multiple inheritance is not supported in C# .NET. In my case it would make things more complex anyway, because in MyTextBox I wanted in a way to be able to access custom properties/methods from both MyUserControl and TextBox. But both of these classes inherit from Component Class, so multiple inheritance (if it were possible) would mess thing up here. The question I asked myself is: What do you finally want to do? The answer is, that I simply want to be able to "extend" all my UserControls (or Components) in general (namely regarding all UserControls) and also specifically (namely regarding TextBox, Combobox, ListView, etc), so that when I create a MyTextBox object, I could do both:
myTextBox.MyUserControlStuff();
and
myTextBox.TextBoxStuff();
So here are my solutions:
1. Use extensions
and extend UserControl (or Component) class. In that case, I have:
public class MyTextBox : TextBox
{
public void MyTextBoxStuff()
{
...
}
}
public static class UserControlExtensions
{
public static void MyUserControlStuff(this UserControl control)
{
...
}
}
And MyTextBox can also do all UserControl stuff and TextBox stuff
Downside: every (specific) extension method applies to UserControl also.
2. Use an interface
public class MyTextBox : TextBox, IUserControlStuff
{
public void MyTextBoxStuff()
{
...
}
public void MyUserControlStuff()
{
...
}
}
public interface IUserControlStuff
{
public void MyUserControlStuff();
}
Downside: I will have to re-implement MyUserControlStuff each time again and again.
3. Use an "extra code" class and make it a member of each custom UserControl & MyUserControl (like #Mr.Karlsen suggested)
See the answer of #Mr.Karlsen for more details. I would say this is the best solution, but it has its downside
Downside: A little messy. A developer, new to my project would find it difficult to understand when seeing it for the first time. I would personally avoid it.
Finally I decided to go with the interface, because my UserControl properties/methods are specific to my needs so it wouldn't be good to extend UserControl with "specific" stuff. I have to write more code in my case and even re-write the same code while implementing my interface's methods, which I really hate. Anyway!!

Is there a good pattern for exposing a generic collection as readonly?

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.

Categories