c# Notify parent property from child - c#

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.

Related

Event circularity

I find myself quite often in the following situation:
I have a user control which is bound to some data. Whenever the control is updated, the underlying data is updated. Whenever the underlying data is updated, the control is updated. So it's quite easy to get stuck in a never ending loop of updates (control updates data, data updates control, control updates data, etc.).
Usually I get around this by having a bool (e.g. updatedByUser) so I know whether a control has been updated programmatically or by the user, then I can decide whether or not to fire off the event to update the underlying data. This doesn't seem very neat.
Are there some best practices for dealing with such scenarios?
EDIT: I've added the following code example, but I think I have answered my own question...?
public partial class View : UserControl
{
private Model model = new Model();
public View()
{
InitializeComponent();
}
public event EventHandler<Model> DataUpdated;
public Model Model
{
get
{
return model;
}
set
{
if (value != null)
{
model = value;
UpdateTextBoxes();
}
}
}
private void UpdateTextBoxes()
{
if (InvokeRequired)
{
Invoke(new Action(() => UpdateTextBoxes()));
}
else
{
textBox1.Text = model.Text1;
textBox2.Text = model.Text2;
}
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
model.Text1 = ((TextBox)sender).Text;
OnModelUpdated();
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
model.Text2 = ((TextBox)sender).Text;
OnModelUpdated();
}
private void OnModelUpdated()
{
DataUpdated?.Invoke(this, model);
}
}
public class Model
{
public string Text1 { get; set; }
public string Text2 { get; set; }
}
public class Presenter
{
private Model model;
private View view;
public Presenter(Model model, View view)
{
this.model = model;
this.view = view;
view.DataUpdated += View_DataUpdated;
}
public Model Model
{
get
{
return model;
}
set
{
model = value;
view.Model = model;
}
}
private void View_DataUpdated(object sender, Model e)
{
//This is fine.
model = e;
//This causes the circular dependency.
Model = e;
}
}
One option would be to stop the update in case the data didn't change since the last time. For example if the data were in form of a class, you could check if the data is the same instance as the last time the event was triggered and if that is the case, stop the propagation.
This is what many MVVM frameworks do to prevent raising PropertyChanged event in case the property didn't actually change:
private string _someProperty = "";
public string SomeProperty
{
get
{
return _someProperty;
}
set
{
if ( _someProperty != value )
{
_someProperty = value;
RaisePropertyChanged();
}
}
}
You can implement this concept similarly for Windows Forms.
What you're looking for is called Data Binding. It allows you to connect two or more properties, so that when one property changes others will be updated auto-magically.
In WinForms it's a little bit ugly, but works like a charm in cases such as yours. First you need a class which represents your data and implements INotifyPropertyChanged to notify the controls when data changes.
public class ViewModel : INotifyPropertyChanged
{
private string _textFieldValue;
public string TextFieldValue {
get
{
return _textFieldValue;
}
set
{
_textFieldValue = value;
NotifyChanged();
}
}
public void NotifyChanged()
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(null));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Than in your Form/Control you bind the value of ViewModel.TextFieldValue to textBox.Text. This means whenever value of TextFieldValue changes the Text property will be updated and whenever Text property changes TextFieldValue will be updated. In other words the values of those two properties will be the same. That solves the circular loops issue you're encountering.
public partial class Form1 : Form
{
public ViewModel ViewModel = new ViewModel();
public Form1()
{
InitializeComponent();
// Connect: textBox1.Text <-> viewModel.TextFieldValue
textBox1.DataBindings.Add("Text", ViewModel , "TextFieldValue");
}
}
If you need to modify the values from outside of the Form/Control, simply set values of the ViewModel
form.ViewModel.TextFieldValue = "new value";
The control will be updated automatically.
You should look into MVP - it is the preferred design pattern for Winforms UI.
http://www.codeproject.com/Articles/14660/WinForms-Model-View-Presenter
using that design pattern gives you a more readable code in addition to allowing you to avoid circular events.
in order to actually avoid circular events, your view should only export a property which once it is set it would make sure the txtChanged_Event would not be called.
something like this:
public string UserName
{
get
{
return txtUserName.Text;
}
set
{
txtUserName.TextChanged -= txtUserName_TextChanged;
txtUserName.Text = value;
txtUserName.TextChanged += txtUserName_TextChanged;
}
}
or you can use a MZetko's answer with a private property

