Two way databinding in winforms, Inotifypropertychanged implemented in base class - c#

I use Winforms Databinding and I have derived classes, where the base class implements IPropertychanged :
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName) {
var handler = this.PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Each propertysetter calls:
protected void SetField<T>(ref T field, T value, string propertyName) {
if (!EqualityComparer<T>.Default.Equals(field, value)) {
field = value;
IsDirty = true;
this.RaisePropertyChanged(propertyName);
}
}
A typical Propertysetter:
public String LocalizationItemId {
get {
return _localizationItemId;
}
set {
SetField(ref _localizationItemId, value, "LocalizationItemId");
}
}
The way a property is bound to a textbox
private DerivedEntity derivedEntity
TextBoxDerivedEntity.DataBindings.Add("Text", derivedEntity, "Probenname");
If I programmatically assign text to the textbox, the textbox does not show it. But I can manually edit the textbox.

I know it is too late to answer, but this problem can be solved, if you set event when your binding should change value, if you set it on property value change event your problem will be solved. You can do this by this way
textBox.DataBindings.Add("textBoxProperty", entity, "entityProperty", true, DataSourceUpdateMode.OnPropertyChanged);

Binding source is updated on TextBox Validated event. TextBox validated event is called when user edit TextBox and then changes focus to other control.
Since you're changing TextBox text programmatically TextBox doesn't know that text were changed and therefore validation is not called and binding is not updated, so you need to update binding manually.
Initialize binding:
var entity;
textBox.DataBindings.Add("textBoxProperty", entity, "entityProperty");
Change TextBox.Text:
textBox.Text = "SOME_VALUE";
Update binding manually:
textBox.DataBindings["textBoxProperty"].WriteValue();
Binding.WriteValue() reads value from control and updates entity accordingly.
You could read about WriteValue at MSDN.

The subscriber isn't initialized. i.e.
private DerivedEntity derivedEntity
TextBoxDerivedEntity.DataBindings.Add("Text", derivedEntity, "Probenname");
derivedEntity is null.
Initialize it and you'll be fine.

I implemented the "INotifyPropertyChanged", but raise the PropertyChanged event only when the new value is different from the old value:
public class ProfileModel : INotifyPropertyChanged
{
private Guid _iD;
private string _name;
public event PropertyChangedEventHandler PropertyChanged;
public Guid ID
{
get => _iD;
set
{
if (_iD != value)
{
_iD = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ID"));
}
}
}
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
}
}
}
}
Now just bind to the controls:
txtProfileID.DataBindings.Clear();
txtProfileID.DataBindings.Add("Text", boundProfile, "ID", true, DataSourceUpdateMode.OnPropertyChanged);

Related

Execute method in viewmodel when property changed

I have some ViewModel with string property Name
My ViewModel inherit from ViewModelBase : INotifyPropertyChanged
...
private string _name;
public string Name
{
get { return _name; }
set { SetField(ref _name, value, "Name"); }
}
...
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
I want to execute some logic when my property Name change its value (on user input). My ViewModel will have alot of such properies and i will have alot of ViewModels with same property processing.
How should i run method that process chaneged propery in a right way?
Should i subscribe on PropertyChanged event in my ViewModel, then use switch on string property name to detect actual changed property and then use it value?
Or should i just run my method from setter?
Is there any patterns for such interactions?
Since your SetField method returns true if the property has changed, I would call my method in the setter if true is returned.
...
public string Name
{
get { return _name; }
set
{
if (SetField(ref _name, value, "Name"))
MyMethod();
}
}
...

DataBinding Radio Buttons

