Winforms databinding and validation, why is datasource updated when validation fails? - winforms

The code below illustrates some unexpected (for me!) behaviour when combining data binding and validation in winforms. Can anyone tell me how I can prevent the datasource from being updated when validation fails?
Many thanks.
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 ValidationBug
{
/// <summary>
/// This illustrates some unexpected behaviour with winforms validation and binding
///
/// To reproduce: Run the program, enter a value into the textbox, click the X to close the form.
///
/// Expected behaviour: validation of textbox fails so data source is not updated.
///
/// Observed behaviour: data source is updated.
/// </summary>
public class Form1 : Form
{
private class Data
{
private string _field;
public string Field
{
get { return _field; }
set
{
// this should never be called, but it is.
_field = value;
}
}
}
private System.ComponentModel.IContainer components = null;
public Form1()
{
this.Load += new System.EventHandler(this.Form1_Load);
}
private void Form1_Load(object sender, EventArgs e)
{
AutoValidate = System.Windows.Forms.AutoValidate.EnablePreventFocusChange;
var txt = new TextBox();
// validation always fails.
txt.Validating += new CancelEventHandler((s, ev) => ev.Cancel = true);
Controls.Add(txt);
var data = new Data();
this.components = new System.ComponentModel.Container();
BindingSource bs = new BindingSource(this.components);
bs.DataSource = typeof(Data);
// only update datasource on succesful validation.
txt.DataBindings.Add(new Binding("Text", data, "Field", false, DataSourceUpdateMode.OnValidation));
}
}
}

I tend to "brute force" my code - could you set an initial value to your private string _field, perhaps in a constructor?
Also, are you sure that setting your CancelEventHandler's Cancel property to TRUE marks your data as invalid?
You may even want to add a private bool _valid field to your Data class that only returns values if it is valid.
private class Data
{
private bool _valid;
private string _field;
public Data()
{
_field = null;
_valid = false;
}
public string Field
{
get
{
if (_valid)
{
return _field;
} else {
return null;
}
set
{
// this should never be called, but it is.
_field = value;
_valid = !String.IsNullOrEmpty(_field);
}
}
}
Just some ideas to look into.

Related

WinForms Button: draw as the default button without setting the form's AcceptButton property (custom drawing, IsDefault property, BS_DEFPUSHBUTTON)

Imagine the following construction in WinForms .NET. A WinForms form contains a custom control with several buttons, which are instances of the traditional Button class. One of these buttons is the default button for the form. The custom control executes the action associated with the default button when ENTER is pressed. This is done in the redefined ProcessCmdKey method:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Return)
{
buttonOK_Click(null, EventArgs.Empty);
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
The default button must have an additional visual cue telling the user that this is the default button (an extra border inside the button). If we did this in a normal form, we would set its AcceptButton property. However, this approach is not applicable here. Even if we find the parent form using the Control.FindForm method or with an expression like (this.Parent as Form), we cannot set the AcceptButton property of the host form and then clear it the right way without resource leak or similar problems (a lot of technical details to place here and to bloat the question).
The first possible way to solve this task is to redefine or enhance the drawing of the button. Is there a relatively easy way to draw a button as the default button with the corresponding visual cue without implementing full custom painting? In my understanding, we might write a special class for our default button based on the following core:
internal class DefaultButton : Button
{
protected override void OnPaint(PaintEventArgs pevent)
{
Rectangle rc = new Rectangle(0, 0, this.Width, this.Height);
ButtonRenderer.DrawButton(pevent.Graphics, rc, System.Windows.Forms.VisualStyles.PushButtonState.Default);
}
}
However, it should take into account the focused state, whether another button on a form is focused (in this case the default button is not drawn with the visual cue), and the like. I could not find a good example of this to use as a basis for my development.
Another possible way to solve my problem could be setting the protected IsDefault property or/and specifying the BS_DEFPUSHBUTTON flag in the overridden CreateParams method in a class inherited from the Button class, for example:
internal class DefaultButton : Button
{
public DefaultButton() : base()
{
IsDefault = true;
}
protected override CreateParams CreateParams
{
get
{
const int BS_DEFPUSHBUTTON = 1;
CreateParams cp = base.CreateParams;
cp.Style |= BS_DEFPUSHBUTTON;
return cp;
}
}
}
But I could not make this code work. Buttons based on this class are always drawn as normal push buttons without the default button visual cue.
I'm not sure about the original requirement; for example I don't have any idea why a UserControl itself should set the AcceptButton of a Form, or what is the expected behavior if there are multiple instances of such controls on the form. It doesn't seem to be responsibility of the UserControl to set the AcceptButton of the Form and there might be better solutions, like relying on events and setting the AcceptButton.
Anyways, the following code example shows you how to set the AcceptButton of a Form; maybe it helps you to find a solutions. The highlights of the code:
The code uses dispose to set the AcceptButton to null.
The code implements ISupportInitialize to set the accept button after initialization of the control is done. If you create the control instance at run-time with code, don't forget to call EndInit, like this: ((System.ComponentModel.ISupportInitialize)(userControl11)).EndInit(); after adding it to the Form, but if you use designer, the designer will take care of that.
The code calls NotifyDefault(true) just for visual effect in design time when it's hosted on a form.
Here's the example:
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public class UserControl1 : UserControl, ISupportInitialize
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.textBox1 = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(15, 57);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// button2
//
this.button2.Location = new System.Drawing.Point(96, 57);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(75, 23);
this.button2.TabIndex = 1;
this.button2.Text = "button2";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(15, 17);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(100, 20);
this.textBox1.TabIndex = 2;
//
// UserControl1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.textBox1);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Name = "UserControl1";
this.Size = new System.Drawing.Size(236, 106);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TextBox textBox1;
public System.Windows.Forms.Button button1;
public System.Windows.Forms.Button button2;
public UserControl1()
{
InitializeComponent();
//Just for visual effect in design time when it's hosted on a form
button2.NotifyDefault(true);
}
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("1");
}
private void button2_Click(object sender, EventArgs e)
{
MessageBox.Show("2");
}
public void BeginInit()
{
}
public void EndInit()
{
var f = this.FindForm();
if (f != null)
f.AcceptButton = button2;
}
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
if (disposing)
{
var f = this.FindForm();
if (f != null)
f.AcceptButton = null;
}
base.Dispose(disposing);
}
}
}