ObservableCollection filtering on Windows Phone 8.1 Universal

I am writing windows phone 8.1 universal application and main applicaiton control is Pivot with few pivot items. In the pivot items are ListViews containing TestItems. I want to filter items on one list by IsRead property. Is it possible to just filter main collection without keeping 2 collections? CollectionViewSource does not support filtering a sorting on universal apps, if I know. But keeping (and synchronizing on changes) two collections doesn't look like good idea.
EDIT:
I have used ObservableCollection because list of items may be updated on the background. Probably it was not clear from original question.
class TestItem : ModelBase
{
private bool isRead;
public bool IsRead
{
get { return isRead; }
set
{
isRead = value;
NotifyPropertyChanged();
}
}
private string name;
public string Name
{
get { return name; }
set
{
name = value;
NotifyPropertyChanged();
}
}
}
class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
Items = new ObservableCollection<TestItem>();
}
public ObservableCollection<TestItem> Items { get; private set; }
public ObservableCollection<TestItem> ItemsRead { get; private set; } // key point
private void RefreshItems()
{
// data manipulation - on both collections?
}
// ...
}
You can use Linq;
In your case:
using System.Linq;
class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
Items = new ObservableCollection<TestItem>();
}
public ObservableCollection<TestItem> Items { get; private set; }
//public ObservableCollection<TestItem> ItemsRead { get; private set; } // key point
public IEnumerable<TestItem> ItemsRead
{
get
{
IEnumerable<TestItem> itemsRead = from item in Items
where item.IsRead
select item;
return itemsRead;
}
}
private void RefreshItems()
{
// data manipulation - on both collections?
}
// ...
}
Please, check syntax, it can contain some mistakes.
You can manipulate with the first collection, the second collection will be automatically updated.
You can define a CollectionViewSource in your XAML:
<Grid.Resources>
<CollectionViewSource x:Name="MyCollectionViewSource"/>
</Grid.Resources>
And then set it's source like this:
//Global variable
MainViewModel vm;
//Constructor
public MyPage(){
//Other code
vm = new MainViewModel();
vm.Items.CollectionChanged += Items_CollectionChanged;
UpdateViewSource();
}
private void Items_CollectionChanged(object sender, CollectionChangedEventArgs e){
UpdateViewSource();
}
private void UpdateViewSource(){
MyCollectionViewSource.Source = vm.Items.Where(x => x.IsRead);
}
I haven't tested this code.
You need only one ObservableCollection containing the initial objects and another property (let's say ItemsFiltered) with a get method returning the results after filtering. In the constructor you can subscribe to the CollectionChanged event of the observable collection to raise the OnPropertyChanged event for the ItemsFiltered property. You raise the same event when the filter state is changed. This is a simple example:
public MainViewModel()
{
_initialItems.CollectionChanged += (sender, e) => OnPropertyChanged("Items");
}
private ObservableCollection<TestItem> _initialItems = new ObservableCollection<TestItem>();
public List<TestItem> Items
{
get
{
if (IsReadFilter)
{
return _initialItems.Where(i => i.IsRead).ToList();
}
return _initialItems;
}
}
private bool _isReadFilter;
public bool IsReadFilter
{
get { return _isReadFilter; }
set
{
if (_isReadFilter != value)
{
_isReadFilter = value;
OnPropertyChanged("IsReadFilter");
OnPropertyChanged("Items");
}
}
}
Basically, the idea is that every time IsReadFilter value is changed, the UI gets notified that the Items property is changed and calls its get method to get the new value and update. Items are also updated every time the observable collection is changed from other places.

Properties changing in a custom control

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 );

