Data binding not updating - c#

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.

Related

USer Control Custom event and set get property

I have 2 labels and a property in user control:
Here is property:
private int _SelectIndex;
[Browsable(true)]
public int SelectIndex { get; set; }
and 2 labels:
Label lbl1, lbl2;
void iControl()
{
lbl1 = new Label();
lbl2 = new Label();
lbl1.Name = "lbl1";
lbl2.Name = "lbl2";
lbl1.Click += lbl_Click;
lbl2.Click += lbl_Click;
this.Controls.Add(lbl1);
this.Controls.Add(lbl2);
}
Click:
void lbl_Click(object sender, EventArgs e)
{
Label selectedlbl = sender as Label;
if(selectedlbl.Name == "lbl1")
this.Select = 1;
else
this.Select = 2;
}
Class Event:
public class SelectEventArgs : EventArgs
{
private int index;
public SelectEventArgs(int index)
{
this.index = index;
}
public int ItemIndex
{
get
{
return index;
}
}
}
Custom event in my control:
public event EventHandler SelectEvent;
protected virtual void OnSelectEvent()
{
if (SelectEvent!= null)
SelectEvent(this, new SelectEventArgs(this._SelectIndex));
}
I need an event to get and set property value in MainForm as following:
int index = 0;
public Form1()
{
InitializeComponent();
this.icontrol = new iControl();
this.SelectEvent += Select();
}
void Select(object sender, SelectItem e)
{
//use this to set value of Select
this.icontrol.SelectIndex = index;
//and this to get value of Select
index = this.icontrol.SelectIndex;
}
Select is empty.
How to get it to work?
I post here for any one need it:
1.Declare a delegate:
public delegate void SelectIndexEventHandler(object sender, SelectEventArgs e);
public class SelectEventArgs : EventArgs
{
private int index;
public SelectEventArgs(int index)
{
this.index = index;
}
public int ItemIndex
{
get { return index; }
set { index = value; }
}
}
2. declare an event SelectIndexChanged and a method OnSelectIndexChanged:
public event SelectIndexEventHandler SelectIndexChanged;
protected virtual void OnSelectIndexChanged(SelectEventArgs e)
{
if (SelectIndexChanged != null)
SelectIndexChanged(this, e);
}
3.Call it in setter:
public int SelectIndex
{
get { return _SelectIndex; }
set {
_SelectIndex = value;
OnSelectIndexChanged(new SelectEventArgs(value));
}
}
and then MainForm:
this.gListBox1.SelectIndexChanged += icontrol_SelectIndexChanged;
void icontrol_SelectIndexChanged(object sender, SelectEventArgs e)
{
var current = e.ItemIndex;
}
thank again jbmintjb Reza Aghaei.
The code has multiple issues. Consider these tips to solve the issues:
SelecetEvent does't belong to the Form. The event belongs to icontrol.
this.SelectEvent += Select(); is incorrect, you should use:
icontrol.SelectEvent += Select;
When you have a custom event args, you should define the event this way:
public event EventHandler<SelectEventArgs> SelectEvent;
You should raise the event in setter of your property, using OnSelectEvent method which you created.
To learn more about events take a look at C# Handling and Raising Events.
Take a look at the SelectedIndexChanged event on the listbox control, think that is what you are looking for

How to update a ListBox if an element was changed c#

