Two Ways DataBinding in Winforms - c#

I'm learning about databinding, I have a Class with one property, then I have another class with a combobox and 2 values "1 and 2", I've created an array object of my class with the property, so when combobox has 1 my textbox will give it a value to class[0].property, instead if I have 2 this happens class[1].property here is the code so you will understand better:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WindowsFormsApplication1
{
struct Class1
{
public string pollo { get; set; }
}
}
My second class:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
Class1[] prova = new Class1[2];
int a;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
a = Convert.ToInt32(comboBox1.SelectedItem) - 1;
prova[a].pollo = textBox1.Text;
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
a = Convert.ToInt32(comboBox1.SelectedItem) - 1;
textBox1.DataBindings.Add("Text", prova[a], "pollo", false, DataSourceUpdateMode.OnPropertyChanged);
textBox1.DataBindings.Clear();
}
private void Form1_Load(object sender, EventArgs e)
{
comboBox1.SelectedIndex = 0;
}
}
}
All works fine, but this is a one way databinding in fact I have to set the property with the click button and in this case there is no such difference between:
textBox1.DataBindings.Add("Text", prova[a], "pollo", false, DataSourceUpdateMode.OnPropertyChanged);
and
textBox1 = prova[a];
So why use a databinding? I mean how can I make it two ways so that automatically set my property?
Thanks