I will try to keep this concise as possible.
I am having an issue where I have a two way databinding with 3 radio buttons. My issue is, I am getting sort of a cyclical change as soon as I change the chosen radio button. So what is happening, I change the radio button which changes the source data property, but the source data property changes the other 2 properties that are bound, which in turn changes the other radio buttons, which calls the PropertyChange function on those properties as well. How can I fix it so that the PropertyChange only happens once per radio button switch.
Property Changed Event Handler:
public class SolutionOptions : INotifyPropertyChanged
{
bool _direct;
bool _iterative;
bool _domain;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public bool Domain
{
get { return _domain; }
set
{
if (_domain == value) return;
_domain = value;
OnPropertyChanged("Domain");
}
}
public bool Iterative
{
get { return _iterative; }
set
{
if (_iterative == value) return;
_iterative = value;
OnPropertyChanged("Iterative");
}
}
public bool Direct
{
get { return _direct; }
set
{
if (_direct == value) return;
_direct = value;
OnPropertyChanged("Direct");
}
}
Data Binding Code:
this.radioButton_DirectSolver.DataBindings.Add("Checked", _ssf, "SolOptions.Direct");
this.radioButton_IterSolver.DataBindings.Add("Checked", _ssf, "SolOptions.Iterative");
this.radioButton_DomainDecomp.DataBindings.Add("Checked", _ssf, "SolOptions.Domain");
Form Image:
First try adding this to all your setters
if (_domain == value) return;
So you don't call OnPropertyChanged if it does not change.
This is a good practice in general.
Second (only if the first does not work) put each button in a separate group so they don't call each other and handle setting others to false in your code behind.

WPF: UI not updated on value change in bound object

I am relatively new to WPF and having a problem with data binding. I am binding a dependency property of a user control to a class property in my code behind. During intantiation of the class entity in my code behind the UI is sucessfully updated through INotifyPropertyChanged. However when subsequently changing the value in my code behind the OnPropertyChangedEventHandler fires, but the OnPropertyChanged method does no longer answer to this. Below the details. It would be great if someone could give me some hints what I am doing wrong.
I implemented a user control that I am binding to a property CurrentAccProp.DiscountRate of my partial class in code behind:
<local:doubleUEdit x:Name="InterestRate" LabelField="Interest rate" MinimumValue="0" MaximumValue="1" FormatStringForNumbers="P2" IncrementSize="0.01" UncertainValue="{Binding ElementName=RibbonWindow, Path=CurrentAccProp.DiscountRate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The class of which CurrentAccProp is an instance implements INotifyPropertyChanged to inform the UI about value changes
//Event to inform data grid about changes
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
OnPropertyChanged is called in the setter for the DiscountRate property:
doubleU discountingrate;
public doubleU DiscountRate
{
get {return discountingrate;}
set
{
discountingrate = value;
OnPropertyChanged("DiscountingRate");
}
}
The property of my user control that I am binding to is implemented as a dependency property:
//Property for data binding to doubleU
[Description("The formatstring for the double boxes"), Category("Default")]
public doubleU UncertainValue
{
get { return new doubleU(0, 0, (double)doubleUSupremum.Value, (double)doubleUSupremum.Value); }
set { doubleURangeSlider.LowerValue = value.Interval.Infimum; doubleURangeSlider.HigherValue = value.Interval.Supremum; doubleUInfimum.Value = value.Interval.Infimum; doubleUSupremum.Value = value.Interval.Supremum; }
}
public static readonly DependencyProperty UncertainValueProperty =
DependencyProperty.Register(
"UncertainValue",
typeof(doubleU),
typeof(doubleUEdit),
new PropertyMetadata(default(doubleU), OnItemsPropertyChanged));
private static void OnItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
doubleUEdit MydblUEdt = d as doubleUEdit;
MydblUEdt.UncertainValue = e.NewValue as doubleU;
}
When I am instantiating CurrentAccProp in my code behind the OnPropertyChanged informs the UI and the value is updated.
AccountingProperties currentaccprop = new AccountingProperties(new doubleU(0.0));
public AccountingProperties CurrentAccProp { get { return currentaccprop; } set { currentaccprop = value; } }
However, when I later update the value of DiscountRate
CurrentAccProp.DiscountRate = new doubleU(1.0);
OnPropertyChanged gets executed, but the UI is no longer updated. Does anyone have a clue what I am doing wrong here?
The typo pointed out by HighCore and zaknotzach was indeed the problem. Thanks for your help! I implemented the approach in the thread referenced by HighCore to avoid this and it works like a charm. Below the changed AccountingProperties class from which CurrentAccProp is instantiated for reference:
public class AccountingProperties : INotifyPropertyChanged
{
doubleU discountrate;
public doubleU DiscountRate
{
get {return discountrate;}
set { SetField(ref discountrate, value, () => DiscountRate); }
}
//------------------------------------------------
//constructors
public AccountingProperties(doubleU discountrate)
{
DiscountRate = discountrate;
}
//Event to inform data grid about changes
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
if (selectorExpression == null)
throw new ArgumentNullException("selectorExpression");
MemberExpression body = selectorExpression.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The body must be a member expression");
OnPropertyChanged(body.Member.Name);
}
protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(selectorExpression);
return true;
}
}
You need to first change the string in
OnPropertyChanged("DiscountingRate");
to "DiscountRate". The string you are giving your OnPropertyChanged function must match the property name. That is most likely the issue you are having.
As already answered, the problem is OnPropertyChanged("DiscountingRate"); providing the event with an incorrect property name.
In order to prevent errors like this, you can avoid using string literals all together. In your OnPropertyChanged parameter, use CallerMemberName. You can modify your OnPropertyChanged signature to
public void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
// Do your stuff
}
Then in your setters, you just call this.OnPropertyChanged();. The method will be given the property name that was changed.
public doubleU DiscountRate
{
get {return discountingrate;}
set
{
discountingrate = value;
OnPropertyChanged();
}
}
The benefit to this is that you can refactor your code and not worry about breaking your property changed events.

