Properties changing in a custom control - c#

I've created a custom control for easy addition as rows to a TableLayoutPanel. One entire row as seen in the image is a single instance of the control. I've to implement some functionality so that whenever rate and quantity are filled, the (complete) change should cause a sum += (rate * quantity) in the Total box.
What I was thinking of was something on the lines of a OnPropertyChanged event handler inside ProductControl.cs, which is the class for the custom control. I surmise that I would need two property change handlers, one for Rate and one for Quantity. Each would check whether the other field is empty or not, and proceed to update a Product = Rate*Quantity value.
However, how would I access these handlers in the main form? I need to update the Total box as Total += (Rate*Quantity) or Total += Product.
Source for ProductControl.cs -:
public partial class ProductControl : UserControl
{
Dictionary<int, PairClass<string, double>> PC = new Dictionary<int, PairClass<string, double>>();
public string Number
{
get
{
return ProdNo.Text;
}
set
{
ProdNo.Text = value;
}
}
public ProductControl()
{
InitializeComponent();
}
public void SetMapping(Dictionary<int, PairClass<string, double>> map)
{
PC = map;
}
//This implements the functionality that whenever a Product Number is entered,
//the Product Name and Rate appear automatically, by virtue of a mapping
//between Product Number: Pair<Product Name, Rate>.
private void ProdNoText_TextChanged(object sender, EventArgs e)
{
int x = 0;
PairClass<string, double> pc = null;
if (int.TryParse(ProdNoText.Text, out x))
{
PC.TryGetValue(x, out pc);
if (pc != null)
{
PNameText.Text = pc.First;
RateText.Text = pc.Second.ToString();
}
}
else
{
}
}
}