You have some problems in your code, preventing the binding from working correctly, and so obscuring the usefulness.
First, to be clear: the binding that is being set is between the currently selected Class1 object and the Text property of the TextBox. You are using the ComboBox to change the currently selected object to bind the TextBox to. I'm assuming you're aware of this, but I want to make sure.
Now, as far as the problems in the code go…
The most serious problem is that your data type Class1 is being declared as a struct, rather than as a class. The struct type is a value type, meaning whenever code needs an object reference, a copy of the value is boxed (stored in an instance of object). It's very important to understand that this boxed value is a copy. It is completely disconnected from the value you've stored in your array, so even if the binding were successfully set, changes to the object would not be reflected elsewhere in the code where you retrieve the object value from the array.
Almost as serious is that you clear the binding immediately after setting it. This completely negates the point of data binding, which is to allow the framework to automatically update property values based on changes in another object. So yes, in your code example there is literally no difference whatsoever between the set-binding-then-clear-binding operation and simply setting the property directly.
Either of these two problems is sufficient to prevent data binding from working in a useful way. But there is also a third problem…
Your Class1 type does not implement a property changed event. In Winforms, you can implement either an event with the name polloChanged (i.e. the property name, followed by the word Changed, spelled and capitalized exactly like that), or by implementing the INotifyPropertyChanged interface. Without either of these mechanisms, two-way data binding cannot work, because the framework has no way to know when the value of the property has changed. (Ironically, what does work is the target-to-source binding…that is, because the TextBox class does implement the TextChanged event, the data binding is able to set the source property when the target property changes. But it doesn't go the other way).
Here is a version of your code that takes full advantage of data binding, implementing Class1 correctly (as an actual class, and with the necessary polloChanged event), configuring the binding correctly, and binding the object's pollo property to a Label so that it's clear the object's pollo property is being updated:
class Class1
{
private string _pollo = "";
public string pollo
{
get { return _pollo; }
set
{
_pollo = value;
Raise(polloChanged, this);
}
}
private static void Raise(EventHandler handler, object sender)
{
if (handler != null)
{
handler(sender, EventArgs.Empty);
}
}
public event EventHandler polloChanged;
}
public partial class Form1 : Form
{
private Class1[] _prova =
{
new Class1 { pollo = "<not set 1>" },
new Class1 { pollo = "<not set 2>" }
};
public Form1()
{
InitializeComponent();
comboBox1.SelectedIndex = 0;
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
// Obviously in a more complicated data binding scenario, you might
// want to be more specific about which binding(s) is(are) being
// removed, rather than just clearing everything.
textBox1.DataBindings.Clear();
label1.DataBindings.Clear();
// If the user edits the text in the TextBox, the pollo property
// of the currently-selected object will be immediately updated
textBox1.DataBindings.Add("Text", _prova[comboBox1.SelectedIndex],
"pollo", false, DataSourceUpdateMode.OnPropertyChanged);
// We're never going to change the label1.Text property directly,
// so the binding doesn't ever need to update the source property.
label1.DataBindings.Add("Text", _prova[comboBox1.SelectedIndex],
"pollo", false, DataSourceUpdateMode.Never);
}
}
I assume you can infer the necessary textBox1, comboBox1, and label1 controls in the form, rather than having me post all the Designer code.
Finally, in case you prefer the INotifyPropertyChanged approach, here's what your Class1 would look like using that technique:
class Class1 : INotifyPropertyChanged
{
private string _pollo = "";
public string pollo
{
get { return _pollo; }
set
{
_pollo = value;
OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}

Related

Which are ALL the requirements for implementing simple data binding in a UserControl?

To create a custom control supporting simple data binding I followed the instructions reported by Microsoft here and shortly after I discovered they are incomplete. Implementing the DefaultBindingProperty attribute is not enough and it seems you need to have the control either (i) implementing INotifyPropertyChanged or exposing an event named <propertyname>Changed as explained here.
Still, my code (actually a very simplified version of it) here below does not work and I'm struggling for this since almost 1 day now. My control is:
[DefaultBindingProperty("Value")]
public partial class MyUserControl : UserControl
{
NumericUpDown upDown;
public MyUserControl()
{
InitializeComponent();
upDown = new NumericUpDown() { Minimum = 0, Maximum = 100, Dock = DockStyle.Fill };
Controls.Add(upDown);
upDown.ValueChanged += control_ValueChanged;
}
[Bindable(true)]
public double Value
{
get { return (double)upDown.Value; }
set { upDown.Value = (decimal)value; }
}
private void control_ValueChanged(object sender, EventArgs e)
{
if (ValueChanged != null)
ValueChanged(this, e);
}
public event EventHandler<EventArgs> ValueChanged;
}
and my test form:
public partial class Form1 : Form
{
MyUserControl myUserControl;
BindingSource source;
MyClass myClass;
public Form1()
{
InitializeComponent();
source = new BindingSource();
myClass = new MyClass() { Amount = 30 };
source.DataSource = new[] { myClass };
myUserControl = new MyUserControl();
Controls.Add(myUserControl);
myUserControl.DataBindings.Add("Value", source, "Amount", false, DataSourceUpdateMode.OnPropertyChanged);
}
}
public class MyClass
{
int amount;
public int Amount
{
get { return amount; }
set
{
amount = value;
MessageBox.Show("New amount: " + amount);
}
}
}
The messagebox at the end of the code is not fired when changing the value in the NumericUpDown. Is there another piece of information I'm missing? What am I doing wrong here?
it seems you need to have the control either implementing INotifyPropertyChanged or exposing an event named <propertyName>Changed as explained here.
These are the options when you implement a data source. For custom control the only option is the later, since the base control already is using that pattern, and implementing INotifyPropertyChanged will break it, because as stated in one of the notes here
For change notification to occur in a binding between a bound client and a data source your bound data-source type should either implement the INotifyPropertyChanged interface (preferred) or you can provide propertyNameChanged events for the bound type, but you shouldn't do both.
So you correctly used the PropertyNameChanged pattern (or at least tried :)
Is there another piece of information I'm missing? What am I doing wrong here?
Here are some related links - Change Notification in Windows Forms Data Binding and How to: Apply the PropertyNameChanged Pattern, but what you are missing and unfortunately seems to not to be explained anywhere in the documentation is what exactly PropertyNameChanged pattern is, and the key point is that in addition to the specially named event, it expects a specific type of that event, and more precisely, to be of type EventHandler. I guess now you see the problem - you have used EventHandler<EventArgs>. The method signature is one and the same, but the same can be said for Action<object, EvenetArgs>. EventHandler<TEventArgs> is now the recommended one, bur remember it didn't exist at the time data binding has been introduced. Etc.
Shorty, change this line
public event EventHandler<EventArgs> ValueChanged;
to
public event EventHandler ValueChanged;
and everything will work as expected.

How do I read a property from my main form in a user control

I have written a user control, MenuItem, which inherits from a Form Label.
I have a backgroundworker thread whose IsBusy property is exposed through a property in the MainForm as IsBackgroundBusy.
How do I read this property from the MenuItem usercontrol? I am currently using Application.UseWaitCursor and I set that in the backgroundworker and it works perfectly, however I do not want the cursor to change. That's why I figured a property that I could set would be much better.
Here is the code in my MainForm:
public partial class MainForm : Form
{
public bool IsBackgroundBusy
{
get
{
return bwRefreshGalleries.IsBusy;
}
}
Here is the code for my usercontrol:
public partial class MenuItem: Label
{
private bool _disableIfBusy = false;
[Description("Change color if Application.UseWaitCursor is True")]
public bool DisableIfBusy
{
get
{
return _disableIfBusy;
}
set
{
_disableIfBusy = value;
}
}
public MenuItem()
{
InitializeComponent();
}
protected override void OnMouseEnter( EventArgs e )
{
if ( Application.UseWaitCursor && _disableIfBusy )
{
this.BackColor = SystemColors.ControlDark;
}
else
{
this.BackColor = SystemColors.Control;
}
base.OnMouseEnter( e );
}
(Note: it's not clear to me whether you have an actual UserControl here or not. The MenuItem class you show inherits Label, not UserControl. You should probably avoid using the term "usercontrol" or "user control" when you are not actually dealing with a UserControl object).
Absent a complete code example, it's hard to know exactly what the right solution here is. However, assuming you are using the BackgroundWorker in a typical fashion, then you simply need for the owner of the control (i.e. the containing Form) to pass the necessary state to the control as it changes. E.g.:
class MenuItem : Label
{
public bool IsParentBusy { get; set; }
}
// I.e. some method where you are handling the BackgroundWorker
void button1_Click(object sender, EventArgs e)
{
// ...some other initialization...
bwRefreshGalleries.RunWorkerCompleted += (sender1, e1) =>
{
menuItem1.IsParentBusy = false;
};
menuItem1.ParentIsBusy = true;
bwRefreshGalleries.RunAsync();
}
If you already have a handler for the RunWorkerCompleted event, then just put the statement to set the IsParentBusy property there instead of adding another handler.
Then instead of using the Application.UseWaitCursor property, you can just look at the IsParentBusy property.
There are other mechanisms you could use; I do agree with the general sentiment that the MenuItem control should not be tied to your specific Form sub-class. If for some reason the above doesn't work in your case, you need to elaborate on your question: provide a good code example and explain exactly why simply having the container of the control manage its state directly doesn't work for you

Proper Form Application Design

I'm creating a WinForm application in C# and one of its functions is displaying text in text boxes. I'm coding the logic for querying a database in a separate class and am unable to access the text box element in the class I'm creating (I'm getting a "name" does not exist in the current context error). Do I put all of my form logic into my Form1.cs file?
You should try to keep your display logic separate from the rest of the application - the simplest thing to do is have the form class handle getting/setting form values. This means your data access component will query the database and the form will have to map the output to something that can be displayed e.g.
public class Form1 : Form
{
public DataAccess Db { get; set; }
public void UpdateSomething()
{
this.textbox.Text = this.Db.GetSomeDatabaseValue();
}
}
No keep Business logical apart from UI logic. You should raise an event in the Business class and catch it in the UI form. From there display it.
If they are not try setting the modifiers in properties to public or internal.
Edit- edited to fit answer format
If you want to access the TextBox in another class change the access modifier as
public or Internal (If it is in the same assembly)
.
Default it will be private
Better you can pass the value to the business logic layer.Not the entire control,would not be good always.
B.I is to do all the business so the value of text box is enough.
Have you looked at the backgroundworker? With this, you can run things asynchronously when you click a button on your form. All of your updating things on your form would be done on your form itself. Your other code (that you were having trouble accessing Form1 from) would 'report progress.' When progress is reported, you can send any object that you want to Form1 and then in an event handler on the form you can take information from that object and update the view. You would use this, for example, to update a progress bar while keeping the UI responsive.
We are currently doing an application with a MVP pattern in winforms. We are using the bindings in winforms so the UI will update when the data does. Our forms use BindingSources and BindingLists. We bind the main BindingSource to our presenter class.
Example of Form codebehind
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SomeNameSpace.Utilities.UI;
using SomeNameSpace.Utilities.Validation;
namespace Management.UI
{
public partial class ManualControl : UserControl, IManualRaffleView
{
private ManualPresenter _presenter;
public ManualControl()
{
InitializeComponent();
}
[Browsable(false)]
public ManualPresenter Presenter
{
get
{
return _presenter;
}
set
{
_presenter = value;
if(_presenter != null)
{
_manualPresenterBindingSource.DataSource = _presenter;
_ListBindingSource.DataSource = _presenter;
_ListBindingSource.DataMember = "Something";
_KindListBindingSource.DataSource = _presenter;
_KindListBindingSource.DataMember = "SomethingElse";
_presenter.CloseView += new Action(CloseMe);
_presenter.VerifyingCancel += new Func<bool>(VerifyingCancel);
_presenter.Showing += new Action(ShowMe);
_presenter.Synchronizer = this;
}
}
}
void CloseMe()
{
this.Enabled = false;
}
private void ManualRaffleForm_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = false;
}
private void ShowMe()
{
this.Enabled = true;
}
bool VerifyingCancel()
{
return MessageBox.Show("Cancel?", Notifier.ApplicationName,
MessageBoxButtons.YesNo, MessageBoxIcon.Question,
MessageBoxDefaultButton.Button2) == DialogResult.Yes;
}
private void _cancelButton_Click(object sender, EventArgs e)
{
Presenter.HandleCancel();
}
private void _initiateButton_Click(object sender, EventArgs e)
{
Presenter.HandleInitiate();
}
private void _saveButton_Click(object sender, EventArgs e)
{
if(Presenter.Error == true.ToString())
Presenter.HandleDone();
else
_manualPresenterBindingSource.ResetBindings(false);
}
}
}
Then our Presenter implements INotifyPropertyChanged and may look something like this
namespace SomeCompany.UI
{
public class ManualPresenter : INotifyPropertyChanged, IDataErrorInfo
{
#region Fields
//fields
#endregion Fields
public string SomeFormField
{ get{ return _someFormField;}
set{
if(_someFormField != value)
{
_someFormField = value;
//Update Model if Needed
_model.SomeFormField = _someFormField;
NotifyCHanged("SomeFormField");
}
}
}
The form text boxes will bind to the properties in the presenter and any listboxes or combo boxes will bind to BindingLists.
We then use Linq to Sql for our models. There is very little logic in the Forms. Mostly just a little that is necessary for validation.

Sending string from class to Form1

Although there are some similar questions I’m having difficulties finding an answer on how to receive data in my form from a class.
I have been trying to read about instantiation and its actually one of the few things that does make sense to me :) but if I were to instantiate my form, would I not have two form objects?
To simplify things, lets say I have a some data in Class1 and I would like to pass a string into a label on Form1. Is it legal to instantiate another form1? When trying to do so it looks like I can then access label1.Text but the label isn’t updating. The only thing I can think of is that the form needs to be redrawn or there is some threading issue that I’m unaware of.
Any insight you could provide would be greatly appreciated.
EDIT: Added some code to help
class Class1
{
public int number { get; set; }
public void Counter()
{
for (int i = 0; i < 10; i++)
{
number = i;
System.Threading.Thread.Sleep(5000);
}
}
}
and the form:
Class1 myClass = new Class1();
public Form1()
{
InitializeComponent();
label1.Text = myClass.number.ToString();
}
NO,
I think your form needs to access a reference to Class1 to use the data available in that, and not the other way around.
VERY seldomly a data class should instanciate a display form to display what it has to offer.
If the class is a data access layer of singleton, the form should reference that class, and not the other way around.
Based on the comments in your original post, here are a few ways to do it:
(this code is based on a Winform app in VS2010, there is 1 form, with a Label named "ValueLabel", a textbox named "NewValueTextBox", and a button.
Also, there is a class "MyClass" that represents the class with the changes that you want to publish.
Here is the code for the class. Note that I implement INotifyPropertyChanged and I have an event. You don't have to implement INotifyPropertyChanged, and I have an event that I raise whenever the property changes.
You should be able to run the form, type something into the textbox, click the button, and see the value of the label change.
public class MyClass: System.ComponentModel.INotifyPropertyChanged
{
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
public string AValue
{
get
{
return this._AValue;
}
set
{
if (value != this._AValue)
{
this._AValue = value;
this.OnPropertyChanged("AValue");
}
}
}
private string _AValue;
protected virtual void OnPropertyChanged(string propertyName)
{
if(this.PropertyChanged != null)
this.PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
Example 1: simple databinding between the label and the value:
So this time, we will just bind the label to an instance of your class. We have a textbox and button that you can use to change the value of the property in MyClass. When it changes, the databinding will cause the label to be update automatically:
NOTE: Make sure to hook up Form1_Load as the load event for hte form, and UpdateValueButton_Click as the click handler for the button, or nothing will work!
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private MyClass TheClass;
private void Form1_Load(object sender, EventArgs e)
{
this.TheClass = new MyClass();
this.ValueLabel.DataBindings.Add("Text", this.TheClass, "AValue");
}
private void UpdateValueButton_Click(object sender, EventArgs e)
{
// simulate a modification to the value of the class
this.TheClass.AValue = this.NewValueTextBox.Text;
}
}
Example 2
Now, lets bind the value of class directly to the textbox. We've commented out the code in the button click hander, and we have bound both the textbox and the label to the object value. Now if you just tab away from the textbox, you will see your changes...
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private MyClass TheClass;
private void Form1_Load(object sender, EventArgs e)
{
this.TheClass = new MyClass();
// bind the Text property on the label to the AValue property on the object instance.
this.ValueLabel.DataBindings.Add("Text", this.TheClass, "AValue");
// bind the textbox to the same value...
this.NewValueTextBox.DataBindings.Add("Text", this.TheClass, "AValue");
}
private void UpdateValueButton_Click(object sender, EventArgs e)
{
//// simulate a modification to the value of the class
//this.TheClass.AValue = this.NewValueTextBox.Text;
}
Example 3
In this example, we won't use databinding at all. Instead, we will hook the property change event on MyClass and update manually.
Note, in real life, you might have a more specific event than Property changed -- you might have an AValue changed that is only raised when that property changes.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private MyClass TheClass;
private void Form1_Load(object sender, EventArgs e)
{
this.TheClass = new MyClass();
this.TheClass.PropertyChanged += this.TheClass_PropertyChanged;
}
void TheClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "AValue")
this.ValueLabel.Text = this.TheClass.AValue;
}
private void UpdateValueButton_Click(object sender, EventArgs e)
{
// simulate a modification to the value of the class
this.TheClass.AValue = this.NewValueTextBox.Text;
}
}
There's no reason why you can't have multiple instances of a form, but in this case you want to pass the Class1 instance to the form you have. The easiest way is to add a property to Form1 and have that update the label text:
public class Form1 : Form
{
public Class1 Data
{
set
{
this.label.Text = value.LabelText;
}
}
}
Events and delegates are what you want to use here. Since you tagged this as beginner, I will leave the implementation as an exercise :), but here is the general idea:
Declare an event/delegate pair in your data class.
Create a method in your form to bind to the event.
When "something" happens in your data class, fire the event
The form's method will be invoked to handle the event and can update widgets appropriately
Good luck.

Non-abstract event defined in abstract class won't show in Designer for derived classes

I'm using the class listed below to create a UserControl wrapping a ComboBox that can accept a List<T> and return an object of type T when the internal ComboBox's selection is changed.
Everything works fine in code, exactly as I expect it to, but I can't get SelectedItemChanged event to show up in the Designer anymore when using my control. It worked fine when the abstract base class was non-abstract, but I'm trying to condense 5 essentially duplicate controls into one.
Unimportant parts have been snipped.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
namespace UserComboTest
{
public abstract partial class DropDownList<T> : UserControl where T : class
{
protected abstract int FindIndex(T item);
public abstract void Populate(List<T> items, T defaultItem);
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible), Browsable(true)]
public event EventHandler<SelectedItemEventArgs> SelectedItemChanged;
private void comboBox_SelectedIndexChanged(object sender, EventArgs e)
{
if (null != SelectedItemChanged)
{
SelectedItemChanged(this, new SelectedItemEventArgs(Selected));
}
}
public class SelectedItemEventArgs : EventArgs
{
public SelectedItemEventArgs(T selectedItem)
{
Selected = selectedItem;
}
public T Selected { get; private set; }
}
}
public class UserDropDownList : DropDownList<User>
{
protected override int FindIndex(User user)
{
// find index for item
}
public override void Populate(List<User> users, User defaultUser)
{
// populate the list
}
}
}
EDIT: Fixed the code-breaking problem. Turned out both my namespace and form were named UserComboTest, so when it serialized the fully-qualified type name (UserComboTest.UserDropDownList), it assumed that it was a member or class under the form, not the namespace. In other words, it thought it was looking for UserComboTest.UserComboTest.UserDropDownList, which doesn't exist. Renaming the form to UserComboTest.UserComboTestForm solved that half of the problem.
Still remaining is the fact that the designer doesn't show the SelectedItemChanged event, and if I set it manually, it gets removed, so I either have to set it outside of InitializeComponent, or figure out how to get it to be serialized.
Generally, the winforms designer reacts badly to abstract base classes. You should turn the abstract methods into empty virtual methods and make the class non-abstract.

Categories