Bind "Enabled" properties of controls to a variable

I am running into an issue that I have found on some similar post, however, they are not quite the same and I am not quite sure how to apply it to my scenario. They may or may not be the same as my case. So, I am posting my own question here hopefully, I will get an answer to my specific scenario.
Basically, I have a window form with a bunch of controls. I would like to have the ability to bind their Enabled property to a Boolean variable that I set so that they can be enable or disable to my discretion.
public partial class MyUI : Form
{
private int _myID;
public int myID
{
get
{
return _myID;;
}
set
{
if (value!=null)
{
_bEnable = true;
}
}
}
private bool _bEnable = false;
public bool isEnabled
{
get { return _bEnable; }
set { _bEnable = value; }
}
public myUI()
{
InitializeComponent();
}
public void EnableControls()
{
if (_bEnable)
{
ctl1.Enabled = true;
ctl2.Enabled = true;
......
ctl5.Enabled = true;
}
else
{
ctl1.Enabled = false;
ctl2.Enabled = false;
......
ctl5.Enabled = false;
}
}
}
}
The method EnableControls above would do what I need but it may not be the best approach. I prefer to have ctrl1..5 be bound to my variable _bEnable. The variable will change depending on one field users enter, if the value in the field exists in the database, then other controls will be enabled for user to update otherwise they will be disabled.
I have found a very similar question here
but the data is bound to the text field. How do I get rid of the EnableControls method and bind the value of _bEnabled to the "Enabled" property in each control?
Go look into the MVVM (Model - View - ViewModel) pattern, specifically its implementation within Windows Forms. Its much easier to apply it to a WPF/Silverlight application, but you can still use it with Windows Forms without too much trouble.
To solve your problem directly, you will need to do 2 things:
Create some class that will hold your internal state (i.e. whether or not the buttons are enabled). This class must implement INotifyPropertyChanged. This will be your View Model in the MVVM pattern.
Bind an instance of the class from 1.) above to your Form. Your form is the View in the MVVM pattern.
After you have done 1 and 2 above, you can then change the state of your class (i.e. change a property representing whether a button is enabled from true to false) and the Form will be updated automatically to show this change.
The code below should be enough to get the concept working. You will need to extend it obviously, but it should be enough to get you started.
View Model
public class ViewModel : INotifyPropertyChanged
{
private bool _isDoStuffButtonEnabled;
public bool IsDoStuffButtonEnabled
{
get
{
return _isDoStuffButtonEnabled;
}
set
{
if (_isDoStuffButtonEnabled == value) return;
_isDoStuffButtonEnabled = value;
RaisePropertyChanged("IsDoStuffButtonEnabled");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View
public class View : Form
{
public Button DoStuffButton { get; set; }
public void Bind(ViewModel vm)
{
DoStuffButton.DataBindings.Add("Enabled", vm, "IsDoStuffButtonEnabled");
}
}
Usage
public class Startup
{
public ViewModel ViewModel { get; set; }
public View View { get; set; }
public void Startup()
{
ViewModel = new ViewModel();
View = new View();
View.Bind(ViewModel);
View.Show();
ViewModel.IsDoStuffButtonEnabled = true;
// Button becomes enabled on form.
// ... other stuff here.
}
}
Maybe you can try this approach: in your isEnabled property's setter method, add an if statement:
if(_bEnable) EnableControls();
else DisableControls();
And if your control names are ctl1,ctl2... etc. you can try this:
EnableControls()
{
for(int i=1; i<6;i++)
{
string controlName = "ctl" + i;
this.Controls[controlName].Enabled = true;
}
}
And apply the same logic in DisableControls
If you have more controls in future this could be more elegant.

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