I am trying to bind some RadioButtons to booleans in a class which in-turn enable/disable other elements on the form. For example:
x radioButton1
x checkBox1
x radioButton2
x checkBox2
I want to enable checkBox1 only when radioButton1 is selected and likewise for radioButton2 and checkBox2.
When I try to bind these it takes two clicks to change a RadioButton selection. It seems like the order of binds is causing a logic issue.
Here is code that shows this. The form is just two default named RadioButtons and two CheckBoxes.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
BindingSource bindingSource = new BindingSource(new Model(), "");
radioButton1.DataBindings.Add(new Binding("Checked", bindingSource, "rb1Checked", true, DataSourceUpdateMode.OnPropertyChanged));
radioButton2.DataBindings.Add(new Binding("Checked", bindingSource, "rb2Checked", true, DataSourceUpdateMode.OnPropertyChanged));
checkBox1.DataBindings.Add(new Binding("Enabled", bindingSource, "cb1Enabled", true, DataSourceUpdateMode.OnPropertyChanged));
checkBox2.DataBindings.Add(new Binding("Enabled", bindingSource, "cb2Enabled", true, DataSourceUpdateMode.OnPropertyChanged));
}
}
public class Model : INotifyPropertyChanged
{
private bool m_rb1Checked;
public bool rb1Checked
{
get { return m_rb1Checked; }
set
{
m_rb1Checked = value;
NotifyPropertyChanged("cb1Enabled");
}
}
private bool m_rb2Checked;
public bool rb2Checked
{
get { return m_rb2Checked; }
set
{
m_rb2Checked = value;
NotifyPropertyChanged("cb2Enabled");
}
}
public bool cb1Enabled { get { return rb1Checked; } }
public bool cb2Enabled { get { return rb2Checked; } }
public Model()
{
rb1Checked = true;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string fieldName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(fieldName));
}
}
#endregion
}
Anyone see a way to make this work?
This appears to be a bug and won't be fixed:
http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/5735dac2-63e9-4797-80af-91969bf4d16e/
As a workaround, I "manually" hooked up the binding like this:
// Set initial values
radioButton1.Checked = model.Checked;
radioButton2.Checked = model.Checked;
// Change on event
radioButton1.CheckedChanged += delegate { model.rb1Checked = radioButton1.Checked; };
radioButton2.CheckedChanged += delegate { model.rb2Checked = radioButton2.Checked; };
// These stay the same
checkBox1.DataBindings.Add(new Binding("Enabled", bindingSource, "cb1Enabled", true, DataSourceUpdateMode.OnPropertyChanged));
checkBox2.DataBindings.Add(new Binding("Enabled", bindingSource, "cb2Enabled", true, DataSourceUpdateMode.OnPropertyChanged));
For me BT's answer was not enough. I had to add manual checking of which button was clicked, otherwise it still needed 2 clicks for radiobuttons.
this.ViewModel = new FancyClassViewModel();
this.radioButton1.CheckedChanged +=
delegate { this.ViewModel.Radio1Checked = this.radioButton1.Checked; };
this.radioButton2.CheckedChanged +=
delegate { this.ViewModel.Radio2Checked = this.radioButton2.Checked; };
this.ViewModel.PropertyChanged += (sender, e) =>
{
if (e.PropertyName == "Radio1")
{
this.radioButton1.Checked = this.ViewModel.Radio1Checked;
}
if (e.PropertyName == "Radio2")
{
this.radioButton2.Checked = this.ViewModel.Radio2Checked;
}
};
Related
I have datagrid of objects (CopyObject). Each object contains combo box with list of another objects (PGroupGridObject) and a checkbox.
To each PGroupGridObject list in CopyObject i add dummy PGroupGridObject with the name "All".
In PGroupGridObject class i add an event that triggered if the "All" group is checked.
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
OnPropertyChanged("IsChecked");
if (PGroupName.Equals("All"))
{
pGroupGridObjectEvent.pGroupGridObject = this;
MyCustomEvent?.Invoke(this, pGroupGridObjectEvent);
}
}
}
the event in PGroupGridObject:
public delegate void MyEventHandlerPGroup(object sender, MyEventArgsPGroup args);
public class MyEventArgsPGroup : EventArgs
{
public PGroupGridObject pGroupGridObject { get; set; }
}
public class PGroupGridObject : ViewModelBase
{
public string coName;
public event EventHandler<MyEventArgsPGroup> MyCustomEvent;
MyEventArgsPGroup pGroupGridObjectEvent = new MyEventArgsPGroup();
...
...
}
In CopyObject constructor I'm checking this event when add groups to the list.
foreach (PGroupGridObject pGroup in pGroups)
{
PGroupGridObject p = new PGroupGridObject(pGroup.Object);
p.MyCustomEvent += (o, e) =>
{
foreach (PGroupGridObject curpg in pGroups)
curpg.IsChecked = true;
};
this.PGroups.Add(p);
}
When i'm debugging it i can see that at the end of the action the groups IsChecked field is set to true , but i don't see it in the grid. seems that the OnPropertyChanged("IsChecked") not working in that case.
What i'm missing here ?
In my application, I will need to update a certain image when the selection of my combobox changes. Currently I do this by capturing the SelectedIndexChanged event and update the image there.
I have been trying another approach to do it, namely, using Binding in order to simplify my code, but it doesn't work.
Because the binding of ComboBox.SelectedItem to a certain property only applies after the ComboBox loses focus or when another control gains focus-- not when or before the SelectedIndexChanged event is fired.
How to get the binding to apply immediately when the SelectedItem or the SelectedIndex is changed?
This is my code:
public partial class Form1 : Form
{
private readonly Person person;
public Form1()
{
InitializeComponent();
person = new Person()
{
Name = "joe",
Race = Race.Indian
};
textBox1.DataBindings.Clear();
textBox1.DataBindings.Add(nameof(textBox1.Text), person, nameof(person.Name));
comboBox1.DataSource = Enum.GetValues(typeof(Race));
comboBox1.DataBindings.Clear();
comboBox1.DataBindings.Add(nameof(comboBox1.SelectedItem), person, nameof(person.Race));
comboBox1.SelectedIndexChanged += ComboBox1_SelectedIndexChanged;
}
private void ComboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
//when selected index is changed, the data is lagging behind the selected item.
//eg: SelectedItem is already Chinese, but person.Race is still Indian
MessageBox.Show($"data {person.Race}, selected item {comboBox1.SelectedItem}");
}
private int count = 0;
private void button1_Click(object sender, EventArgs e)
{
person.Race =(Race)(((int)person.Race + 1) % Enum.GetValues(typeof(Race)).Length);
person.Name = person.Name + $"{count++}";
}
}
public enum Race
{
Indian,
Chinese,
Causacian,
Arabian,
Jewish,
SouthINdian,
}
public class Person: INotifyPropertyChanged
{
private Race _race;
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public Race Race
{
get
{
return _race;
}
set
{
_race = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs (nameof(Race)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
To solve the problem, you can bind to SelectedValue property:
comboBox1.DataSource = Enum.GetValues(typeof(Race)).Cast<Race>()
.Select(x => new { Value = x, Display = x.ToString() })
.ToList();
comboBox1.DisplayMember = "Display";
comboBox1.ValueMember = "Value";
comboBox1.DataBindings.Add(nameof(comboBox1.SelectedValue), person, nameof(person.Race),
true, DataSourceUpdateMode.OnPropertyChanged);
I read some about DataBinding, mostly complicated things like SQL or whatever XAML and stuff.
All I want my programm to do is, if the "value" of a variable changes just write it in a textbox or label. (using WindowsForms)
So far what I have:
namespace DataBinding_Test
{
public partial class Form1 : Form
{
BindingSource bs = new BindingSource();
Class1 test = new Class1();
public Form1()
{
InitializeComponent();
test.name = "Hello";
bs.DataSource = test;
label1.DataBindings.Add(new Binding("Text", bs, "name", false, DataSourceUpdateMode.OnPropertyChanged));
}
private void button1_Click(object sender, EventArgs e)
{
test.name = textBox1.Text;
}
}
}
Class1 just has a public property name. On startup lable1 will show my "Hello" string. Then on button click the name property will change. On debug I saw the actual DataSource of "bs" contains the new property value, but the label will not show anything...
Is there any realtivly easy way to do this?
The Backround is: periodically there will be a polling of sensor data throug RS232. If the value of one sensor changes I want to show this in label or textbox. Now a backroundthreaded timer will need invokes and stuff to access the GUI thread; thought this would be easier with databinding but seems not :P
Thanks to all, great site, great work! :)
Another way to make things work without implementing INotifyPropertyChanged
class Class1
{
private string name;
public string Name
{
get { return name; }
set
{
//Check if you are assigning the same value. Depends on your business logic
//here is the simplest check
if (Equals(name, value))
return;
name = value;
OnNameChanged();
}
public event EventHandler NameChanged;
protected virtual void OnNameChanged()
{
var handler = NameChanged;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
}
The trick is to have event with the name combined by name of property and Changed suffix and to raise it whenever value of your property is changed
In order your code would work you should implement INotifyPropertyChanged interface in your binded class. Without it your binding simply doesn't know, when the change occures. There you should implenent the logic, according to which you would notify your subscribers about which when something changed in your class (the setter part) and what has changed (PropertyChangedEventArgs). See example for your class:
class Class1: INotifyPropertyChanged
{
private string name = "";
public string Name
{
get { return name; }
set { name = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged()
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
And change the property name from "name" to "Name" in your binding:
label1.DataBindings.Add(new Binding("Text", bs, "Name", false, DataSourceUpdateMode.OnPropertyChanged));
// create winforms project on form1 drag a textbox (testbox1)
// and a button (button1) with a button click event handler
// this updates the textbox when the button is clicked
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;
namespace WindowsFormsApplication3
{
public partial class Form1 : Form
{
MyClass Myobj = new MyClass();
public Form1()
{
InitializeComponent();
/* propertyname, datasource, datamember */
textBox1.DataBindings.Add("Text", Myobj, "Unit");
}
public class MyClass : INotifyPropertyChanged
{
private int unit = 3;
/* property change event */
public event PropertyChangedEventHandler PropertyChanged;
public int Unit
{
get
{
return this.unit;
}
set
{
if (value != this.unit)
{
this.unit = value;
NotifyPropertyChanged("Unit");
}
}
}
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
private void button1_Click(object sender, EventArgs e)
{
Myobj.Unit += 4;
}
}
}
I created an extension method for this that I would like to share
Usage
private void Form1_Load(object sender, EventArgs e)
{
ResultLabel.Bind(NameTextBox);
WarningLabel.Bind(NameTextBox,i => i.Length == 0 ? "field required!" : "");
SendButton.Bind(NameTextBox, i => SendButton.Enabled = !(i.Length == 0));
}
Extension
public static class Extention
{
public static void Bind(this Control owner, Control dataSource)
{
List<EventInfo> fields = dataSource.GetType().GetEvents().ToList();
int index = fields.FindIndex(item => item.Name == "TextChanged");
if (index >= 0)
{
Control sender = dataSource as Control;
owner.Text = dataSource.Text;
dataSource.TextChanged += delegate (Object o, EventArgs e) { owner.Text = sender.Text; };
}
}
public static void Bind(this Control owner, Control dataSource, Func<string,string> onChange)
{
List<EventInfo> fields = dataSource.GetType().GetEvents().ToList();
int index = fields.FindIndex(item => item.Name == "TextChanged");
if (index >= 0)
{
Control sender = dataSource as Control;
owner.Text = onChange(sender.Text);
dataSource.TextChanged += delegate (Object o, EventArgs e) { owner.Text = onChange(sender.Text); };
}
}
public static void Bind(this Control owner, Control dataSource, Action<string> onChange)
{
List<EventInfo> fields = dataSource.GetType().GetEvents().ToList();
int index = fields.FindIndex(item => item.Name == "TextChanged");
if (index >= 0)
{
Control sender = dataSource as Control;
onChange(sender.Text);
dataSource.TextChanged += delegate (Object o, EventArgs e) { onChange(sender.Text); };
}
}
}
I'm not sure if that is what you want but you can can write whatever you variable contains into the Textbox or Label by using the control.Text property.
textBox1.Text ="Some other Text"
or
string variable = "Hello 2";
textBox1.Text = variable;
Why dou you want to use Databinding? Its mutch easier this way.
Just getting started with data binding in C# and looking for some help. The below binding statements break (Visible property stops toggling with MyBool & MyBoolInverse) when the line binding SelectedItem of the combo box to MyEnumVar of the BusinessObject executes. Binding directly to the object instead of the BindingSource, or binding to SelectedValue instead of SelectedItem, has the same effect. Further, the value of MyEnumVar doesn't change with selections to the combo box. What am I doing wrong?
public partial class Form1 : Form
{
BindingSource bs = new BindingSource();
private BusinessObject bo = new BusinessObject();
public Form1()
{
InitializeComponent();
bs.DataSource = bo;
// Checkbox determines what type of dialog to display.
boolCheckBox.DataBindings.Add("Checked", bs, "MyBool", true,
DataSourceUpdateMode.OnPropertyChanged);
trueBox.DataBindings.Add("Visible", bs, "MyBoolInverse");
falseComboBox.DataBindings.Add("Visible", bs, "MyBool");
falseBox.DataBindings.Add("Visible", bs, "MyBool");
falseButton.DataBindings.Add("Visible", bs, "MyBool");
myEnumComboBox.DataSource = Enum.GetValues(
typeof(BusinessObject.MyEnum));
// Line below breaks above bindings, same for SelectedValue.
myEnumComboBox.DataBindings.Add("SelectedItem", bs, "MyEnumVar");
}
}
class BusinessObject : INotifyPropertyChanged
{
public enum MyEnum { RED, BLU }
MyEnum _MyEnumVar;
public MyEnum MyEnumVar
{
get { return _MyEnumVar; }
set
{
if (value != _MyEnumVar)
{
_MyEnumVar = value;
NotifyPropertyChanged("MyEnumVar");
}
}
}
private bool _MyBool;
public bool MyBool
{
get { return _MyBool; }
set
{
if (value != _MyBool)
{
_MyBool = value;
MyBoolInverse = !value;
NotifyPropertyChanged("MyBool");
}
}
}
private bool _MyBoolInverse;
public bool MyBoolInverse
{
get { return _MyBoolInverse; }
private set
{
if (value != _MyBoolInverse)
{
_MyBoolInverse = value;
NotifyPropertyChanged("MyBoolInverse");
}
}
}
public BusinessObject()
{
MyBoolInverse = !MyBool;
MyEnumVar = MyEnum.BLU;
}
// Boilerplate INotifyPropertyChanged implementation & helper.
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Visible property has problems with binding. Try manual 'binding'. Something along the lines of
trueBox.Visible = bo.MyBoolInverse;
bo.PropertyChanged += (s, e) => {
if(e.PropertyName == "MyBoolInverse")
trueBox.Visible = bo.MyBoolInverse;
};
Edit: Also, binding to MyEnumVar is not working beacause it is not declared as a public property.
I created a simple example with data binding (unfortunately we have a similar case in our system). I created a funky combo box:
public class FunkyComboBox : ComboBox
{
private object currentValue = null;
public FunkyComboBox()
{
if (LicenseManager.UsageMode == LicenseUsageMode.Runtime)
this.Items.Add("Other...");
}
protected override void OnSelectedIndexChanged(EventArgs e)
{
if (!this.Text.StartsWith("Other") && currentValue != this.SelectedItem)
{
currentValue = this.SelectedItem;
BindingManagerBase bindingManager = DataManager;
base.OnSelectedIndexChanged(e);
}
}
protected override void OnSelectionChangeCommitted(EventArgs e)
{
string itemAsStr = this.SelectedItem != null ? SelectedItem.ToString() : "";
if (itemAsStr.StartsWith("Other"))
{
string newItem = "item" + this.Items.Count;
if (!Items.Contains(newItem))
{
Items.Add(newItem);
}
SelectedItem = newItem;
}
else
{
OnSelectedIndexChanged(e); //forces a selectedIndexChanged event to be thrown
base.OnSelectionChangeCommitted(e);
}
}
}
Which adds new items when you click Other (in our system it opens a form where you can query the database, etc). Then I have a simple data object:
public class MyClass
{
private string value;
public string MyData
{
get{ return value;}
set{ this.value = value;}
}
}
And a test form with two controls bound to this object (some designer code removed):
public partial class Form1 : Form
{
MyClass myObj = new MyClass();
public Form1()
{
InitializeComponent();
myObj.MyData = "Nothing";
myClassBindingSource.DataSource = myObj;
}
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.textBox1 = new System.Windows.Forms.TextBox();
this.myClassBindingSource = new System.Windows.Forms.BindingSource(this.components);
this.funkyComboBox1 = new DataBindingTests.FunkyComboBox();
((System.ComponentModel.ISupportInitialize)(this.myClassBindingSource)).BeginInit();
this.SuspendLayout();
//
// textBox1
//
this.textBox1.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.myClassBindingSource, "MyData", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
//
// myClassBindingSource
//
this.myClassBindingSource.DataSource = typeof(DataBindingTests.MyClass);
//
// funkyComboBox1
//
this.funkyComboBox1.DataBindings.Add(new System.Windows.Forms.Binding("SelectedItem", this.myClassBindingSource, "MyData", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
this.funkyComboBox1.DataBindings.Add(new System.Windows.Forms.Binding("SelectedValue", this.myClassBindingSource, "MyData", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
//
// Form1
//
this.Controls.Add(this.textBox1);
this.Controls.Add(this.funkyComboBox1);
((System.ComponentModel.ISupportInitialize)(this.myClassBindingSource)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
private FunkyComboBox funkyComboBox1;
private System.Windows.Forms.BindingSource myClassBindingSource;
private System.Windows.Forms.TextBox textBox1;
}
If you run this code and start playing with the combo box, you will notice that the edit box changes only if you click on it. After every change a null value is set to my object and the text box is cleared. How can I make it set the correct value after every change?
I'm not really sure why the ComboBox data binding behaves this way, but I have found a workaround. It seems as though the databinding doesn't work correctly if you don't use a datasource for your ComboBox's values.
I made a few minor changes to FunkyComboBox, and it now works as expected.
public class FunkyComboBox : ComboBox
{
private object currentValue = null;
private List<string> innerItems = new List<string>();
public FunkyComboBox()
{
if (LicenseManager.UsageMode == LicenseUsageMode.Runtime)
innerItems.Add("Other...");
this.DataSource = innerItems;
}
protected override void OnSelectedIndexChanged(EventArgs e)
{
if (!this.Text.StartsWith("Other") && currentValue != this.SelectedItem)
{
currentValue = this.SelectedItem;
BindingManagerBase bindingManager = DataManager;
base.OnSelectedIndexChanged(e);
}
}
protected override void OnSelectionChangeCommitted(EventArgs e)
{
string itemAsStr = this.SelectedItem != null ? SelectedItem.ToString() : "";
if (itemAsStr.StartsWith("Other"))
{
string newItem = "item" + this.Items.Count;
if(!innerItems.Contains(newItem))
{
innerItems.Add(newItem);
this.RefreshItems();
} SelectedItem = newItem;
}
else
{
OnSelectedIndexChanged(e);
//forces a selectedIndexChanged event to be thrown
base.OnSelectionChangeCommitted(e);
}
}
}
It seems to be a bug with the base ComboBox as well. It is not possible to get this binding source craziness to work correctly.