I have written a control in C# that derives from System.Windows.Forms.Control. I have added a property Selected to which I want to databind to a business entity using a BindingSource.
I’ve implemented the PropertyNameChanged pattern by adding a SelectedChanged event that I fire when the Selected property is changed.
This is my code:
public partial class RateControl : Control
{
[Category("Property Changed")]
public event EventHandler SelectedChanged;
public int Selected
{
get
{ return m_selected; }
set
{
if (m_selected != value)
{
m_selected = value;
OnSelectedChanged();
Invalidate();
}
}
}
protected virtual void OnSelectedChanged()
{
if (this.SelectedChanged != null)
this.SelectedChanged(this, new EventArgs());
}
}
When I bind to the Selected property, I see the event being subscibed to. The event is also fired when the property changes.
However the business entity is not updated. I don’t even see the getter of the Selected property being accessed.
What am I missing?
Have you got the binding's update mode set to DataSourceUpdateMode.OnPropertyChanged? Either via binding.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;, or using one of the DataBindings.Add(...) overloads.
The following works for me to push values to the business object...
using System;
using System.Diagnostics;
using System.Windows.Forms;
class MyForm : Form
{
public MyForm()
{
MyBusinessObject obj = new MyBusinessObject();
Button btn = new Button();
btn.Click += delegate { Foo++; };
DataBindings.Add("Foo", obj, "Bar", false, DataSourceUpdateMode.OnPropertyChanged);
DataBindings.Add("Text", obj, "Bar");
Controls.Add(btn);
}
private int foo;
public event EventHandler FooChanged;
public int Foo
{
get { return foo; }
set
{
if (foo != value)
{
foo = value;
Debug.WriteLine("Foo changed to " + foo);
if (FooChanged != null) FooChanged(this, EventArgs.Empty);
}
}
}
}
class MyBusinessObject
{
private int bar;
public event EventHandler BarChanged;
public int Bar
{
get { return bar; }
set
{
if (bar != value)
{
bar = value;
Debug.WriteLine("Bar changed to " + bar);
if (BarChanged != null) BarChanged(this, EventArgs.Empty);
}
}
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.Run(new MyForm());
}
}
Related
I have a custom form where I have a GridView on it. Most of my forms will inherit from this custom form.
so let's say that I have
class A : B
{
//Contents
}
with the above scenario, my problem is: I am not able to edit the grid's columns,
on the designer view's property grid. it's like they are locked.
so I have decided to create a custom property to set a list of column names etc.
so to do this I have these classes
[TypeConverter(typeof(BrowseLayoutColumns))]
public class BrowseLayoutColumns : ExpandableObjectConverter
{
#region Properties
private string _columnName = string.Empty;
public string ColumnName
{
get => _columnName;
set
{
if (null == value) return;
_columnName = value;
}
}
private string _bindingField = string.Empty;
public string BindingField
{
get => _bindingField;
set
{
if (null == value) return;
_bindingField = value;
}
}
#endregion
public override string ToString()
{
return "Columns";
}
}
internal class MyList<T> : List<T> where T : class
{
#region ListMethods
public new void Add(T item)
{
base.Add(item);
ListChanged?.Invoke();
}
public new void Clear()
{
base.Clear();
ListChanged?.Invoke();
}
#endregion
#region Events
public event ListChangedEventHandler ListChanged;
public delegate void ListChangedEventHandler();
#endregion
}
and inside my Custom class I added
private MyList<BrowseLayoutColumns> _browseLayoutColumns = new MyList<BrowseLayoutColumns>();
[Category("Design")]
public MyList<BrowseLayoutColumns> BrowseLayoutColumns
{
get => _browseLayoutColumns;
set => _browseLayoutColumns = value;
}
and inside form Initialization I've created the ListChanged event.
private void _browseLayoutColumns_ListChanged()
{
if (_browseLayoutColumns == null) return;
foreach (var column in _browseLayoutColumns)
{
myGridView1.Columns.Add(column.ColumnName, column.BindingField);
}
}
so now as you can see below in the design time I'm able to add columns
the problem here is, it's like the data entered here is not persistent, I mean, it is not adding these values to the columns because my event is not triggered when I run the program and when I debug I see that my BrowseLayoutList property is empty.
any help?
P.S I've tested my event and others by adding to browselayoutcolumns property manually
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.
Sorry I'm newbie in C# and events especially.
Why I receive NPE?
class WcfModel : IWcfModel
{
private List<ConsoleData> _dataList;
public List<ConsoleData> DataList
{
get { return _dataList; }
set { _dataList = value;
DataArrived(_dataList); // NPE
}
}
public event Action<List<ConsoleData>> DataArrived;
}
If no object subscribed to the event (that is the delegate has no subscribers), it will be null. You need to test for that:
set {
_dataList = value;
var dataDel = DataArrived;
if(dataDel != null)
dataDel(_dataList);
}
Alternatively, use ObservableCollection<ConsoleData> - it has built in events for changes to the collection.
Rather use ObservableCollection<ConsoleData> which has its own event publisher.
class WcfModel : IWcfModel
{
private ObservableCollection<ConsoleData> _dataList;
public WcfModel ()
{
_dataList = new ObservableCollection<ConsoleData>();
_dataList.CollectionChanged += DataArrived
}
public ObservableCollection<ConsoleData> DataList
{
get { return _dataList; }
}
public event Action<object, NotifyCollectionChangedEventArgs> DataArrived;
}
Now whenever you do
wcfModelInstance.DataList.Add(new ConsoleData("hello"));
You will be notified when you subscribe DataArrived event in WcfModel.
Hope this helps.
You should add a null checker for the event as the following codes:
class WcfModel: IWcfModel
{
private List<ConsoleData> _dataList;
public List<ConsoleData> DataList
{
get { return _dataList; }
set
{
_dataList = value;
if ( DataArrived != null )
DataArrived ( _dataList );
}
}
public event Action<List<ConsoleData>> DataArrived;
}
I just recently discovered an INotifyPropertyChange interface. I managed to implement this interface in my clss and everything works fine. However I was wondering if it is possible to intercept this event in code and fire a function
Let's say that I have a function
DoStuff()
and I wan't to fire this function everytime property1, property2 or property3 changes.
Of course I could put this function in set block in my class but this is not a good idea(I think).
If you mean to internal method that'll handle this event you can do it by registering to the event in the class constructor. For example:
public class AnswerViewModel : IAnswerViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
private string content;
public AnswerViewModel()
{
PropertyChanged += (sender, args) => DoStuff();
}
public string Content
{
get { return content; }
set
{
content = value;
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public void DoStuff()
{
// this method will be called whenever PropertyChanged event raised
}
}
If the intercepting method belongs to other class:
public class PropertiesInterceptor
{
private readonly AnswerViewModel viewModel;
private readonly List<string> propertiesToIntercept =
new List<string> { "property1", "property2", "property3" };
public PropertiesInterceptor(AnswerViewModel viewModel)
{
this.viewModel = viewModel;
viewModel.PropertyChanged += OnPropertyChanged;
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (propertiesToIntercept.Contains(args.PropertyName))
{
DoStuff();
}
}
private void DoStuff()
{
// Do something with viewModel
}
}
Intercept the PropertyChanged Event:
http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.propertychanged.aspx
You could fire the method from a RaisePropertyChanged() method:
public int Property1
{
get { return this.property1; }
set
{
if (this.property1 != value)
{
this.property1 = value;
RaisePropertyChanged("Property1");
}
}
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
DoStuff(); // Call DoStuff here.
}
Stealing Elisha's answer to answer your question in Merlyn's answer
public class AnswerViewModel : IAnswerViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
private string property1;
private string property2;
private string propertyX;
public AnswerViewModel()
{
PropertyChanged += (sender, args) =>
{
if(args.PropertyName == "Property1" || args.PropertyName == "Property2")
DoStuff();
}
}
public string Property1
{
get { return content; }
set
{
property1 = value;
PropertyChanged(this, new PropertyChangedEventArgs("Property1"));
}
}
public string Property2
{
get { return content; }
set
{
property2 = value;
PropertyChanged(this, new PropertyChangedEventArgs("Property2"));
}
}
public string PropertyX
{
get { return content; }
set
{
propertyX = value;
PropertyChanged(this, new PropertyChangedEventArgs("PropertyX"));
}
}
public void DoStuff()
{
// this method will be called whenever PropertyChanged event raised from Property1 or Property2
}
}
If the class DoStuff is in is a member you can do
private otherClass
public AnswerViewModel()
{
PropertyChanged += (sender, args) =>
{
if(args.PropertyName == "Property1" || args.PropertyName == "Property2")
otherClass.DoStuff();
}
}
Otherwise you can just have otherClass register a event on its own in your main code.
Did you need it to replace the existing NotifyPropertyChanged event handlers, or just get called when NotifyPropertyChanged is called?
If you mean the second, you can simply register an event handler
edit
You can add an event handler that gets called on NotifyPropertyChanged, checks if the property parameter is equal to Property1, Property2, or Property3, and only then forwards it to the actual function you want to call.
How do you perform databinding against the MonthCalendar.SelectionRange property? Given the property is of type 'SelectionRange' which is a class I am not sure how to go about it. Any examples would be much appreciated.
Well, there don't seem to be any obvious events for this either on the MonthCalendar or the SelectionRange, and neither implements INotifyPropertyChanged, so it looks like data-binding might not be possible here.
Update: It does, however, raise the DateChanged, so you could hook some stuff together manually, or (more usefully) by subclassing the control to expose the values and events in a fashion suitable for binding. Note the Actual(...) are useful because the end (otherwise) is just before midnight, rather than midnight itself...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Forms;
class Foo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
Debug.WriteLine(ToString());
}
private void SetField<T>(ref T field, T value, string propertyName)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(propertyName);
}
}
private DateTime start, end;
public DateTime Start { get { return start; } set { SetField(ref start, value, "Start"); } }
public DateTime End { get { return end; } set { SetField(ref end, value, "End"); } }
}
class BindableCalendar : MonthCalendar
{
public DateTime ActualSelectionStart
{
get { return SelectionRange.Start; }
set { if (ActualSelectionStart != value) { SetSelectionRange(value, ActualSelectionEnd); } }
}
public DateTime ActualSelectionEnd
{
get { return SelectionRange.End; }
set { if (ActualSelectionEnd != value) { SetSelectionRange(ActualSelectionStart, value); } }
}
// should really use EventHandlerList here...
public event EventHandler ActualSelectionStartChanged, ActualSelectionEndChanged;
DateTime lastKnownStart, lastKnownEnd;
protected override void OnDateChanged(DateRangeEventArgs drevent)
{
base.OnDateChanged(drevent);
if (lastKnownStart != drevent.Start)
{
if (ActualSelectionStartChanged != null) ActualSelectionStartChanged(this, EventArgs.Empty);
lastKnownStart = drevent.Start;
}
if (lastKnownEnd != drevent.End)
{
if (ActualSelectionEndChanged != null) ActualSelectionEndChanged(this, EventArgs.Empty);
lastKnownEnd = drevent.End;
}
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
MonthCalendar cal;
Button btn;
using (Form form = new Form
{
Controls = {
(cal = new BindableCalendar { Dock = DockStyle.Fill, MaxSelectionCount = 10 }),
(btn = new Button { Dock = DockStyle.Bottom, Text = "thwack"})
}
})
{
Foo foo = new Foo { Start = DateTime.Today, End = DateTime.Today.AddDays(1) };
cal.DataBindings.Add("ActualSelectionStart", foo, "Start").DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
cal.DataBindings.Add("ActualSelectionEnd", foo, "End").DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
btn.Click += delegate
{
foo.Start = foo.Start.AddDays(1);
foo.End = foo.End.AddDays(1);
};
Application.Run(form);
}
}
}
For me it seems to be very simple. I just bound SelectionStart and SelectionEnd properties of the MonthCalendar component.
this.Calendar1.DataBindings.Add(new System.Windows.Forms.Binding("SelectionStart",
bindingSource, "DateField", true));
this.Calendar1.DataBindings.Add(new System.Windows.Forms.Binding("SelectionEnd",
bindingSource, "DateField", true));