I'm trying to bind my Winforms UI to my ViewModel. I was able to successfully update my ViewModel on UI changes and vice versa. However, I can't seem to understand what is the use of "PropertyName" used in PropertyChangedEventHandler since whatever I put there, it will always work. I don't know if I've already mixed things up since I've read a lot of articles about architectural patterns (MVP,MVC,MVVM,and MVP-VM (which is the one I was trying to do now) ).
Here is the part of the concerned code:
ViewModel
public class AdditionViewModel:INotifyPropertyChanged
{
private string augend;
public string Augend
{
get { return augend; }
set {
if(augend != value)
{
augend = value;
OnPropertyChanged(new PropertyChangedEventArgs("ugend"));
}
}
}
private string addend;
public string Addend
{
get { return addend; }
set {
if (addend != value)
{
addend = value;
OnPropertyChanged(new PropertyChangedEventArgs("ddend"));
}
}
}
private string answer;
public string Answer
{
get { return answer; }
set {
if(answer != value)
{
answer = value;
OnPropertyChanged(new PropertyChangedEventArgs("nswer"));
}
}
}
public AdditionClass additionClass;
public AdditionViewModel(AdditionClass _additionClass)
{
additionClass = _additionClass;
}
public void Add()
{
//Some verifications first before inserting the value to the model class
this.Augend = "1";//Testing for from code to UI binding
additionClass.Augend = Double.Parse(Augend);
additionClass.Addend = Double.Parse(Addend);
//Somewhere here should implement the compute but since addition is a very simple task, no methods were called;
Answer = additionClass.Answer.ToString();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
MessageBox.Show(e.PropertyName);
handler(this, new PropertyChangedEventArgs(e.PropertyName));
}
}
}
Form:
private void Form1_Load(object sender, EventArgs e)
{
additionPresenter = new Presenter.AdditionPresenter(new ViewModel.AdditionViewModel(new Model.AdditionClass()));
additionViewModelBindingSource.DataSource = additionPresenter.additionViewModel;
}
private void button1_Click(object sender, EventArgs e)
{
additionPresenter.AddButtonClick();
}
Presenter:
public AdditionPresenter(AdditionViewModel _additionViewModel)
{
additionViewModel = _additionViewModel;
}
public void AddButtonClick()
{
additionViewModel.Add();
}
One of the auto-generated code from Designer (Binding on UI):
//
// textBox1
//
this.textBox1.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.additionViewModelBindingSource, "Addend", true));
this.textBox1.Location = new System.Drawing.Point(24, 41);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(100, 20);
this.textBox1.TabIndex = 0;
As can be seen on the ViewModel, I've omitted all the "A"s at the start of each PropertyName in the setters but the application is still working.
Sorry for the long code pastes. I can't seem to find a better explanation than just to show you the implementation
INotifyPropertyChanged is not necessary for data binding, but it enables two-way data binding.
In fact as mentioned in documentations: The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed.
In simple (one-way) data binding, when you change the bound property of control, value push into the bound property of your object and it doesn't need INotifyPropertyChanges.
But without INotifyPropertyChanged, if you change the value of bound property of your object using code, new value doesn't push into your control's bound property.
Having wrong property names in PropertyChanged event why do I still have two-way data-binding?
In fact it's because of using BindingSource as source of data-boinding, as mentioned by Fabio in comments.
When using BindingSource as data source of your data-bindings, it's enough for your objects to implement INotifyPropertyChanged and raise PropertyChaned event (even with empty or wrong property name) and then the BindingSource (actually its inner BindingList<T>) subscribes for PropertyChaned event and when received the event it checkes if you didn't passed a correct property name or if you passed empty property name it the will call ResetBindings() that consequencly causes a control bound to the BindingSource to reread all the items in the list and refresh their displayed values.
Correct names in PropertyChanged causes the normal behavior of two-way data-binding and also causes raising ListChanged event with correct property in e.PropertyDescriptor.
Related
What is the purpose of INotifyPropertyChanged. I know this event is fired whenever a property is changed but how can the View/UI knows that this event is fired:
Here is my Customer class that implements the INotifyPropertyChanged event:
public class Customer : INotifyPropertyChanged
{
private string _firstName;
public string LastName { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
PropertyChanged(this,new PropertyChangedEventArgs(propertyName));
}
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
OnPropertyChanged("FirstName");
}
}
}
But now how to notify the UI that property has changed. Like when the user assigns null or empty to the first name how can I display a MessageBox on the UI.
INotifyPropertyChanged allows WPF UI elements (via the standard data binding mechanisms) to subscribe to the PropertyChanged event and automatically update themselves. For example, if you had a TextBlock displaying your FirstName property, by using INotifyPropertyChanged, you can display it on a form, and it will automatically stay up to date when the FirstName property is changed in code.
The View just subscribes to the event - which tells it everything necessary. The event includes the name of the changed property, so if a UI element is bound to that property, it updates.
WPF can know because it can inspect whether an object implements this interface, then cast the object to said interface and register for the event. It can then trigger the Binding Infrastructure to update the display. If you want to react as well, you can register yourself for the same event.
EDIT: I reread your question and some of your comments. Here is a possible solution utilizing the DataContextChanged event and the INotifyPropertyChanged interface on your Customer object. You should also look into Data Binding Validation in WPF and .Net 3.5.
<TextBox Text="{Binding FirstName}" />
// assuming:
// myWindow.DataContext = new Customer();
myWindow.DataContextChanged += MyWindow_DataContextChanged;
private void MyWindow_DataContextChanged(object sender,
DependencyPropertyChangedEventArgs e)
{
var oldCustomer = e.OldValue as Customer;
if (oldCustomer != null)
{
oldCustomer.PropertyChanged -= Customer_CheckProps;
}
var newCustomer = e.NewValue as Customer;
if (newCustomer != null)
{
newCustomer.PropertyChanged += Customer_CheckProps;
}
}
private void Customer_CheckProps(object sender, PropertyChangedEventArgs e)
{
var customer = sender as Customer;
if (customer != null)
{
if (e.PropertyName == "FirstName"
&& String.IsNullOrEmpty(customer.FirstName))
{
// Display Message Box
}
}
}
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
I have a datagrid that I am binding to an ObservableCollection of a custom type. The type has a boolean property, "IsCopying". The "binding" is done by specifying the ItemSource property of the grid to the ObservableCollection.
I want to change data before it is bound to the grid. The grid just needs to be a read-only view of the data. I have changed the column header by using the AutoGeneratingColumn event:
if (e.Column.header.ToString() == "IsCopying")
{
DataGridTextColumn t = new DataGridTextColumn();
t.header = "Status";
e.Column = t;
}
...which works fine. I want to do the same thing during the binding of the individual property to the cell, of each row. I am thing it would work something like:
//NOT REAL CODE!!!!!!!
private void dgItem_CellBinding(object sender, DataGridCellBindingEventArgs e)
{
MyCustomType theItem = e.(MyCustomType)ObjectGettingBound;
if (theItem.IsCopying == true)
{
e.TypeGettingBound= DataGridCellType.Text //or however this works;
e.DataToBind = "Working...";
}else{
e.TypeGettingBound = DataGridCellType.Text;
e.DataToBind = "Waiting for command...";
}
}
Hopefully the above makes sense. I can not find the event for when the individual cell is binding and how to intercept it. I am sure this is very common but I'm new to this and can't find anything on SO that addresses this particular issue. Maybe I'm going about it the wrong way?
does your custom type implement INotifyPropertyChange?
can you add a public property to your custom type that raises PropertyChange event and exposes the text hat you need?
public class MyCustomType: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
private bool _IsCopying;
public bool IsCopying
{
get { return _IsCopying; }
set
{
_IsCopying = value;
OnPropertyChanged("IsCopyingText");
}
}
public string IsCopyingText
{
get
{
if(IsCopying) return "Working...";
else return "Waiting for command...";
}
}
}
then you bind the column to IsCopyingText property, and if you don't want IsCopying column to show, set its Cancel property to True in AutoGeneratingColumn event (http://msdn.microsoft.com/en-us/library/cc903950(v=vs.95).aspx)
to make grid readonly: http://msdn.microsoft.com/en-us/library/system.windows.controls.datagrid.isreadonly.aspx
Another way is to use a IValueConverter, like here:
WPF Datagrid - Column Binding related to other columns
When the Text property of a TextBox is bound to an object property which that object implements INotifyPropertyChanged, the event PropertyChanged may fire two times while having the same value:
1) when the text is changed inside the TextBox 2) when the control is leaving from it.
Consider these methods of a form:
private void Form1_Load(object sender, EventArgs e)
{
TextBox textBox = new TextBox();
TextBox secondTextBox = new TextBox();
secondTextBox.Location = new Point(0, 100);
this.Controls.Add(textBox);
this.Controls.Add(secondTextBox);
MyClass instance = new MyClass();
instance.PropertyChanged += instance_PropertyChanged;
textBox.DataBindings.Add("Text", instance, "Id", true, DataSourceUpdateMode.OnPropertyChanged);
}
private void instance_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine(e.PropertyName + " changed");
}
and the back-end class:
private class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
int _id;
public int Id
{
get
{
return _id;
}
set
{
_id = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Id"));
}
}
}
To reproduce the problem, type something in the upper textbox, check the console, and then enter the lower textbox and check again the console. Upon leaving, a property change is reported. Why?
The default value of Binding.DataSourceUpdateMode property is OnValidation. In this configuration the data source is being only updated when Validating event occurs. In your example you use OnPropertyChanged mode, so you additionally request update of the data source whenever the text is changed inside the TextBox.
It is the default behaviour i.e. the Binding class was implemented in this way. If you want more details, you can examine Binding.Target_PropertyChanged and Binding.Target_Validate methods with a reflector.
From my perspective this behaviour isn't a problem but you need to change the implementation of the setter in the following way:
set
{
if(_id != value)
{
_id = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Id"));
}
}
Even if we assume that the implementation of Binding class is wrong, I think that it is a good practise to check whether a value has changed before generating PropertyChanged event.
Based on Michal's answer, I found the solution in switching off the CausesValidation property of TextBox as:
textBox.CausesValidation = false;
In a windows forms application, a property change that triggers INotifyPropertyChanged, will result in the form reading EVERY property from my bound object, not just the property changed. (See example code below)
This seems absurdly wasteful since the interface requires the name of the changing property. It is causing a lot of clocking in my app because some of the property getters require calculations to be performed.
I'll likely need to implement some sort of logic in my getters to discard the unnecessary reads if there is no better way to do this.
Am I missing something? Is there a better way? Don't say to use a different presentation technology please -- I am doing this on Windows Mobile (although the behavior happens on the full framework as well).
Here's some toy code to demonstrate the problem. Clicking the button will result in BOTH textboxes being populated even though one property has changed.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace Example
{
public class ExView : Form
{
private Presenter _presenter = new Presenter();
public ExView()
{
this.MinimizeBox = false;
TextBox txt1 = new TextBox();
txt1.Parent = this;
txt1.Location = new Point(1, 1);
txt1.Width = this.ClientSize.Width - 10;
txt1.DataBindings.Add("Text", _presenter, "SomeText1");
TextBox txt2 = new TextBox();
txt2.Parent = this;
txt2.Location = new Point(1, 40);
txt2.Width = this.ClientSize.Width - 10;
txt2.DataBindings.Add("Text", _presenter, "SomeText2");
Button but = new Button();
but.Parent = this;
but.Location = new Point(1, 80);
but.Click +=new EventHandler(but_Click);
}
void but_Click(object sender, EventArgs e)
{
_presenter.SomeText1 = "some text 1";
}
}
public class Presenter : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _SomeText1 = string.Empty;
public string SomeText1
{
get
{
return _SomeText1;
}
set
{
_SomeText1 = value;
_SomeText2 = value; // <-- To demonstrate that both properties are read
OnPropertyChanged("SomeText1");
}
}
private string _SomeText2 = string.Empty;
public string SomeText2
{
get
{
return _SomeText2;
}
set
{
_SomeText2 = value;
OnPropertyChanged("SomeText2");
}
}
private void OnPropertyChanged(string PropertyName)
{
PropertyChangedEventHandler temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
}
The reason why all properties are being read when the event gets fired rests in the PushData method called on the binding object when the ProperyChanged event is fired. If you look at the stacktrace, you will notice that the PropValueChanged method of the internal object BindToObject is called, that in turn calls the Oncurrentchanged event on the BindingManager. The binding mechanism keeps track of the current item changes, but it doesn't do a more granular distinction. The "culprit" PushData method calls the getter on your properties (take a look at the code using reflector). So there is no way around it. That being said, as a rule of thumb, in the get and set accessors it is not recommended to do heavy processing, use separate get and set methods for that (if possible)
Also take a look at this article, and this comment in particular (http://www.codeproject.com/Messages/2514032/How-Binding-watches-control-properties-i-e-how-doe.aspx), that explains exactly how the propertychanged event gets fired, though it will not address your getter problem: http://www.codeproject.com/KB/database/databinding_tutorial.aspx?msg=2514032
An idea to explore is to delay the getter being called. You can achieve this by playing around with the ControlUpdateMode property of the binding. When this value is set to Never, the corresponding control will not update when there is a change. However, when you switch the value back to OnPropertyChanged, PushData method will be called, so the getters will be accessed. So considering your example this code will temporary prevent the textbox 2 to update:
void but_Click(object sender, EventArgs e)
{
txt2.DataBindings[0].ControlUpdateMode = ControlUpdateMode.Never;
_presenter.SomeText1 = "some text 1";
}
I'm testing subclassing binding like this and managing OnPropertyChanged, maybe helps you.
public class apBinding : Binding
{
public apBinding(string propertyName, INotifyPropertyChanged dataSource, string dataMember)
: base(propertyName, dataSource, dataMember)
{
this.ControlUpdateMode = ControlUpdateMode.Never;
dataSource.PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == this.BindingMemberInfo.BindingField)
{
this.ReadValue();
}
}
}
Now the problem that i find is that the control overwrites the value of the linked object,
so i modified to
public class apBinding : Binding
{
public apBinding(string propertyName, INotifyPropertyChanged dataSource, string dataMember)
: base(propertyName, dataSource, dataMember)
{
dataSource.PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.ControlUpdateMode = ControlUpdateMode.Never;
if (e.PropertyName == this.BindingMemberInfo.BindingField)
{
this.ReadValue();
}
}
}
then the first time propertychanges is called i disable controlupdate. and the control is correctly updated at the first run.