You can create a custom event for your control like this:
You first create the class ProductChangedEventArgs derived from EventArgs and containing all the informations the main form will need to handle che product change (let's say Rate and Quantity). This class only needs a constructor that accepts Rate and Quantity and two public getters.
Then, in your class:
// This will be the signature of your event handlers
public delegate void ChangedEventHandler(object sender, ProductChangedEventArgs e);
// The event itself to which will be possible to bind callbacks functions
// with the signature given by ChangedEventHandler
public event ChangedEventHandler Changed;
protected virtual void OnChanged(ProductChangedEventArgs e)
{
// This checks there's at least one callback bound to the event
if (Changed != null){
// If there are callbacks, call all of them
Changed(this, e);
}
}
Now all you need to do, is call OnChanged in whichever point you want the event to be emitted from.
You will, for example, call it in the setters of all your product properties.
private int _rate;
public int Rate{
set{
if(_rate != value){
_rate = value;
// Call the callbacks passing an EventArgs that reflects the actual state
// of the product
OnChanged(this, new ProductChangedEventArgs(_rate, ... ));
}
}
}
Now in the main form, you can bind callbacks to the Changed event like so:
productControl1.Changed += new ChangedEventHandler( callback );

Related

c# Notify parent property from child

I have the following classes:
public class child
{
public string product { get; set; }
public decimal amount { get; set; }
public decimal price { get; set; }
public decimal total { get; set; }
}
public class parent
{
public decimal total { get; set; }
public BindingList<child> childs { get; set; }
}
Now, in a windows form I set the following:
var parent_object = new parent();
numericUpDown1.DataBindings.Add("Value", parent_object , "total", true, DataSourceUpdateMode.OnPropertyChanged);
dataGridView1.DataBindings.Add("DataSource", parent_object , "childs", true, DataSourceUpdateMode.OnPropertyChanged);
Finally (and I have no idea how to do), is that the total property of parent changes automatically when:
The amount or price of some detail of the dataGridView is changed.
A row is added to the dataGridView.
A row is removed from the dataGridView.
Thanks for your help.
Your code so far looks reasonable except that there are a few missing pieces in terms of getting everything hooked up so I'll offer a few suggestions. The first would be to simplify the data binding for the DataGridView where all you need is dataGridView.DataSource = childs. If you did nothing else besides initialize it by overriding MainForm.OnLoad you'd already have a decent-looking view (but it would be missing the two-way interactions).
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
dataGridView.DataSource = childs;
// Add one or more child items to autogenerate columns.
childs.Add(new child
{
product = "GEARWRENCH Pinch Off Pliers",
price = 27.10m,
amount = 1.0m
});
childs.Add(new child
{
product = "AXEMAX Bungee Cords",
price = 25.48m,
amount = 1.0m
});
// Format rows
foreach (DataGridViewColumn column in dataGridView.Columns)
{
switch (column.Name)
{
case nameof(child.product):
column.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
break;
default:
column.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
column.DefaultCellStyle.Format = "F2";
break;
}
}
}
private readonly BindingList<child> childs = new BindingList<child>();
private readonly parent parent_object = new parent();
Bindable Properties
In order to create a bound property that supports two-way communication, you need a way to detect and notify when the properties change. For example, to make the total property bindable in the parent class do this:
public class parent : INotifyPropertyChanged
{
decimal _total = 0;
public decimal total
{
get => _total;
set
{
if (!Equals(_total, value))
{
_total = value;
OnPropertyChanged();
}
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
The data binding shown in your code for numericUpDown will now respond to changes of total.
numericUpDown.DataBindings.Add(
nameof(numericUpDown.Value),
parent_object,
nameof(parent_object.total),
false,
DataSourceUpdateMode.OnPropertyChanged);
Responding to Changes Internally
Once you make all of your properties in the child class bindable in the same way by using the example above, consider taking the approach of handling certain changes internally in which case you would suppress the firing of the property change notification.
public class child : INotifyPropertyChanged
{
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
switch (propertyName)
{
case nameof(price):
case nameof(amount):
// Perform an internal calculation.
recalcTotal();
break;
default:
// Notify subscribers of important changes like product and total.
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
break;
}
}
private void recalcTotal()
{
total = price * amount;
}
...
}
Connecting the total property in the parent class.
The only thing missing now is having a way to tell the parent_object that a new global total for all the rows is needed. The good news is that the bindings are already in place from the previous steps. To detect any change to the DGV whether a new row is added or a a total is edited, subscribe to the ListChanged event of the childs collection by making this the first line in the OnLoad (before adding any items).
childs.ListChanged += parent_object.onChildrenChanged;
This is a method that will need to be implemented in the parent_object class. Something like this:
internal void onChildrenChanged(object sender, ListChangedEventArgs e)
{
var tmpTotal = 0m;
foreach (var child in (IList<child>)sender)
{
tmpTotal += child.total;
}
total = tmpTotal;
}
READY TO TEST
If you implement these steps, you'll have a fully-functional linked view where you can add, remove, and modify child records.
Hope this gives some overall insight on how all the puzzle pieces fit together.

How do I add and subtract event handlers inside a derived abstract class?

Short version
In my abstract class MyCbo_Abstract (derived from ComboBox class), I want to create a custom property that when set will subtract all the control's event handlers, set the base property value, then re-add all the control's event handlers.
What I have so far
I have a concrete ComboBox class derived from an abstract ComboBox class derived from Microsoft's ComboBox class.
public abstract class MyCbo_Abstract : ComboBox
{
public MyCbo_Abstract() : base()
{
}
}
public partial class MyCboFooList : MyCbo_Abstract
{
public MyCboFooList() : base()
{
}
}
My main Form class subscribes to certain base ComboBox events.
Note: The designer has: this.myCboFooList = new MyCboFooList();
public partial class FormMain : Form
{
public FormMain()
{
myCboFooList.SelectedIndexChanged += myCboFooList_SelectedIndexChanged;
}
private void myCboFooList_SelectedIndexChanged(object sender, EventArgs e)
{
// do stuff
}
}
There are times when I want to suppress the invocation of defined event handlers, e.g., when I programmatically set a ComboBox object's SelectedIndex property.
Instead of having to remember to write the code to subtract and re-add event handlers each time I want to modify the SelectedIndex property and suppress its events, I want to create a custom property SelectedIndex_NoEvents that when set will subtract all the control's event handlers, set the base property value SelectedIndex, then re-add all the control's event handlers.
The problem
My problem is that I don't know how to iterate over a EventHandlerList because it has no GetEnumerator. And, in looking at the list in the debugger, saveEventHandlerList is a weird chained thing that I can't figure out how to otherwise traverse.
public abstract class MyCbo_Abstract : ComboBox
{
int selectedIndex_NoEvents;
public int SelectedIndex_NoEvents
{
get
{
return base.SelectedIndex;
}
set
{
EventHandlerList saveEventHandlerList = new EventHandlerList();
saveEventHandlerList = Events;
//foreach won't work - no GetEnumerator available. Can't use for loop - no Count poprerty
foreach (EventHandler eventHandler in saveEventHandlerList)
{
SelectedIndexChanged -= eventHandler;
}
base.SelectedIndex = value;
//foreach won't work - no GetEnumerator available. Can't use for loop - no Count poprerty
foreach (EventHandler eventHandler in saveEventHandlerList)
{
SelectedIndexChanged += eventHandler;
}
saveEventHandlerList = null;
}
}
//Probably don't need this
public override int SelectedIndex
{
get
{
return base.SelectedIndex;
}
set
{
base.SelectedIndex = value;
}
}
public DRT_ComboBox_Abstract() : base()
{
}
}
Before giving you the solution that I created, let me say that this feels extremely hacky. I urge you to seriously think about another solution. There may be all kinds of crazy edge cases where this code breaks down, I haven't thoroughly tested it beyond the example code shown below.
Add the following utility class:
public class SuspendedEvents
{
private Dictionary<FieldInfo, Delegate> handlers = new Dictionary<System.Reflection.FieldInfo, System.Delegate>();
private object source;
public SuspendedEvents(object obj)
{
source = obj;
var fields = obj.GetType().GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
foreach (var fieldInfo in fields.Where(fi => fi.FieldType.IsSubclassOf(typeof(Delegate))))
{
var d = (Delegate)fieldInfo.GetValue(obj);
handlers.Add(fieldInfo, (Delegate)d.Clone());
fieldInfo.SetValue(obj, null);
}
}
public void Restore()
{
foreach (var storedHandler in handlers)
{
storedHandler.Key.SetValue(source, storedHandler.Value);
}
}
}
You can use it like this:
var events = new SuspendedEvents(obj); //all event handlers on obj are now detached
events.Restore(); // event handlers on obj are now restored.
I used the following test setup:
void Main()
{
var obj = new TestObject();
obj.Event1 += (sender, e) => Handler("Event 1");
obj.Event1 += (sender, e) => Handler("Event 1");
obj.Event2 += (sender, e) => Handler("Event 2");
obj.Event2 += (sender, e) => Handler("Event 2");
obj.Event3 += (sender, e) => Handler("Event 3");
obj.Event3 += (sender, e) => Handler("Event 3");
Debug.WriteLine("Prove events are attached");
obj.RaiseEvents();
var events = new SuspendedEvents(obj);
Debug.WriteLine("Prove events are detached");
obj.RaiseEvents();
events.Restore();
Debug.WriteLine("Prove events are reattached");
obj.RaiseEvents();
}
public void Handler(string message)
{
Debug.WriteLine(message);
}
public class TestObject
{
public event EventHandler<EventArgs> Event1;
public event EventHandler<EventArgs> Event2;
public event EventHandler<EventArgs> Event3;
public void RaiseEvents()
{
Event1?.Invoke(this, EventArgs.Empty);
Event2?.Invoke(this, EventArgs.Empty);
Event3?.Invoke(this, EventArgs.Empty);
}
}
It produces the following output:
Prove events are attached
Event 1
Event 1
Event 2
Event 2
Event 3
Event 3
Prove events are detached
Prove events are reattached
Event 1
Event 1
Event 2
Event 2
Event 3
Event 3
There is no way to easily disable event firing of WinForm controls exposed in the .Net framework. However, the Winform controls follow a standard design pattern for events in that all event signatures are based on the EventHandler Delegate and the registered event handlers are stored in an EventHandlerList that is defined in the Control Class. This list is stored in a field (variable) named "events" and is only publicly exposed via the read-only property Events.
The class presented below uses reflection to temporarily assign null to the events field effectively removing all event handlers registered for the Control.
While it may be an abuse of the pattern, the class implements the IDisposable Interface to restore the events field on disposal of the class instance. The reason for this is to facilitate the use of the using block to wrap the class usage.
public class ControlEventSuspender : IDisposable
{
private const string eventsFieldName = "events";
private const string headFieldName = "head";
private static System.Reflection.FieldInfo eventsFieldInfo;
private static System.Reflection.FieldInfo headFieldInfo;
private System.Windows.Forms.Control target;
private object eventHandlerList;
private bool disposedValue;
static ControlEventSuspender()
{
Type compType = typeof(System.ComponentModel.Component);
eventsFieldInfo = compType.GetField(eventsFieldName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
headFieldInfo = typeof(System.ComponentModel.EventHandlerList).GetField(headFieldName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
}
private static bool FieldInfosAquired()
{
if (eventsFieldInfo == null)
{
throw new Exception($"{typeof(ControlEventSuspender).Name} could not find the field '{ControlEventSuspender.eventsFieldName}' on type Component.");
}
if (headFieldInfo == null)
{
throw new Exception($"{typeof(ControlEventSuspender).Name} could not find the field '{ControlEventSuspender.headFieldName}' on type System.ComponentModel.EventHandlerList.");
}
return true;
}
private ControlEventSuspender(System.Windows.Forms.Control target) // Force using the the Suspend method to create an instance
{
this.target = target;
this.eventHandlerList = eventsFieldInfo.GetValue(target); // backup event hander list
eventsFieldInfo.SetValue(target, null); // clear event handler list
}
public static ControlEventSuspender Suspend(System.Windows.Forms.Control target)
{
ControlEventSuspender ret = null;
if (FieldInfosAquired() && target != null)
{
ret = new ControlEventSuspender(target);
}
return ret;
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposedValue)
{
if (disposing)
{
if (this.target != null)
{
RestoreEventList();
}
}
}
this.disposedValue = true;
}
public void Dispose()
{
Dispose(true);
}
private void RestoreEventList()
{
object o = eventsFieldInfo.GetValue(target);
if (o != null && headFieldInfo.GetValue(o) != null)
{
throw new Exception($"Events on {target.GetType().Name} (local name: {target.Name}) added while event handling suspended.");
}
else
{
eventsFieldInfo.SetValue(target, eventHandlerList);
eventHandlerList = null;
target = null;
}
}
}
Example usage in the button1_Click method:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
using (ControlEventSuspender.Suspend(comboBox1))
{
comboBox1.SelectedIndex = 3; // SelectedIndexChanged does not fire
}
}
private void button2_Click(object sender, EventArgs e)
{
comboBox1.SelectedIndex = -1; // clear selection, SelectedIndexChanged fires
}
private void button3_Click(object sender, EventArgs e)
{
comboBox1.SelectedIndex = 3; // SelectedIndexChanged fires
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
Console.WriteLine("index changed fired");
System.Media.SystemSounds.Beep.Play();
}
}
SoapBox Diatribe
Many will say that the use of Reflection to access non-public class members is dirty or some other derogatory term and that it introduces a brittleness to the code as someone may change the underlying code definition such that the code that relies on member names (magic strings) is no longer valid. This is a valid concern, but I view it as no different than code that accesses external databases.
Reflection can be thought of a query of a type (datatable) from an assembly (database) for specific fields (members: fields, properties, events). It is no more brittle than a SQL statement such as Select SomeField From SomeTable Where AnotherField=5. This type of SQL code is prevent in the world and no one thinks twice about writing it, but some external force could easily redefine the database you code relies on an render all the magic string SQL statements invalid as well.
Use of hard coded names is always at risk of being made invalid by change. You have to weigh the risks of moving forward versus the option of being frozen in fear of proceeding because someone wants to sound authoritative (typically a parroting of other such individuals) and criticize you for implementing a solution that solves the current problem.
I was hoping to write code that would programatically locate all event handler method names created using controlObject.Event += EventHandlerMethodName, but as you see in the other answers, code to do this is complicated, limited, and perhaps not able to work in all cases
This is what I came up with. It satisfies my desire to consolidate the code that subtracts and re-adds event handler method names into my abstract class, but at the expense of having to write code to store and manage event handler method names and having to write code for each control property where I want to suppress the event handler, modify the property value, and finally re-add the event handler.
public abstract class MyCbo_Abstract : ComboBox
{
// create an event handler property for each event the app has custom code for
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
private EventHandler evSelectedValueChanged;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public EventHandler EvSelectedValueChanged { get => evSelectedValueChanged; set => evSelectedValueChanged = value; }
public MyCbo_Abstract() : base()
{
}
// Create a property that parallels the one that would normally be set in the main body of the program
public object _DataSource_NoEvents
{
get
{
return base.DataSource;
}
set
{
SelectedValueChanged -= EvSelectedValueChanged;
if (value == null)
{
base.DataSource = null;
SelectedValueChanged += EvSelectedValueChanged;
return;
}
string valueTypeName = value.GetType().Name;
if (valueTypeName == "Int32")
{
base.DataSource = null;
SelectedValueChanged += EvSelectedValueChanged;
return;
}
//assume StringCollection
base.DataSource = value;
SelectedValueChanged += EvSelectedValueChanged;
return;
}
}
}
public partial class MyCboFooList : MyCbo_Abstract
{
public MyCboFooList() : base()
{
}
}
Designer has
this.myCboFooList = new MyCboFooList();
Main form code
public partial class FormMain : Form
{
public FormMain()
{
myCboFooList.SelectedValueChanged += OnMyCboFooList_SelectedValueChanged;
myCboFooList.EvSelectedValueChanged = OnMyCboFooList_SelectedValueChanged;
}
private void OnMyCboFooList_SelectedValueChanged(object sender, EventArgs e)
{
// do stuff
}
}
And now, if I want to set a property and suppress event(s), I can write something like the following and not have to remember to re-add the event handler method name
myCboFooList._DataSource_NoEvents = null;

Simplest Method to monitor changes between combo box values

I'm looking for an elegant way to track changes between values for a combo box. What I'm looking to do is fire a custom event when the SelectionChanged event happens, but only for a specific value changes. This implies knowing what the initial value was. The event will only be fired when the initial value is changed from z. If the initial value is a, b, or c, the event will not be fired. But if the initial value was z, it will be fired.
Does anyone have an elegant way to solve this problem?
For this you will have to create a custom event handler and may be custom event args,
//Event Handler Class
public class SpecialIndexMonitor
{
public event EventHandler<SpecialIndexEventArgs> SpecialIndexSelected;
//Custom Function to handle Special Index
public void ProcessRequest(object sender, System.EventArgs e)
{
//Your custom logic
//Your code goes here
//Raise event
if(SpecialIndexSelected != null)
{
SpecialIndexEventArgs args = new SpecialIndexEventArgs{
SelectedIndex = ((ComboBox) sender).SelectedIndex;
};
SpecialIndexSelected(this, args);
}
}
}
//Custom Event Args
public class SpecialIndexEventArgs : EventArgs
{
//Custom Properties
public int SelectedIndex { get; set; } //For Example
//Default Constructor
public SpecialIndexEventArgs ()
{
}
}
Inside your form
//Hold previous value
private string _previousItem;
//IMPORTANT:
//After binding items to combo box you will need to assign,
//default selected item to '_previousItem',
//which will make sure SelectedIndexChanged works all the time
// Usage of Custom Event
private void comboBox1_SelectedIndexChanged(object sender,
System.EventArgs e)
{
string selectedItem = (string)comboBox1.SelectedItem;
if(string.Equals(_previousItem, )
switch(_previousItem)
{
case "z":
{
SpecialIndexMonitor spIndMonitor = new SpecialIndexMonitor();
spIndMonitor.SpecialIndexSelected +=
new EventHandler<SpecialIndexEventArgs>(SpecialIndexSelected);
break;
}
case "a":
case "b":
break;
}
_previousItem = selectedItem; //Re-Assign the current item
}
void SpecialIndexSelected(object sender, SpecialIndexEventArgs args)
{
// Your code goes here to handle the special index
}
Haven't compiled the code, but logically it should work for you.

.NET Collections and Accessing Object Methods

I'm completely new to GUI programming and need a little help with a list of pictureboxes.
The idea is that I have a list of pictureboxes. When a user clicks on one I want to (for example) change the BorderStyle property of the one selected to be Fixed3D, but change the remaining collection borders to FixedSingle (or something like that). What's the proper way to do something like this? I guess the bigger picture is how do I get a method of one class to call a method of another without having any information about it?
class myPicture
{
private int _pictureNumber;
private PictureBox _box;
public myPicture(int order)
{
_box = new List<PictureBox>();
_box.Click += new System.EventHandler(box_click);
_pictureNumber = order;
}
public void setBorderStyle(BorderStyle bs)
{
_box.BorderStyle = bs;
}
public void box_click(object sender, EventArgs e)
{
//here I'd like to call the set_borders from myPicturesContainer, but I don't know or have any knowledge of the instantiation
}
}
class myPicturesContainer
{
private List<myPicture> _myPictures;
//constructor and other code omitted, not really needed...
public void set_borders(int i)
{
foreach(myPicture mp in _MyPictures)
mp.setBorderStyle(BorderStyle.FixedSingle);
if(i>0 && _MyPictures.Count>=i)
_MyPictures[i].setBorderStyle(BorderStyle.Fixed3d);
}
}
You will need to create a Clicked event in your myPicture class and raise that event when it is clicked. Then you will need to attach to this event in your myPicturesContainer for each instance of myPicture that you have.
Here is a very simple example of what I mean:
class myPicture
{
public event Action<Int32> Clicked = delegate { };
private int _pictureNumber;
public void box_click(object sender, EventArgs e)
{
this.Clicked(this._pictureNumber);
}
}
class myPicturesContainer
{
private List<myPicture> _myPictures;
public void set_borders(int i)
{
foreach (myPicture mp in _myPictures)
{
mp.Clicked += pictureClick;
}
}
void pictureClick(Int32 pictureId)
{
// This method will be called and the pictureId
// of the clicked picture will be passed in
}
}

Winforms Databinding object containing a List<T>

I'm having trouble with a situation that I know must be pretty common, so I'm hoping the solution is simple. I have an object that contains a List<> of objects. It also has some properties that reflect aggregate data on the objects in the List<> (actually a BindingList<> so I can bind to it). On my form, I have a DataGridView for the List, and some other fields for the aggregate data. I can't figure out how to trigger a refresh of the aggregate data when values in the DataGridView get changed.
I have tried raising a PropertyChanged event when the properties of the objects in the List are changed, but that doesn't seem to refresh the display of the aggregate data. If I access an aggregate property (eg, display it in a messagebox), the textbox on the main form is refreshed.
Here's some simplified code to illustrate what I'm trying to do:
namespace WindowsFormsApplication1 {
public class Person {
public int Age {
get;
set;
}
public String Name {
get;
set;
}
}
public class Roster : INotifyPropertyChanged {
public BindingList<Person> People {
get;
set;
}
public Roster () {
People = new BindingList<Person>();
}
private int totalage;
public int TotalAge {
get {
calcAges();
return totalage;
}
set {
totalage = value;
NotifyPropertyChanged("TotalAge");
}
}
private void calcAges () {
int total = 0;
foreach ( Person p in People ) {
total += p.Age;
}
TotalAge = total;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged ( String info ) {
if ( PropertyChanged != null ) {
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
}
The calcAges method and the TotalAge property look very suspicious.
First, TotalAge should be read-only. If you allow it to be public and writable, what is the logic for changing the components that make up the age?
Second, every time you get the value, you are firing the PropertyChanged event, which is not good.
Your Roster class should look like this:
public class Roster : INotifyPropertyChanged {
public Roster ()
{
// Set the binding list, this triggers the appropriate
// event binding which would be gotten if the BindingList
// was set on assignment.
People = new BindingList<Person>();
}
// The list of people.
BindingList<Person> people = null;
public BindingList<Person> People
{
get
{
return people;
}
set
{
// If there is a list, then remove the delegate.
if (people != null)
{
// Remove the delegate.
people.ListChanged -= OnListChanged;
}
/* Perform error check here */
people = value;
// Bind to the ListChangedEvent.
// Use lambda syntax if LINQ is available.
people.ListChanged += OnListChanged;
// Technically, the People property changed, so that
// property changed event should be fired.
NotifyPropertyChanged("People");
// Calculate the total age now, since the
// whole list was reassigned.
CalculateTotalAge();
}
}
private void OnListChanged(object sender, ListChangedEventArgs e)
{
// Just calculate the total age.
CalculateTotalAge();
}
private void CalculateTotalAge()
{
// Store the old total age.
int oldTotalAge = totalage;
// If you can use LINQ, change this to:
// totalage = people.Sum(p => p.Age);
// Set the total age to 0.
totalage = 0;
// Sum.
foreach (Person p in People) {
totalage += p.Age;
}
// If the total age has changed, then fire the event.
if (totalage != oldTotalAge)
{
// Fire the property notify changed event.
NotifyPropertyChanged("TotalAge");
}
}
private int totalage = 0;
public int TotalAge
{
get
{
return totalage;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged ( String info ) {
if ( PropertyChanged != null ) {
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Now, when the properties in the list items are changed, the parent object will fire the property changed event, and anything bound to it should change as well.
I believe you may be looking for something like this
ITypedList
Also a Google Search of ITypedList leads you to a few nice blogs on how to implement.
When I use an ORM I typically have to do a few of these for nice datagrid binding and presentation.

Categories