Bound UserControl is not updating data source

I have a UserControl that has a Textbox, Button, and a Tooltip controls on it. It does implement INotifyPropertyChanged I have tried overriding the Text property and adding my own property, but in all cases the control reads from the bound data source fine, but never updates the data source. My events are raised when the text is changed. Some of the code is below. All other standard controls are working fine. What do I need to get the control to update the data source when the user has entered or changed the value?
public partial class UrlControl : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[Bindable(true)]
[Browsable(true)]
public string Url
{
get
{
return url.Text;
}
set
{
if (value != url.Text)
{
url.Text = value;
OnPropertyChanged("Url");
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
This is the binding code from the form designer.
this.urlControl1.DataBindings.Add(new System.Windows.Forms.Binding("Url", this.customerBindingSource, "First", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
}
INotifyPropertyChanged is for datasources. It allows your datasource to notify bound controls and other listeners of property changes. However, controls themselves use a different mechanism. It's a bit strange: you create events on your control with the naming convention <PropertyName>Changed. When the value of a property changes, you raise the associated event.
Example:
public string Url
{
get { return url.Text; }
set
{
if (value != url.Text)
{
url.Text = value;
OnUrlChanged(); // raise event
}
}
}
public event EventHandler UrlChanged;
private void OnUrlChanged()
{
// raise the UrlChanged event
if (UrlChanged != null)
UrlChanged(this, new EventArgs());
}
That's all you need to do. The Databinding Fairies will see that event and hook it up when you create the binding.
Here's the topic on MSDN: How to: Apply the PropertyNameChanged Pattern
This should work well for reading values from the datasource.
However, when it comes to writing values to the datasource it looks like you're storing and getting the Url value directly from the url textbox. However, you're not raising property change notifications when the textbox's text is changed within the UI. To fix this, add a TextChanged event handler on the textbox, which can simple call:
void url_TextChanged(object sender, EventArgs e)
{
OnPropertyChanged("Url");
OnUrlChanged(); // See additional note below
}
As a side, although implementing INotifyPropertyChanged should work... When it comes to Windows Forms binding you can also create an event with the property name suffixed with "Changed" and the binding should watch that:
public event EventHandler UrlChanged;
protected virtual void OnUrlChanged()
{
var handler = UrlChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
[Bindable(true)]
[Browsable(true)]
public string Url
{
get
{
return url.Text;
}
set
{
if (value != url.Text)
{
url.Text = value;
OnPropertyChanged("Url");
OnUrlChanged();
}
}
}

Binding property to control in Winforms

What is the best way to bind a property to a control so that when the property value is changed, the control's bound property changes with it.
So if I have a property FirstName which I want to bind to a textbox's txtFirstName text value. So if I change FirstName to value "Stack" then the property txtFirstName.Text also changes to value "Stack".
I know this may sound a stupid question but I'll appreciate the help.
You must implement INotifyPropertyChanged And add binding to textbox.
I will provide C# code snippet. Hope it helps
class Sample : INotifyPropertyChanged
{
private string firstName;
public string FirstName
{
get { return firstName; }
set
{
firstName = value;
InvokePropertyChanged(new PropertyChangedEventArgs("FirstName"));
}
}
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
#endregion
}
Usage :
Sample sourceObject = new Sample();
textbox.DataBindings.Add("Text",sourceObject,"FirstName");
sourceObject.FirstName = "Stack";
A simplified version of the accepted answer that does NOT require you to type names of properties manually in every property setter like OnPropertyChanged("some-property-name"). Instead you just call OnPropertyChanged() without parameters:
You need .NET 4.5 minimum.
CallerMemberName is in the System.Runtime.CompilerServices namespace
public class Sample : INotifyPropertyChanged
{
private string _propString;
private int _propInt;
//======================================
// Actual implementation
//======================================
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
//======================================
// END: actual implementation
//======================================
public string PropString
{
get { return _propString; }
set
{
// do not trigger change event if values are the same
if (Equals(value, _propString)) return;
_propString = value;
//===================
// Usage in the Source
//===================
OnPropertyChanged();
}
}
public int PropInt
{
get { return _propInt; }
set
{
// do not allow negative numbers, but always trigger a change event
_propInt = value < 0 ? 0 : value;
OnPropertyChanged();
}
}
}
Usage stays the same:
var source = new Sample();
textbox.DataBindings.Add("Text", source, "PropString");
source.PropString = "Some new string";
Hope this helps someone.

Categories

Resources