Hi,
I'm struggling a bit using the ListBox.DataSource and the INotifyPropertyChanged Interface. I checked several posts about this issue already but I cannot figure out, how to update the view of a ListBox if an element of the bound BindingList is changed.
I basically want to change the color of an IndexItem after the content has been parsed.
Here the relevant calls in my form:
btn_indexAddItem.Click += new EventHandler(btn_indexAddItem_Click);
lst_index.DataSource = Indexer.Items;
lst_index.DisplayMember = "Url";
lst_index.DrawItem += new DrawItemEventHandler(lst_index_DrawItem);
private void btn_indexAddItem_Click(object sender, EventArgs e)
{
Indexer.AddSingleURL(txt_indexAddItem.Text);
}
private void lst_index_DrawItem(object sender, DrawItemEventArgs e)
{
IndexItem item = lst_index.Items[e.Index] as IndexItem;
if (item != null)
{
e.DrawBackground();
SolidBrush brush = new SolidBrush((item.hasContent) ? SystemColors.WindowText : SystemColors.ControlDark);
e.Graphics.DrawString(item.Url, lst_index.Font, brush, 0, e.Index * lst_index.ItemHeight);
e.DrawFocusRectangle();
}
}
Indexer.cs:
class Indexer
{
public BindingList<IndexItem> Items { get; }
private object SyncItems = new object();
public Indexer()
{
Items = new BindingList<IndexItem>();
}
public void AddSingleURL(string url)
{
IndexItem item = new IndexItem(url);
if (!Items.Contains(item))
{
lock (SyncItems)
{
Items.Add(item);
}
new Thread(new ThreadStart(() =>
{
// time consuming parsing
Thread.Sleep(5000);
string content = item.Url;
lock (SyncItems)
{
Items[Items.IndexOf(item)].Content = content;
}
}
)).Start();
}
}
}
IndexItem.cs
class IndexItem : IEquatable<IndexItem>, INotifyPropertyChanged
{
public int Key { get; }
public string Url { get; }
public bool hasContent { get { return (_content != null); } }
private string _content;
public string Content {
get
{
return (hasContent) ? _content : "empty";
}
set
{
_content = value;
ContentChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void ContentChanged()
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public IndexItem(string url)
{
this.Key = url.GetHashCode();
this.Url = url;
}
public override bool Equals(object obj)
{
return Equals(obj as IndexItem);
}
public override int GetHashCode()
{
return Key;
}
public bool Equals(IndexItem other)
{
if (other == null) return false;
return (this.Key.Equals(other.Key)) ||
((hasContent || other.hasContent) && (this._content.Equals(other._content)));
}
public override string ToString()
{
return Url;
}
}
Any ideas what went wrong and how to fix it? I'll appreciate any hint...
It seems to me that the control should redraw when it raises the ListChanged event for that item. This will force it to do so:
lst_index.DrawItem += new DrawItemEventHandler(lst_index_DrawItem);
Indexer.Items.ListChanged += Items_ListChanged;
private void Items_ListChanged(object sender, ListChangedEventArgs e)
{
lst_index.Invalidate(); // Force the control to redraw when any elements change
}
So why doesn't it do that already? Well, it seems that the listbox only calls DrawItem if both DisplayMember changed, and if the INotifyPropertyChanged event was raised from the UI thread. So this also works:
lock (SyncItems)
{
// Hacky way to do an Invoke
Application.OpenForms[0].Invoke((Action)(() =>
{
Items[Items.IndexOf(item)].Url += " "; // Force listbox to call DrawItem by changing the DisplayMember
Items[Items.IndexOf(item)].Content = content;
}));
}
Note that calling PropertyChanged on the Url is not sufficient. The value must actually change. This tells me that the listbox is caching those values. :-(
(Tested with VS2015 REL)

How to create user control for displaying collection of other user controls in WinForms?

I need to create a user control MyTypeListControl to display collection of objects of type MyType using a user controls MyTypeDisplayControl instance for each of those objects.
So that I could
add instance of MyTypeListControl to my WinForm, then
load collection of MyType and
assign it to MyTypeListControl's DataSource.
In the result it should generate and show appropriate count of MyTypeDisplayControl instances in MyTypeListControl's instance.
In case if I needed to show list of properties - equivalent would be DataGrid with specific fields from MyType assigned to specific DataGrid's columns, but I want to view each MyType item as a user control - with more power for visual representation and functionality than DataGrid provides for it's rows.
Is that even possible?
I found this SO resource how to create My collection type, but this is only small part of the problem solution...
It is quite easy (if you know how) and doesn't take so much effort as you might think in the first place (at least for a simple implementation that handles collection of less then 100 items).
So at first lets create a MyType:
public class MyType
{
public static MyType Empty = new MyType(String.Empty, DateTime.MinValue);
public MyType(string myName, DateTime myBirthday)
{
MyName = myName;
MyBirthday = myBirthday;
}
public DateTime MyBirthday { get; private set; }
public string MyName { get; private set; }
}
At next we need a MyTypeControl:
public partial class MyTypeControl : UserControl
{
private MyType _MyType;
private Label labelBirthday;
private Label labelName;
private Label labelSeparator;
public MyTypeControl()
{
InitializeComponent();
}
public event EventHandler MyTypeChanged;
public MyType MyType
{
get { return _MyType; }
set
{
if (_MyType == value)
return;
_MyType = value ?? MyType.Empty;
OnMyTypeChanged(EventArgs.Empty);
}
}
protected virtual void OnMyTypeChanged(EventArgs eventArgs)
{
UpdateVisualization();
RaiseEvent(MyTypeChanged, eventArgs);
}
protected void UpdateVisualization()
{
SuspendLayout();
labelName.Text = _MyType.MyName;
labelBirthday.Text = _MyType.MyBirthday.ToString("F");
labelBirthday.Visible = _MyType.MyBirthday != DateTime.MinValue;
ResumeLayout();
}
private void InitializeComponent()
{
labelName = new Label();
labelBirthday = new Label();
labelSeparator = new Label();
SuspendLayout();
labelName.Dock = DockStyle.Top;
labelName.Location = new Point(0, 0);
labelName.TextAlign = ContentAlignment.MiddleCenter;
labelBirthday.Dock = DockStyle.Top;
labelBirthday.TextAlign = ContentAlignment.MiddleCenter;
labelSeparator.BorderStyle = BorderStyle.Fixed3D;
labelSeparator.Dock = DockStyle.Top;
labelSeparator.Size = new Size(150, 2);
Controls.Add(labelSeparator);
Controls.Add(labelBirthday);
Controls.Add(labelName);
MinimumSize = new Size(0, 48);
Name = "MyTypeControl";
Size = new Size(150, 48);
ResumeLayout(false);
}
private void RaiseEvent(EventHandler eventHandler, EventArgs eventArgs)
{
var temp = eventHandler;
if (temp != null)
temp(this, eventArgs);
}
}
Then comes our magically list control:
public class MyTypeListControl : UserControl
{
private ObservableCollection<MyType> _Items;
public MyTypeListControl()
{
AutoScroll = true;
_Items = new ObservableCollection<MyType>();
_Items.CollectionChanged += OnItemsCollectionChanged;
}
public Collection<MyType> Items
{
get { return _Items; }
}
private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdateVisualization();
}
private void UpdateVisualization()
{
SuspendLayout();
Controls.Clear();
foreach (var item in _Items)
{
var control = new MyTypeControl { MyType = item, Dock = DockStyle.Top };
Controls.Add(control);
Controls.SetChildIndex(control, 0);
}
ResumeLayout();
}
}
And now simply create the list control in your form or parent control and fill it with some meaningful values:
myTypeListControl.Items.Add(new MyType("Adam", DateTime.UtcNow.Add(-TimeSpan.FromDays(365 * 40))));
myTypeListControl.Items.Add(new MyType("Eva", DateTime.UtcNow.Add(-TimeSpan.FromDays(365 * 38))));

C# DataBinding - automatically writing changed property to label or textbox

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.

DataBinding between multiple controls and a single object

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.

Categories