Issue with property setter with an object and toolbox - c#

This code works fine, I can check if a timecode is valid when the
property changes its value. But the problem is that I cannot set the
value of this property in the toolbox, because of the new ValidTimeCodeEventArgs.
Is there a way to fix this so I could
change the value in the toolbox and check if it is correct?
[Browsable(true)]
public event EventHandler<ValidTimeCodeEventArgs> ValidTimeCode;
private string strTimeCode;
[Category("Custom")]
[Browsable(true)]
[DefaultValue(typeof(string), "00:00:00:00")]
public string StrTimeCode {
get {
return strTimeCode;
}
set {
if (value != null) {
ValidTimeCode(this, new ValidTimeCodeEventArgs(IsValidTimeCode(value, FrameRate, IsDropFrame)));
strTimeCode = value;
initValue = strTimeCode;
Invalidate();
}
}
}
public class ValidTimeCodeEventArgs : EventArgs {
public ValidTimeCodeEventArgs(bool isValid) {
IsValid = isValid;
}
public bool IsValid { get; }
}

Your ValidTimeCode event probably doesn't have any subscribers when you try to raise it. Try:
ValidTimeCode?.Invoke(this, new ValidTimeCodeEventArgs(IsValidTimeCode(value, FrameRate, IsDropFrame)));
This basically does a null check on the handler (it's null if no one subscribed to the event), then only raises it if it isn't null. See here for more info.

Related

Getting "Key cannot be null" after implementing INotifyPropertyChanged on C# object

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.

C# swap in Designer

I have my Control.When I change the properties of the control. I get this:
this.myLabel1.BorderShadow = true;
this.myLabel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
And I need to get this:
this.myLabel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.myLabel1.BorderShadow = true;
How to do so is done automatically in Form.Desinger.cs ?
If you say Why?
private bool BorderShadow_ = false;
public bool BorderShadow
{
get
{
return BorderShadow_;
}
set
{
if (Border_Style_ == BorderStyle.FixedSingle)
{
BorderShadow_ = value;
}
else
{
throw new ArgumentOutOfRangeException("BorderShadow", "BorderShadow can be true if BorderStyle=FixedSingle");
}
}
}
You could look into the ISupportInitialize interface. It allows you to skip the validity check when initializing your controls. For example (adapted from one of my projects):
public class MyControl : Control, ISupportInitialize
{
private bool _created = true;
public void BeginInit()
{
_created = false;
}
public void EndInit()
{
_created = true;
//check all your properties here too
}
private bool BorderShadow_ = false;
public bool BorderShadow
{
get
{
return BorderShadow_;
}
set
{
BorderShadow_ = value;
if (_created && Border_Style_ != BorderStyle.FixedSingle)
throw new ArgumentOutOfRangeException();
}
}
}
If I remember correctly, the VS designer will automatically add calls to BeginInit and EndInit for you as well.
I think they will appears in the same order as they are defined. So you can override with new old setting:
public new BorderStyle BorderStyle
{
get {return base.BorderStyle;}
set {base.BorderStyle = value;}
}
and then declare your BorderShadow setting.
The designer will always order the properties alphabetically and this can't be changed.
The sense of a property is that it is side effect free and that it can be changed at any time in any order. This means that if you have multiple properties which representing some kind of complex state and not all combinations are making sense, this error should not be reported while switching the property itself.
So to accomplish these problems you have two possibilities:
Like #Andrew already mentioned implement ISupportInitialize and take care if you are within this state.
Within the property setter call a method that checks if all settings currently made are making sense and perform the desired action only in this case:
public class MyControl : Control
{
private bool _BorderShadow;
private BorderStyle _BorderStyle;
public bool BorderShadow
{
get { return _BorderShadow; }
set
{
if(_BorderShadow != value)
{
_BordeShadow = value;
ApplyBorderShadowIfNeeded();
}
}
}
public BorderStyle BorderStyle
{
get { return _BorderStyle; }
set
{
if(_BorderStyle != value)
{
_BorderStyle = value;
ApplyBorderShadowIfNeeded();
}
}
}
private void ApplyBorderShadowIfNeeded()
{
if(_BorderStyle == BorderStyle.FixedSingle
&& _BorderShadow)
{
// ToDo: Apply the shadow to the border.
}
}
}

how to prevent infinite property change

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

How to use INotifyPropertyChanged on a property in a class within a class..?

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.

How to create an event on property changing and changed event in C#

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...

Categories

Resources