I have a Windows Forms ListBox data-bound to a BindingList of business objects. The ListBox's displayed property is a string representing the name of the business object. I have a TextBox that is not data-bound to the name property but instead is populated when the ListBox's selected index changes, and the TextBox, upon validation, sets the business object's name property and then uses BindingList.ResetItem to notify the BindingList's bound control (the ListBox) to update itself when the TextBox's text value is changed by the user.
This works great unless the name change is only a change in case (i.e. "name" to "Name"), in which case the ListBox doesn't get updated (it still says "name", even though the value of the underlying business object's name property is "Name").
Can anyone explain why this is happening and what I should do instead? My current workaround is to use BindingList.ResetBindings, which could work for me but may not be acceptable for larger datasets.
Update 9/27/2011: Added a simple code example that reproduces the issue for me. This is using INotifyPropertyChanged and binding the textbox to the binding list. Based on How do I make a ListBox refresh its item text?
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace WinformsDataBindingListBoxTextBoxTest
{
public partial class Form1 : Form
{
private BindingList<Employee> _employees;
private ListBox lstEmployees;
private TextBox txtId;
private TextBox txtName;
private Button btnRemove;
public Form1()
{
InitializeComponent();
FlowLayoutPanel layout = new FlowLayoutPanel();
layout.Dock = DockStyle.Fill;
Controls.Add(layout);
lstEmployees = new ListBox();
layout.Controls.Add(lstEmployees);
txtId = new TextBox();
layout.Controls.Add(txtId);
txtName = new TextBox();
layout.Controls.Add(txtName);
btnRemove = new Button();
btnRemove.Click += btnRemove_Click;
btnRemove.Text = "Remove";
layout.Controls.Add(btnRemove);
Load += new EventHandler(Form1_Load);
}
private void Form1_Load(object sender, EventArgs e)
{
_employees = new BindingList<Employee>();
for (int i = 0; i < 10; i++)
{
_employees.Add(new Employee() { Id = i, Name = "Employee " + i.ToString() });
}
lstEmployees.DisplayMember = "Name";
lstEmployees.DataSource = _employees;
txtId.DataBindings.Add("Text", _employees, "Id");
txtName.DataBindings.Add("Text", _employees, "Name");
}
private void btnRemove_Click(object sender, EventArgs e)
{
Employee selectedEmployee = (Employee)lstEmployees.SelectedItem;
if (selectedEmployee != null)
{
_employees.Remove(selectedEmployee);
}
}
}
public class Employee : INotifyPropertyChanged
{
private string name;
private int id;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("Name");
}
}
public int Id
{
get { return id; }
set
{
id = value;
OnPropertyChanged("Id");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
}
Update 9/28/2011: The problem seems to be internal to the ListBox control, specifically the way it decides (not) to update an item if its string representation is equivalent to the new value, ignoring case differences. As far as I can tell this is hard coded into the control with no way to override it.
Think I found the problem:
I just hope this concept will be helpfull to solve your problem
I have a TextBox, the user input is compared to string "Name". My button click event is:
private void btn_Click(object sender, EventArgs e)
{
if(txt.Text == "Name")
MessageBox.Show("Value is Same");
}
If you write "name" in textbox, condition will be false. If you type type "Name" in textbox, condition will be true.
Now try changing the btn click:
using System.Globalization;
private void btn_Click(object sender, EventArgs e)
{
TextInfo ChangeCase = new CultureInfo("en-US", false).TextInfo;
string newText = ChangeCase.ToTitleCase(txt.Text);
if (newText == "Name")
MessageBox.Show("Value is Same");
}
now you type "name" or "Name" condition is true.
Remember it will just capaitalize the first letter of the string suplied. "my name" will outputted as "My Name".
And if your condition says:
if(txt.Text == "name")
MessageBox.Show(Value is Same);
Then you can try something like
string newText = (txt.Text).ToLower();
if(newText == "name")
MessageBox.Show(Value is Same);
Here the supplied string will outputted in the lower case always.
Hope it helps.
This really is the same problem as when renaming files or directories while only case is different. I suggest the same work-around that I found earlier:
if (oldValue.ToUpper() == newValue.ToUpper()){
ListBox1.Items[i] = newValue + "_tmp"; // only adding stuff to force an update
ListBox1.Items[i] = newValue; // now the new value is displayed, even only case has changed
}
Now for your question, I suggest you try to check if the setter is changing a value only in lower/upper case (a.ToUpper() == b.ToUpper()). If true, then first give a extra change, before the intended change, something like:
name = value + "_tmp";
OnPropertyChanged("Name");
name = value;
OnPropertyChanged("Name");
Hope this helps.
Related
What I would like to do is to create a class that contains for instance a Label and Text box. It can differs. I would like to create and add this obj dynamically.
I would like to bind that data to its properties and of-course show the changes immediately:
My steps are:
Create Class e.g. WpfObject
Create new Label
Create new TextBox
Create property
Constructor with or without parameters
Constructor contains databinding settings
I followed WPF tutorial on wpf-tutorial page also checked msdn for hints, and tried to do analogically and not to forgot any step . Of course I tried to google where the problem can be.
By debugging I just found that onPropertyChanged resp. PropertyChanged still returns null.
Well I do not know for what I have to look. I tried to learn something from another issues stated here, or on the web but probably I understand something wrong or forgot something.
So would like to ask for some hint, or help.
Here I am adding my code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
List<infoObject> LinfoObjectList = new List<infoObject>();
private void btnAddBox_Click(object sender, RoutedEventArgs e)
{
infoObject theObject = new infoObject("theinfo");
LinfoObjectList.Add(theObject);
mainGrid.Children.Add(theObject.AddLabel());
mainGrid.Children.Add(theObject.AddTextbox());
}
private void btnCustomBox_Click(object sender, RoutedEventArgs e)
{
foreach(var item in LinfoObjectList)
{
item.ChangeInfoObject();
}
}
}
public class infoObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string _sTheText;
string STheText
{
set { _sTheText = value;
OnPropertyChanged("sTheText");
}
get { return _sTheText; }
}
Label theLabel = new Label();
TextBox theTextBox = new TextBox();
int i;
public infoObject(string _objectName){
STheText = " ";
i = 0;
theLabel.Width = 100;
theLabel.Height = 25;
theLabel.Content = _objectName;
theTextBox.Width = 100;
theTextBox.Height = 30;
theTextBox.Text = STheText.ToString();
Binding binding = new Binding();
binding.Path = new PropertyPath("sTheText");
theTextBox.SetBinding(TextBox.TextProperty, binding);
}
public void ChangeInfoObject()
{
STheText = "textWasChanged"+i.ToString();
i = +1;
}
public Label AddLabel()
{
return this.theLabel;
}
public TextBox AddTextbox()
{
return this.theTextBox;
}
public void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
edit:
to clarify what I would like to do:
I would like to make some library or sth. like that and that in future I will be able to create textboxes , labels, buttons for for instance each of element I have stored in list or elsewhere.
So I just will add e.g. Element.add( starting position , left margin ,topmargin, root-name , otherparams) and it will create these mentioned dynamically and I get rid of positioning, putting into grid, for each element.
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);
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;
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.
I have a TextBox on WPF that I want to validate. I'm using Binding to validate it:
<TextBox Text="{Binding Path=Name, UpdateSourceTrigger=Explicit}" TabIndex="0" LostFocus="TextBox_OnLostFocus">
</TextBox>
The LostFocus event:
private void TextBox_OnLostFocus(object sender, RoutedEventArgs e)
{
((Control) sender).GetBindingExpression(TextBox.TextProperty);
}
Code behind the validation:
public string Name
{
get { return _name; }
set
{
_name = value;
this.OnPropertyChanged(new PropertyChangedEventArgs("Name"));
}
}
public string Error { get { return this[null]; } }
public string this[string columnName]
{
get
{
string result = string.Empty;
columnName = columnName ?? string.Empty;
if (columnName == string.Empty || columnName == "Name")
{
if (string.IsNullOrEmpty(this.Name))
{
result += Properties.Resources.ValidationName + Environment.NewLine;
}
}
return result.TrimEnd();
}
}
I have some questions:
1. When I first load my Window, my control is surrounded by a red square (the validation one), but I want it to appear only when I fire it (on the Explicit side).
2. How can I know if all my fields have been validated? I mean, when I press a button I only need to know how to know if all controls have been validated.
NOTE: I do have this context on the Constructor:
User u = new User();
DataContext = u;
Your first question may be answered here Did you try setting the binding mode to Default?
The Validation.HasError Attached Property will tell you if any binding on a particular UI Element has any binding validation errors. Use that on every control you need to have validated. Try that first. If you are using a pattern like MVVM, you could create properties on your VM to bind to the Validation.HasError properties.
Actually, my problem had something to do with the class I used to validate. The class does this:
public ErrorProvider()
{
this.DataContextChanged += new DependencyPropertyChangedEventHandler(ErrorProvider_DataContextChanged);
this.Loaded += new RoutedEventHandler(ErrorProvider_Loaded);
}
So whenever it first loads, it suscribes to the Load event and then it launches this:
private void ErrorProvider_Loaded(object sender, RoutedEventArgs e)
{
Validate();
}
so I commented it, and launched the Validate() method when needed....