Lets say I have sales price, down payment amount, down payment percent and loan amount. When any of these properties are changed by the user the others need to be updated to reflect the new values. How do you deal with this type of infinite property change events?
When flow control is necessary across multiple attributes, I'll institute a flow control variable - a boolean - and in each property that's being changed, I'll add a test to see if I'm under flow control or not.
private bool controlledChange = false;
public property int MyVal1
{
set
{
_myVal1 = value;
if(!controlledChange)
{
controlledChange = true;
MyVal2 -= 1;
controlledChange = false;
}
}
}
public property int MyVal2
{
set
{
_myVal2 = value;
if(!controlledChange)
{
controlledChange = true;
MyVal1 += 1;
controlledChange = false;
}
}
}
This way whatever property is changed can initiate changes across the other properties, but when they get changed, they will no NOT to initiate their own set of changes in turn.
You should also look to make as many of those properties read only as possible, if they can have calculated results, so that you limit how the object can be changed.
THe easiest way is to only raise a change event if the property has really changed:
public decimal SalePrice {
get{
return salePrice;
}
set {
if (salePrice != value) {
salePrice = value; // putting as first statement prevents the setter
// to be entered again ...
RaiseSalePriceChange();
// Set other properties
}
}
}
I'm not sure I completely understand, since I don't know what you mean by 'infinite'
This may be a good use case for actually backing your properties with fields. That way, you can trigger events on Property sets, but internally set the fields one at a time without triggering N events.
class MyClass
{
private string m_Name;
private int m_SomeValue;
public string Name
{
get { return m_Name; }
set
{
if (value != m_Name)
{
m_Name = value;
m_SomeValue++;
// Raise Event
}
}
}
public int SomeValue
{
get { return m_SomeValue; }
set
{
if (m_SomeValue != value)
{
m_SomeValue = value;
// Raise Event
}
}
}
If INotifyPropertyChanged is really needed to notify external objects, so I would just centralise everything. Like this:
private double salesPrice;
private double downPaymentAmount;
private double downPaymentPercent;
private double loanAmount;
public double SalesPrice
{
get
{
return salesPrice;
}
set
{
if (salesPrice != value)
{
salesPrice = value;
// maybe you would rather use a RecalculateForSalePriceChanged() method
RecalculateDownPaymentAmount();
RecalculateDownPaymentPercent();
RecalculateLoanAmount();
propertiesChanged();
}
}
}
public double DownPaymentAmount
{
get
{
return downPaymentAmount;
}
set
{
if (downPaymentAmount != value)
{
downPaymentAmount = value;
// see above
RecalculateDownPaymentPercent();
RecalculateLoanAmount();
RecalculateSalesPrice();
propertiesChanged();
}
}
}
public double DownPaymentPercent
{
get
{
return downPaymentPercent;
}
set
{
if (downPaymentPercent != value)
{
downPaymentPercent = value;
// see above
RecalculateDownPaymentAmount();
RecalculateLoanAmount();
RecalculateSalesPrice();
propertiesChanged();
}
}
}
public double LoanAmount
{
get
{
return loanAmount;
}
set
{
if (loanAmount != value)
{
loanAmount = value;
// see above
RecalculateDownPaymentAmount();
RecalculateDownPaymentPercent();
RecalculateSalesPrice();
propertiesChanged();
}
}
}
private void propertiesChanged()
{
RaisePropertyChanged("SalesPrice", "DownPaymentAmount", "DownPaymentPercent", "LoanAmount");
}
Maybe you can concentrate the recalculations in less methods or even a single one, but I do not know how you calculate them. But certainly you have to keep a specific order when recalculating the values.
Since they only operate on fields and do not change the properties, there will be no PropertyChanged-feedback-loop.
Hope this helps and I did not misunderstood what you wanted.
What the OP wanted was something like following
class A : INotifyPropertyChanged
{
private int field1;
public int Property1
{
get { return field1; }
set
{
field1 = value;
field2++;
RaisePropertyChanged("Property1");
RaisePropertyChanged("Property2");
}
}
private int field2;
public int Property2
{
get { return field2; }
set
{
field2 = value;
field1++;
RaisePropertyChanged("Property1");
RaisePropertyChanged("Property2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
What he might be doing was handling other properties in the setter of each property he mentioned thus leading to cyclic invocation of setters.
Vijay
Related
I am developing a WPF application in C# 4.5.2 in Visual Studio 2017. Within the application I have a custom object that gets rolled into an ObservableCollection<T> for later processing. I want to be able to handle this CollectionChanged event if/when necessary.
I added the appropriate decoration to the class (: INotifyPropertyChanged), added the event and handler, and attributed each property in my object with the Interface PropertyChanged:
public class OrderLineItem : INotifyPropertyChanged
{
private int _lineItemNumber;
public int LineItemNumber
{
get => _lineItemNumber;
set { _lineItemNumber = value; OnPropertyChanged(); }
}
private int _quantity;
public int Quantity
{
get => _quantity;
set { _quantity = value; OnPropertyChanged(); }
}
private string _partNumber;
public string PartNumber
{
get => _partNumber;
set { _partNumber = value; OnPropertyChanged(); }
}
private Hinge _hinged;
public Hinge Hinging
{
get => _hinged;
set { _hinged = value; OnPropertyChanged(); }
}
private Finish _finished;
public Finish Finished
{
get => _finished;
set { _finished = value; OnPropertyChanged(); }
}
private decimal _unitPrice;
public decimal UnitPrice
{
get => _unitPrice;
set { _unitPrice = value; OnPropertyChanged(); }
}
private decimal _modifyPrice;
public decimal ModifyPrice
{
get => _modifyPrice;
set { _modifyPrice = value; OnPropertyChanged(); }
}
private decimal _extendedPrice;
public decimal ExtendedPrice
{
get => _extendedPrice;
set { _extendedPrice = value; OnPropertyChanged(); }
}
private List<string> _modifications;
public List<string> Modifications
{
get => _modifications;
set { _modifications = value; OnPropertyChanged(); }
}
private CabinetType _type;
public CabinetType Type
{
get => _type;
set { _type = value; OnPropertyChanged(); }
}
private string _display;
public string Display
{
get => _display;
set { _display = value; OnPropertyChanged(); }
}
public enum Hinge { None = 0, L, R, BD }
public enum Finish { None = 0, L, R, B }
public OrderLineItem()
{
LineItemNumber = -1;
Quantity = -1;
PartNumber = string.Empty;
Hinging = Hinge.None;
Finished = Finish.None;
UnitPrice = 0.00m;
ModifyPrice = 0.00m;
ExtendedPrice = 0.00m;
Modifications = new List<string>();
Type = CabinetType.None;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In my MainWindow.xaml.cs file I have added the .CollectionChanged handler to my ObservableCollection<T> and the .PropertyChanged handler to my object:
private void AddedItemsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (OrderLineItem newItem in e.NewItems)
{
_addedItems.Add(newItem);
newItem.PropertyChanged += OnItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (OrderLineItem oldItem in e.OldItems)
{
_addedItems.Add(oldItem);
oldItem.PropertyChanged -= OnItemPropertyChanged;
}
}
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
OrderLineItem item = sender as OrderLineItem;
if (item != null) _addedItems.Add(item);
}
Any time I add an item to the collection I add the PropertyChanged handler to it:
...
item.PropertyChanged += ItemOnPropertyChanged;
...
This collection gets displayed in a DataGrid via the .ItemsSource property.
The issue in this question's title only happens after implementing the INotifyPropertyChanged and its associated methods/interfaces etc.
The stack trace I was able to get does not provide me with any useful information (line number, file, method etc) to attempt to debug this. I did enable all of the debugging options to make sure I wasn't ignoring any potential exceptions. Here is a pastebin of the exception. The line number indicated is this:
AppDomain.CurrentDomain.UnhandledException +=
(sender, args) => throw new Exception("Unhandled exception: " + args.ExceptionObject);
If I do not have this line, or comment it out this is a pastebin of the exception I get instead.
Any idea of how I should proceed trying to solve this, besides not implementing the CollectionChanged event?
There seem to be a relation with INotifyPropertyChanged and Binding rules but I don't know which one.
I got into the same issue: implementing INotifyPropertyChanged on a PropertyGrid item triggered the Key cannot be null binding error. By the way, it was one of the most useless and random error logs I have seen.
Cannot save value from target back to source.
BindingExpression:[info on an unrelated parent binding...]
ArgumentNullException:'System.ArgumentNullException: Key cannot be null.
After some time, I found out about weird Binding behaviors in old wpf versions where adding Path= to the bindings could fix it, but that wasn't it.
I actually had a Binding in code-behind like so:
void GetBinding(PropertyItem propertyItem)
{
return new Binding($"({propertyItem.PropertyName})")
{
//[Source, Mode, etc...]
}
}
And the code worked for both properties and attached properties, but stopped working right after implementing the property notifier on this class. Maybe a more strict rule got triggered and flagged my property as not being an "attached" one, but it worked before without the interface.
So, I removed the parenthesis and it works now.
return new Binding($"{propertyItem.PropertyName}")
Looking at your exception, it seems like the problem is related to searching for a null key from your HybridDictionary.
I'm guessing that the INotifyPropertyChanged implementation is simply triggering this HybridDictionary[null] lookup, and is unrelated to the fundamental problem.
At a higher level, this seems like something the VS debugger should jump you straight to, so this seems odd, and I would recommend digging around your environment to make sure debugging is working properly.
How can I refresh the following ObservableCollection?
public class ViewModelProperties
{
private ObservableCollection<ServerProperties> properties;
public ObservableCollection<ServerProperties> Properties
{
get
{
properties = new ObservableCollection<ServerProperties>();
for (var lineNumber = 0; lineNumber < MainWindow.lineCount; lineNumber++)
{
if (MainWindow.textProperties[lineNumber, 0] == null) break;
properties.Add(new ServerProperties(MainWindow.textProperties[lineNumber, 0],
MainWindow.textProperties[lineNumber, 1]));
}
return properties;
}
}
}
public class ServerProperties
{
private string property;
private string value;
public ServerProperties()
{
}
public ServerProperties(string property, string value)
{
Property = property;
Value = value;
}
public string Property
{
get
{
return this.property;
}
set
{
this.property = value;
}
}
public string Value
{
get
{
return this.value;
}
set
{
this.value = value;
}
}
public override string ToString()
{
return string.Format("[Property : {0}]", Value);
}
}
I changed the value of textProperties[,] and now I'd like to overwrite the previous content of the collection with the current content of textProperties[,].
What would be the simplest way to do this?
Any help would be appreciated.
Start off by implementing INotifyPropertyChanged in your ViewModel as well as in the ServerProperties object. This way you can raise the PropetyChanged event which will pass back to the user interface.
ViewModel
public class ViewModelProperties : INotifyPropertyChanged {
public event ProeprtyChangedEventHandler PropertyChanged;
private ObservableCollection<ServerProperties> properties = new ObservableCollection<ServerProperties>();
public ObservableCollection<ServerProperties> Properties {
get { return properties;}
set {
properties = value;
this.RaisePropertyChangedEvent("Properties");
}
}
private void RaisePropertyChanged(string propertyName) {
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Implementing this on the ServerProperties object as well will allow you to change the objects, at any level, and have it bubble up to the interface.
Lastly look into your population code and in order to get the property to update successfully first populate it to a List then re-initialise the ObservableCollection using the List.
Properties = new ObservableCollection<ServerProperties>(propertiesList);
This also allows you to better handle the creation of your ObservableCollection and perform tests before posting the output to the interface. Hope it helps.
For example, one of the simpler solutions could be
public class ViewModelProperties
{
private ObservableCollection<ServerProperties> properties = new ObservableCollection<ServerProperties>();
public ObservableCollection<ServerProperties> Properties
{
get
{
return properties;
}
}
public void SetProperties()
{
properties.Clear();
for (var lineNumber = 0; lineNumber < MainWindow.lineCount; lineNumber++)
{
if (MainWindow.textProperties[lineNumber, 0] == null) break;
properties.Add(new ServerProperties(MainWindow.textProperties[lineNumber, 0],
MainWindow.textProperties[lineNumber, 1]));
}
}
}
Any time you wish to add new items to OC, just call the SetProperties method.
My issue seems to be "scope", though I'm not certain that's the right terminology. I want to notify a read-only list to re-evaluate itself when a property within a custom object is set. I believe it is simply not aware of it's existence. Maybe there is an easy way around this I cannot think of, but I'm drawing a blank.
I find this hard to put into words, so here's simplified code with my comments on what I expect to happen.
Properties within object in which I am databinding to:
private CvarAspectRatios _aspectRatio = new CvarAspectRatios("none", GetRatio());
public CvarAspectRatios AspectRatio
{
get { return _aspectRatio; }
set
{ // This setter never gets hit since I bind to this
if (value != null) // object's 'Value' property now.
{
_aspectRatio = value;
NotifyPropertyChanged("AspectRatio");
NotifyPropertyChanged("ResolutionList"); // I want to inform ResolutionList
} // that it needs to repopulate based
} // on this property: AspectRatio
}
private ResolutionCollection _resolutionList = ResolutionCollection.GetResolutionCollection();
public ResolutionCollection ResolutionList
{
get
{
ResolutionCollection list = new ResolutionCollection();
if (AspectRatio != null && AspectRatio.Value != null)
{
foreach (Resolutions res in _resolutionList.Where(i => i.Compatibility == AspectRatio.Value.Compatibility))
{
list.Add(res);
}
return list;
}
return _resolutionList;
}
}
CvarAspectRatios Class:
public class CVarAspectRatios : INotifyPropertyChanged
{
private string _defaultValue;
public string DefaultValue
{
get { return _defaultValue; }
set { _defaultValue = value; NotifyPropertyChanged("DefaultValue"); }
}
private AspectRatios _value;
public AspectRatios Value
{
get { return _value; }
set
{
_value = value;
NotifyPropertyChanged("Value");
NotifyPropertyChanged("ResolutionList"); // This value gets set, and I'd like for ResolutionList to update
} // but it cannot find ResolutionList. No errors or anything. Just
} // no update.
public AspectRatios() { }
public AspectRatios(string defaultValue, AspectRatios val)
{
DefaultValue = defaultValue;
Value = val;
}
// Implementation of INotifyPropertyChanged snipped out here
}
What do you folks think? If you'd like a sample application I can whip one up.
Since CVarAspectRatios implements INotifyPropertyChanged, you can have the viewmodel class subscribe to the PropertyChanged event for the AspectRatio.
public class YourViewModel
{
public YourViewModel()
{
AspectRatio.PropertyChanged += AspectRatio_PropertyChanged;
}
void AspectRatio_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Value")
NotifyPropertyChanged("ResolutionList");
}
}
Just bear in mind that if you discard that AspectRatio object (if the object reference changes and not just the value property of that object), you should unsubscribe from the event on the discarded one.
To just transform your existing code into something which should work:
private CvarAspectRatios _aspectRatio; //No field initialization because that would not attach event handler, you could do it though and take care of the handler alone in the ctor
public CvarAspectRatios AspectRatio
{
get { return _aspectRatio; }
set
{
if (_aspectRatio != value) // WTH # "value != null"
{
_aspectRatio.PropertyChanged -= AspectRatio_PropertyChanged;
_aspectRatio = value;
_aspectRatio.PropertyChanged += new PropertyChangedEventHandler(AspectRatio_PropertyChanged);
NotifyPropertyChanged("AspectRatio");
}
}
}
void AspectRatio_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Value")
{
NotifyPropertyChanged("ResolutionList");
}
}
Why don't you factor out re-populating ResolutionList into a separate private method which gets called from the setter of AspectRatios?
If a list needs to update based on a changed property, the list (or a list manager object, for better encapsulation) would normally need to subscribe to the PropertyChanged event of the object hosting the property. If the list is itself a property of the same object, as in this case, it would be simpler and leaner for the property's setter to call a method that updates the list.
I have 10 properties where when each is set it increments the value of variable Value. When the value of Value is 10, the app will end. But it seems to be awkward to write the same condition into each of them like this:
int Value=0;
int A
{
set
{
a=value;
Value++;
if(Value>10) ... //check here
}
}
int B
{
set
{
b=value;
Value++;
if(Value>10) //check here again
}
}
How can I simplify checking its value?
You could make a private property for Value and in the setter of that property, if the value is set to above 10, exit the application.
private int value=0;
private int a, b;
public int A
{
set
{
this.a = value;
Value++;
}
get { return this.a; }
}
public int B
{
set
{
this.b = value;
Value++;
}
get { return this.b; }
}
private int Value
{
set
{
this.value = value;
if (this.value > 10)
{
// Exit.
}
}
get { return this.value; }
}
I'm assuming that your properties are in a class which has other responsibilities than exiting the program when the counter hits 10. If so, I would remove the logic of checking the counter and exiting the app from the class.
Maybe you can use an event. The subscriber to this event will be notified when Value hits 10 (you could actually make "10" configurable, but this is just a short example) and exit the app.
class YourClass {
public event ValueHandler ValueIs10;
public EventArgs e = null;
public delegate void ValueHandler(YourClass m, EventArgs e);
private int _value=0;
public int Value {
get {return _value;}
private set {
_value=value;
if(_value==10 && ValueIs10 != null) ValueIs10(this, e);
}
}
public int A
{
set
{
a=value;
Value++;
}
}
public int B
{
set
{
b=value;
Value++;
}
}
// ...
}
private int counter;
private int b;
private int a;
public int A
{
set
{
counter++;
a = value;
CheckCounter();
}
}
public int B {
set
{
counter++;
b = value;
CheckCounter();
}
}
public void CheckCounter()
{
if (counter>10)
{
//Do something
}
}
or make counter a property..
private int Counter
{
set
{
counter = value;
CheckCounter();
}
get
{
return counter;
}
}
and use this when incrementing..
Counter++;
Both answers from #Richard and #Emil are correct, but to make your code more expandable in the future, it's better to implement the built-in INotifyPropertyChanged interface.
class Data : INotifyPropertyChanged
{
private int _counter = 0;
public event PropertyChangedEventHandler PropertyChanged = (sender, arg) =>
{
// if (_counter > 10) Exit();
// from arg you can know what property is changed
// which is probably used for future
};
private int _number;
public int Number
{
get { return _number; }
set
{
//here is another unclear point in your question
//will the counter increases when setting a.Number = 100
//but it's already 100 before setting
if (_number != value)
{
_number = value;
PropertyChanged(this, new PropertyChangedEventArgs("Number"));
}
}
}
}
I created a property
public int PK_ButtonNo
{
get { return PK_ButtonNo; }
set { PK_ButtonNo = value; }
}
Now I want to add events to this property for value changing and changed.
I wrote two events. Here I want both the events to contain changing value as well as changed value.
i.e
When user implements the event. He must have e.OldValue, e.NewValue
public event EventHandler ButtonNumberChanging;
public event EventHandler ButtonNumberChanged;
public int PK_ButtonNo
{
get { return PK_ButtonNo; }
private set
{
if (PK_ButtonNo == value)
return;
if (ButtonNumberChanging != null)
this.ButtonNumberChanging(this,null);
PK_ButtonNo = value;
if (ButtonNumberChanged != null)
this.ButtonNumberChanged(this,null);
}
}
How will I get the changing value and changed value when I implement this event.
Add the following class to your project:
public class ValueChangingEventArgs : EventArgs
{
public int OldValue{get;private set;}
public int NewValue{get;private set;}
public bool Cancel{get;set;}
public ValueChangingEventArgs(int OldValue, int NewValue)
{
this.OldValue = OldValue;
this.NewValue = NewValue;
this.Cancel = false;
}
}
Now, in your class add the changing event declaration:
public EventHandler<ValueChangingEventArgs> ButtonNumberChanging;
Add the following member (to prevent stackoverflow exception):
private int m_pkButtonNo;
and the property:
public int PK_ButtonNo
{
get{ return this.m_pkButtonNo; }
private set
{
if (ButtonNumberChanging != null)
ValueChangingEventArgs vcea = new ValueChangingEventArgs(PK_ButtonNo, value);
this.ButtonNumberChanging(this, vcea);
if (!vcea.Cancel)
{
this.m_pkButtonNo = value;
if (ButtonNumberChanged != null)
this.ButtonNumberChanged(this,EventArgs.Empty);
}
}
}
The "Cancel" property will allow the user to cancel the changing operation, this is a standard in a x-ing events, such as "FormClosing", "Validating", etc...