Databinding a WinForms ComboBox requires user-interaction twice

I have a ComboBox (DropDownList style) in a Windows Form which has a data-source set to a BindingList, and has the SelectedValue property bound to a viewmodel's property.
Note that the binding is set to OnPropertyChanged rather than OnValidate, this is because when using OnValidate the control will not necessarily update the ViewModel if the form is closed or loses focus (but the control still thinks it has focus. On the Compact Framework there is no way to 'force validation' so I have to use OnPropertyChanged.
There's a problem which is reproducible on both desktop Windows Forms and Smart Device Windows Forms: when attempting to select or set the current item in the combobox (using the mouse or keyboard) the value will not "stick" until it is set twice - that is, you need to select the same item twice before the combobox's value will change.
There are no exceptions thrown (even caught exceptions) and no diagnostics reports to speak of.
I don't think this is a bug in the framework, and it's interesting how it happens on both Desktop and Compact Framework.
Here's my code:
Form1.cs
public partial class Form1 : Form {
private ViewModel _vm;
public Form1() {
InitializeComponent();
this.bindingSource1.Add( _vm = new ViewModel() );
}
}
Form1.Designer.cs (relevant lines)
//
// bindingSource1
//
this.bindingSource1.DataSource = typeof( WinForms.Shared.ViewModel );
//
// comboBox1
//
this.comboBox1.DataBindings.Add( new System.Windows.Forms.Binding( "SelectedValue", this.bindingSource1, "SelectedSomeTypeId", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged ) );
this.comboBox1.DataSource = this.someTypeListBindingSource;
this.comboBox1.DisplayMember = "DisplayText";
this.comboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBox1.FormattingEnabled = true;
this.comboBox1.Location = new System.Drawing.Point( 12, 27 );
this.comboBox1.Name = "comboBox1";
this.comboBox1.Size = new System.Drawing.Size( 182, 21 );
this.comboBox1.TabIndex = 0;
this.comboBox1.ValueMember = "Id";
//
// someTypeListBindingSource
//
this.someTypeListBindingSource.DataMember = "SomeTypeList";
this.someTypeListBindingSource.DataSource = this.bindingSource1;
ViewModel.cs
public class ViewModel : INotifyPropertyChanged {
public ViewModel() {
this.SomeTypeList = new BindingList<SomeType>();
for(int i=0;i<5;i++) {
this.SomeTypeList.Add( new SomeType() {
Id = i + 1,
Name = "Foo" + ((Char)( 'a' + i )).ToString()
} );
}
this.SelectedSomeTypeId = 2;
}
public BindingList<SomeType> SomeTypeList { get; private set; }
private Int64 _selectedSomeTypeId;
public Int64 SelectedSomeTypeId {
get { return _selectedSomeTypeId; }
set {
if( _selectedSomeTypeId != value ) {
_selectedSomeTypeId = value;
OnPropertyChanged("SelectedSomeTypeId");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String propertyName) {
PropertyChangedEventHandler handler = this.PropertyChanged;
if( handler != null ) handler( this, new PropertyChangedEventArgs(propertyName) );
}
}
public class SomeType {
public String Name { get; set; }
public Int64 Id { get; set; }
public String DisplayText {
get { return String.Format("{0} - {1}", this.Id, this.Name ); }
}
}
I have never found the 'right' way around this issue and generally use one of two ways to make things work:
Direct: Just bypass the binding mechanism for this one entry
combo1.SelectedIndexChanged += (s,e) _viewModel.Item = combo1.SelectedItem;
Generic Binding: Make a custom ComboBox and override the OnSelectedIndexChanged event to force the binding update.
public class BoundComboBox : ComboBox
{
protected override void OnSelectedIndexChanged(EventArgs e)
{
var binding = this.DataBindings["SelectedItem"];
if( binding != null )
binding.WriteValue();
base.OnSelectedIndexChanged(e);
}
}

Am I OK with a Clean <project Name> in Visual Studio 12?

I am Performing a Tutorial I found in Expression Blend 4 for connecting to a SQL Server with WPF. After the final steps in VS12 when I do a build I get the following error.
Error 1 The type or namespace name 'DelegateCommand' could not be found (are you missing a using directive or an assembly reference?)
Error 2 The type or namespace name 'DelegateCommand' could not be found (are you missing a using directive or an assembly reference?)
When I do a Clean I do not get these errors.
My Target is .net 4.5 I also tried 4.0
My code That is erroring looks Like this. I bolded the two erroring lines. this is a file called Class1.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace AWADataSource
{
public class ProductPhotosCollection
{
**private DelegateCommand getDataCommand;
public DelegateCommand GetDataCommand { get { return getDataCommand; } }**
public ProductPhotosCollection()
{
getDataCommand = new DelegateCommand(delegate() { GetData(); });
}
public ObservableCollection<ProductPhoto> ProductPhotos
{ get { return this.productPhotos; } }
private ObservableCollection<ProductPhoto> productPhotos =
new ObservableCollection<ProductPhoto>();
private void GetData()
{
ProductPhotosTableAdapters.ProductPhotoTableAdapter da =
new ProductPhotosTableAdapters.ProductPhotoTableAdapter();
ProductPhotos.ProductPhotoDataTable dt = da.GetData();
productPhotos.Clear();
foreach (ProductPhotos.ProductPhotoRow row in dt)
{
productPhotos.Add(new ProductPhoto(
row.ProductPhotoID,
row.ThumbNailPhoto,
row.LargePhoto,
row.ModifiedDate));
}
}
}
public class ProductPhoto
{
// Public Accessors to the private properties.
public int ID { get { return id; } }
public ImageSource ThumbNailPhoto { get { return thumbNailPhoto; } }
public ImageSource LargePhoto { get { return largePhoto; } }
public DateTime ModifiedDate { get { return modifiedDate; } }
// Constructor.
public ProductPhoto(int id, byte[] thumbNailPhoto, byte[] largePhoto,
DateTime modifiedDate)
{
this.id = id;
this.thumbNailPhoto = ByteArrayToImageSource(thumbNailPhoto);
this.largePhoto = ByteArrayToImageSource(largePhoto);
this.modifiedDate = modifiedDate;
}
// Private properties.
private int id;
private ImageSource thumbNailPhoto;
private ImageSource largePhoto;
private DateTime modifiedDate;
// Supporting method.
private ImageSource ByteArrayToImageSource(byte[] data)
{
BitmapImage image = null;
if (null != data)
{
image = new BitmapImage();
image.BeginInit();
image.StreamSource = new System.IO.MemoryStream(data);
image.EndInit();
}
return image;
}
}
}
and my other file is called DelegateCommand.cs which was pretty much a copy and paist.
namespace AWDataSource
{
using System;
using System.Windows.Input;
///
/// DelegateCommand is a simplified version of ICommand in WPF. You can wrap one of these around any method,
/// and thus bind any command on any WPF object to your method.
///
/// DelegateCommand also supports an IsEnabled property that you can use to turn the command on and off.
///
public sealed class DelegateCommand : ICommand
{
// Remember the method so that it can be called at the right time.
private SimpleEventHandler handler;
// Maintain the enabled state.
private bool isEnabled = true;
// Type signature of the method that DelegateCommand works with - returns void, no arguments.
public delegate void SimpleEventHandler();
// Simple constructor: Pass in the method that needs to be called when the command executes.
public DelegateCommand(SimpleEventHandler handler)
{
this.handler = handler;
}
#region ICommand implementation
// Executing the command is as simple as calling the method.
void ICommand.Execute(object arg)
{
this.handler();
}
// Saying whether the command can be executed.
bool ICommand.CanExecute(object arg)
{
return this.IsEnabled;
}
// This is the event that the command architecture of WPF listens to so it knows when to update
// the UI on command enable/disable.
public event EventHandler CanExecuteChanged;
#endregion
// Public visibility of the isEnabled flag - note that when it is set, the event must be raised
// so that WPF knows to update any UI that uses this command.
public bool IsEnabled
{
get { return this.isEnabled; }
set
{
this.isEnabled = value;
this.OnCanExecuteChanged();
}
}
// Simple event propagation that makes sure that someone is listening to the event before raising it.
private void OnCanExecuteChanged()
{
if (this.CanExecuteChanged != null)
{
this.CanExecuteChanged(this, EventArgs.Empty);
}
}
}
}
ProductPhotosCollection is in namespace AWADataSource while DelegateCommand is in AWDataSource.
Probably a typo, but you either need to put them in the same namespace, or use a using to import the AWDataSource namespace into ProductPhotosCollection (or in your case "Class1.cs")

How can I catch scroll events in windows forms PropertyGrid

I'm trying to synchronize the vertical scrollbars of two property grids. The idea is when a user scrolls one property grid the other property grid scrolls by the same amount.
My first approach was to handle the scroll event but it seems PropertyGrid doesn't generate this kind of event. I looked into the controls contained inside the PropertyGrid and there is a PropertyGridView, that I bet is the control with the scrollbar.
Does anybody know a workaround to achieve what I want?
Thank you.
This one shows the synchronization with the neighboring PropertyGridView. Note you will have to extend it to handle the user clicking on either control. This version updates propertyGrid2 to match propertyGrid1, but not vice versa.
using System;
using System.Windows.Forms;
using System.Reflection;
namespace WindowsApplication1
{
public partial class Form1 : Form
{
Control m_pgv_1 = null;
Control m_pgv_2 = null;
MethodInfo m_method_info;
public Form1 ()
{
InitializeComponent ();
// Set the Property Grid Object to something
propertyGrid1.SelectedObject = dataGridView1;
propertyGrid2.SelectedObject = dataGridView1;
// Loop through sub-controlls and find PropertyGridView
m_pgv_1 = FindControl (propertyGrid1.Controls, "PropertyGridView");
m_pgv_2 = FindControl (propertyGrid2.Controls, "PropertyGridView");
// Reflection trickery to get a private/internal field
// and method, scrollBar and SetScrollOffset in this case
Type type = m_pgv_1.GetType ();
FieldInfo f = FindField (type, "scrollBar");
m_method_info = FindMethod (type, "SetScrollOffset");
// Get the scrollBar for our PropertyGrid and add the event handler
((ScrollBar)f.GetValue (m_pgv_1)).Scroll +=
new ScrollEventHandler (propertyGrid1_Scroll);
}
private void propertyGrid1_Scroll (object sender, ScrollEventArgs e)
{
System.Console.WriteLine ("Scroll");
// Set the new scroll position on the neighboring
// PropertyGridView
object [] parameters = { e.NewValue };
m_method_info.Invoke (m_pgv_2, parameters);
}
private static Control FindControl (
Control.ControlCollection controls, string name)
{
foreach (Control c in controls)
{
if (c.Text == name)
return c;
}
return null;
}
private static MethodInfo FindMethod (Type type, string method)
{
foreach (MethodInfo mi in type.GetMethods ())
{
if (method == mi.Name)
return mi;
}
return null;
}
private static FieldInfo FindField (Type type, string field)
{
FieldInfo f = type.GetField (field,
BindingFlags.Instance | BindingFlags.NonPublic);
return f;
}
}
}
It requires a little trickery, but this should do it:
using System;
using System.Windows.Forms;
using System.Reflection;
namespace WindowsApplication1 {
public partial class Form1 : Form {
Control m_pgv = null;
public Form1 () {
InitializeComponent ();
// Set the Property Grid Object to something
propertyGrid1.SelectedObject = dataGridView1;
// Loop through sub-controls and find PropertyGridView
foreach (Control c in propertyGrid1.Controls) {
if (c.Text == "PropertyGridView")
{
m_pgv = (Control)c;
break;
}
}
// Reflection trickery to get a private field,
// scrollBar in this case
Type t = m_pgv.GetType ();
FieldInfo f = t.GetField("scrollBar",
BindingFlags.Instance | BindingFlags.NonPublic);
// Get the scrollBar for our PropertyGrid and add the event handler
ScrollBar sb = (ScrollBar) f.GetValue(m_pgv);
sb.Scroll += new ScrollEventHandler(propertyGrid1_Scroll);
}
private void propertyGrid1_Scroll (object sender, ScrollEventArgs e) {
System.Console.WriteLine ("Scroll");
}
}
}

Silverlight DataBinding cross thread issue

I have an Image control with it's source bound to a property on an object(string url to an image). After making a service call, i update the data object with a new URL. The exception is thrown after it leaves my code, after invoking the PropertyChanged event.
The data structure and the service logic are all done in a core dll that has no knowledge of the UI. How do I sync up with the UI thread when i cant access a Dispatcher?
PS: Accessing Application.Current.RootVisual in order to get at a Dispatcher is not a solution because the root visual is on a different thread(causing the exact exception i need to prevent).
PPS: This only is a problem with the image control, binding to any other ui element, the cross thread issue is handled for you.
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => {...});
Also look here.
Have you tried implementing INotifyPropertyChanged?
The property getter for RootVisual on the Application class has a thread check which causes that exception. I got around this by storing the root visual's dispatcher in my own property in my App.xaml.cs:
public static Dispatcher RootVisualDispatcher { get; set; }
private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new Page();
RootVisualDispatcher = RootVisual.Dispatcher;
}
If you then call BeginInvoke on App.RootVisualDispatcher rather than Application.Current.RootVisual.Dispatcher you shouldn't get this exception.
I ran into a similar issue to this, but this was in windows forms:
I have a class that has it's own thread, updating statistics about another process, there is a control in my UI that is databound to this object. I was running into cross-thread call issues, here is how I resolved it:
Form m_MainWindow; //Reference to the main window of my application
protected virtual void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
if(m_MainWindow.InvokeRequired)
m_MainWindow.Invoke(
PropertyChanged, this, new PropertyChangedEventArgs(propertyName);
else
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
This seems to work great, if anyone has suggestions, please let me know.
When ever we want to update UI related items that action should happen in the UI thread else you will get an invalid cross thread access exception
Deployment.Current.Dispatcher.BeginInvoke( () =>
{
UpdateUI(); // DO the actions in the function Update UI
});
public void UpdateUI()
{
//to do :Update UI elements here
}
The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed.
For example, consider a Person object with a property called FirstName. To provide generic property-change notification, the Person type implements the INotifyPropertyChanged interface and raises a PropertyChanged event when FirstName is changed.
For change notification to occur in a binding between a bound client and a data source, your bound type should either:
Implement the INotifyPropertyChanged interface (preferred).
Provide a change event for each property of the bound type.
Do not do both.
Example:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
// Change the namespace to the project name.
namespace TestNotifyPropertyChangedCS
{
// This form demonstrates using a BindingSource to bind
// a list to a DataGridView control. The list does not
// raise change notifications. However the DemoCustomer type
// in the list does.
public partial class Form1 : Form
{
// This button causes the value of a list element to be changed.
private Button changeItemBtn = new Button();
// This DataGridView control displays the contents of the list.
private DataGridView customersDataGridView = new DataGridView();
// This BindingSource binds the list to the DataGridView control.
private BindingSource customersBindingSource = new BindingSource();
public Form1()
{
InitializeComponent();
// Set up the "Change Item" button.
this.changeItemBtn.Text = "Change Item";
this.changeItemBtn.Dock = DockStyle.Bottom;
this.changeItemBtn.Click +=
new EventHandler(changeItemBtn_Click);
this.Controls.Add(this.changeItemBtn);
// Set up the DataGridView.
customersDataGridView.Dock = DockStyle.Top;
this.Controls.Add(customersDataGridView);
this.Size = new Size(400, 200);
}
private void Form1_Load(object sender, EventArgs e)
{
// Create and populate the list of DemoCustomer objects
// which will supply data to the DataGridView.
BindingList<DemoCustomer> customerList = new BindingList<DemoCustomer>();
customerList.Add(DemoCustomer.CreateNewCustomer());
customerList.Add(DemoCustomer.CreateNewCustomer());
customerList.Add(DemoCustomer.CreateNewCustomer());
// Bind the list to the BindingSource.
this.customersBindingSource.DataSource = customerList;
// Attach the BindingSource to the DataGridView.
this.customersDataGridView.DataSource =
this.customersBindingSource;
}
// Change the value of the CompanyName property for the first
// item in the list when the "Change Item" button is clicked.
void changeItemBtn_Click(object sender, EventArgs e)
{
// Get a reference to the list from the BindingSource.
BindingList<DemoCustomer> customerList =
this.customersBindingSource.DataSource as BindingList<DemoCustomer>;
// Change the value of the CompanyName property for the
// first item in the list.
customerList[0].CustomerName = "Tailspin Toys";
customerList[0].PhoneNumber = "(708)555-0150";
}
}
// This is a simple customer class that
// implements the IPropertyChange interface.
public class DemoCustomer : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private Guid idValue = Guid.NewGuid();
private string customerNameValue = String.Empty;
private string phoneNumberValue = String.Empty;
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// The constructor is private to enforce the factory pattern.
private DemoCustomer()
{
customerNameValue = "Customer";
phoneNumberValue = "(312)555-0100";
}
// This is the public factory method.
public static DemoCustomer CreateNewCustomer()
{
return new DemoCustomer();
}
// This property represents an ID, suitable
// for use as a primary key in a database.
public Guid ID
{
get
{
return this.idValue;
}
}
public string CustomerName
{
get
{
return this.customerNameValue;
}
set
{
if (value != this.customerNameValue)
{
this.customerNameValue = value;
NotifyPropertyChanged();
}
}
}
public string PhoneNumber
{
get
{
return this.phoneNumberValue;
}
set
{
if (value != this.phoneNumberValue)
{
this.phoneNumberValue = value;
NotifyPropertyChanged();
}
}
}
}
}

Resources