Infragistics UltraGrid (9.2) Band duplication after re-creating the datasource - winforms

I'm working my brain off trying to figure out a strange behavior of the UltraGrid Bands mechanism. The below code contains everything you need to re-create the problem.
I'm creating a DataSet and filling it with two DataTables. Then I assign the DataSet as a DataSource of BindingSource object. That BindingSource object is set as an UltraGrid.DataSource.
The below code will be helpful to fully understand what's going on after a single PrepareData() method is launched. The grid displays 3 records with the first one expandable to show the second Band referenced to it by DataRelation. You can check that by commenting out the second call.
public partial class Form1 : Form {
BindingSource bs = new BindingSource();
DataSet ds = new DataSet();
public Form1() {
InitializeComponent();
DoLayout();
Bind();
PrepareData();
PrepareData();
}
private void DoLayout() {
ultraGrid1.DisplayLayout.Override.ExpansionIndicator = Infragistics.Win.UltraWinGrid.ShowExpansionIndicator.CheckOnDisplay;
}
private void Bind() {
bs.DataSource = ds;
ultraGrid1.DataSource = bs;
}
private void PrepareData() {
// you need to keep watching the ultraGrid1.DisplayLayout.Bands.All property
// all the time - normally it contains one entry,
// the default {NewDataSet} entry
bs.SuspendBinding();
ds.Relations.Clear();
ds.Clear();
ds.Tables.Clear();
// when the above code will run for the second time, you can see that
// Bands.All property will claim to have the standard {NewDataSet} entry
// again. Seems like everything is working well
DataTable dt = new DataTable("TABLE1");
dt.Columns.Add("col1");
dt.Columns.Add("col2");
dt.Columns.Add("col3");
dt.Rows.Add("1", "1", "1");
dt.Rows.Add("2", "2", "2");
dt.Rows.Add("3", "3", "3");
// after adding the datatable to the dataset, we can see that there was
// a change made to the DisplayLayout.Band.All list of the ultraGrid1
// {NewDataSet} will change to {TABLE1}
//
// now watch the behavior when the method is run for the second time
ds.Tables.Add(dt);
// after the second run, you can see the first problem in the Bands.All entries
dt = new DataTable("TABLE2");
dt.Columns.Add("scol1");
dt.Columns.Add("scol2");
dt.Rows.Add("s1", "1");
dt.Rows.Add("s2", "1");
dt.Rows.Add("s3", "1");
// the Bands.All property still will say there is only one element in it
// but not anymore once the code is executed for the second time
ds.Tables.Add(dt); // for the second code run - here is the second problem
// now the first time we add that relation, you can see that
// a new entry exists in the Bands.All property of the ultraGrid1: {T1T2}
dt.ParentRelations.Add("T1T2", ds.Tables["TABLE1"].Columns["col1"], dt.Columns["scol2"], false);
// after the second run of the code, here you can see the third problem
bs.ResumeBinding();
}
}
Now if you run the PrepareData() method once more, the grid gets messy. I actually found what's causing the problem, thus the heavy comments in the code above, but I can't figure out why is this happening. As a result I simply want the grid to behave exactly the same no matter how many times the method is called.
Does anyone have a clue what could be the reason for this?
I already tried to null the DataSources, and re-assigning them; tired changing the order of the methods called; even tried to invoke non-public methods of the BandsCollection object like "ClearAllButBandZero" and "Clear", but it was all to no avail.
I've found an artible in the KnowledgeBase of the Infragistics DevCenter:
http://devcenter.infragistics.com/Support/KnowledgeBaseArticle.Aspx?ArticleID=1751
but that didn't help in my case. Each time the DataSet is reconstructed, the UltraGrid.DisplayLayout.Bands collection is getting more and more messy.

It is not necessary to use BindingSource to add a dataset as a datasource in UltraGrid. You can assign the dataset directly as grid's datasource. Take a look to the following code:
public partial class Form1 : Form
{
DataSet ds = new DataSet();
public Form1()
{
InitializeComponent();
DoLayout();
PrepareData();
PrepareData();
}
private void DoLayout()
{
ultraGrid1.DisplayLayout.Override.ExpansionIndicator = Infragistics.Win.UltraWinGrid.ShowExpansionIndicator.CheckOnDisplay;
}
private void Bind()
{
ultraGrid1.DataSource = ds;
}
private void PrepareData()
{
ds = null;
ds = new DataSet();
DataTable dt = new DataTable("TABLE1");
dt.Columns.Add("col1");
dt.Columns.Add("col2");
dt.Columns.Add("col3");
dt.Rows.Add("1", "1", "1");
dt.Rows.Add("2", "2", "2");
dt.Rows.Add("3", "3", "3");
ds.Tables.Add(dt);
dt = new DataTable("TABLE2");
dt.Columns.Add("scol1");
dt.Columns.Add("scol2");
dt.Rows.Add("s1", "1");
dt.Rows.Add("s2", "1");
dt.Rows.Add("s3", "1");
ds.Tables.Add(dt);
dt.ParentRelations.Add("T1T2", ds.Tables["TABLE1"].Columns["col1"], dt.Columns["scol2"], false);
Bind();
}
}

I've found what's causing such behaviour. It's a bug in the .NET Framework.
There is a private field on the BindingSource class:
private Dictionary<string, BindingSource> relatedBindingSources;
The problem is that once you assign null to your BindingSource.DataSource, the Dictionary remains untouched. Then you assign new DataSource, add a relation in it, the Dictionary grows. And never stops.
My solution to this problem was creating a simple method, that will assure me that all possible relations are removed:
private void ClearRelatedBindingSources(BindingSource bindingSource) {
System.Reflection.FieldInfo fi = bindingSource.GetType().GetField("relatedBindingSources", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
Dictionary<string, BindingSource> relatedBsDict = fi.GetValue(bindingSource) as Dictionary<string, BindingSource>;
if (relatedBsDict != null) {
relatedBsDict.Clear();
}
}
All there is to do is to call that method after nulling the BindingSource.DataSource property. That essentially solved the problem of band replication in my grids.
Many thanks to Thanasis thanks to whom I began to dig in the right place ;)

Related

Populating a table from a file only last column is populated JavaFX [duplicate]

This has baffled me for a while now and I cannot seem to get the grasp of it. I'm using Cell Value Factory to populate a simple one column table and it does not populate in the table.
It does and I click the rows that are populated but I do not see any values in them- in this case String values. [I just edited this to make it clearer]
I have a different project under which it works under the same kind of data model. What am I doing wrong?
Here's the code. The commented code at the end seems to work though. I've checked to see if the usual mistakes- creating a new column instance or a new tableview instance, are there. Nothing. Please help!
//Simple Data Model
Stock.java
public class Stock {
private SimpleStringProperty stockTicker;
public Stock(String stockTicker) {
this.stockTicker = new SimpleStringProperty(stockTicker);
}
public String getstockTicker() {
return stockTicker.get();
}
public void setstockTicker(String stockticker) {
stockTicker.set(stockticker);
}
}
//Controller class
MainGuiController.java
private ObservableList<Stock> data;
#FXML
private TableView<Stock> stockTableView;// = new TableView<>(data);
#FXML
private TableColumn<Stock, String> tickerCol;
private void setTickersToCol() {
try {
Statement stmt = conn.createStatement();//conn is defined and works
ResultSet rsltset = stmt.executeQuery("SELECT ticker FROM tickerlist order by ticker");
data = FXCollections.observableArrayList();
Stock stockInstance;
while (rsltset.next()) {
stockInstance = new Stock(rsltset.getString(1).toUpperCase());
data.add(stockInstance);
}
} catch (SQLException ex) {
Logger.getLogger(WriteToFile.class.getName()).log(Level.SEVERE, null, ex);
System.out.println("Connection Failed! Check output console");
}
tickerCol.setCellValueFactory(new PropertyValueFactory<Stock,String>("stockTicker"));
stockTableView.setItems(data);
}
/*THIS, ON THE OTHER HAND, WORKS*/
/*Callback<CellDataFeatures<Stock, String>, ObservableValue<String>> cellDataFeat =
new Callback<CellDataFeatures<Stock, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(CellDataFeatures<Stock, String> p) {
return new SimpleStringProperty(p.getValue().getstockTicker());
}
};*/
Suggested solution (use a Lambda, not a PropertyValueFactory)
Instead of:
aColumn.setCellValueFactory(new PropertyValueFactory<Appointment,LocalDate>("date"));
Write:
aColumn.setCellValueFactory(cellData -> cellData.getValue().dateProperty());
For more information, see this answer:
Java: setCellValuefactory; Lambda vs. PropertyValueFactory; advantages/disadvantages
Solution using PropertyValueFactory
The lambda solution outlined above is preferred, but if you wish to use PropertyValueFactory, this alternate solution provides information on that.
How to Fix It
The case of your getter and setter methods are wrong.
getstockTicker should be getStockTicker
setstockTicker should be setStockTicker
Some Background Information
Your PropertyValueFactory remains the same with:
new PropertyValueFactory<Stock,String>("stockTicker")
The naming convention will seem more obvious when you also add a property accessor to your Stock class:
public class Stock {
private SimpleStringProperty stockTicker;
public Stock(String stockTicker) {
this.stockTicker = new SimpleStringProperty(stockTicker);
}
public String getStockTicker() {
return stockTicker.get();
}
public void setStockTicker(String stockticker) {
stockTicker.set(stockticker);
}
public StringProperty stockTickerProperty() {
return stockTicker;
}
}
The PropertyValueFactory uses reflection to find the relevant accessors (these should be public). First, it will try to use the stockTickerProperty accessor and, if that is not present fall back to getters and setters. Providing a property accessor is recommended as then you will automatically enable your table to observe the property in the underlying model, dynamically updating its data as the underlying model changes.
put the Getter and Setter method in you data class for all the elements.

Why does this FilterEventHandler seem to remain in the CollectionViewSource filter even after the filter is set to null?

Summary:
I'm creating a simple application for practice, where the user can maintain and query a collection of Things. On the UI are several TextBoxes and ComboBoxes with which they can filter the collection.
The three buttons I'm concerned with are [Filter], [Random], and [All]. [Filter] applies the current filter options. [Random] applies the current filter options (if any), and then only shows one random entry from the filtered results. [All], as expected, shows the unfiltered collection.
To fully understand the background for the question, I'll provide the relevant code.
Here is where anything having to do with the CollectionViewSource (or any relevant code I'm posting) gets declared:
//Members
private ObservableCollection<Thing> _myDataCollection;
private CollectionViewSource _CVS;
private Thing _randomThing;
//Properties
public ObservableCollection<Thing> MyDataCollection
{
get { return _myDataCollection; }
set
{
if (_myDataCollection!= value)
{
_myDataCollection= value;
RaisePropertyChanged(() => MyDataCollection);
}
}
}
public CollectionViewSource CVS
{
get { return _CVS; }
set
{
if (_CVS != value)
{
_CVS = value;
RaisePropertyChanged(() => CVS);
}
}
}
public ICollectionView CVSView
{
get { return CVS.View; }
}
Here is where the CVS is initialized (in the window view-model's constructor). For now, the data collection is populated with a ton of random things (that's all that RandomizeData() does).
MyDataCollection = new ObservableCollection<Thing>();
RandomizeData();
CVS = new CollectionViewSource();
CVS.Source = MyDataCollection;
Here is the code for the [Filter] button's command:
//Clear any stale filter options and rebuild with the most current ones.
CVSView.Filter = null;
//IF THE FOLLOWING LINE IS UNCOMMENTED, THE ISSUE OCCURS.
//CVS.Filter -= new FilterEventHandler(SingleRandomFromCollectionFilter);
BuildCVSFilter();
The code for the [All] button literally just clears the filter:
CVSView.Filter = null;
The code for the [Random] button. I suspect the issue is coming from the handler added here.
//Clear any stale filter options and rebuild with the most current ones.
CVSView.Filter = null;
//IF THE FOLLOWING LINE IS UNCOMMENTED, THE ISSUE OCCURS.
//CVS.Filter -= new FilterEventHandler(SingleRandomFromCollectionFilter);
BuildCVSFilter();
//Only proceed if there are actually results in the filtered collection.
int resultsCount = CVSView.Cast<Thing>().Count();
if (resultsCount > 0)
{
//Point to a random thing in the filtered collection.
CVSView.MoveCurrentToPosition(random.Next(0, resultsCount));
_randomThing = CVSView.CurrentItem as Thing;
//Add another filter event that further constrains the collection to only contain the random thing.
CVS.Filter += new FilterEventHandler(SingleRandomFromCollectionFilter);
}
And here is the code for that BuildCVSFilter() I've been calling. I use this so that I can use multiple filters concurrently. The "FilterOption" strings are properties that are bound to the values of the UI controls.
if (!string.IsNullOrEmpty(FilterOption1))
{
CVS.Filter += new FilterEventHandler(Fitler1);
}
if (!string.IsNullOrEmpty(FilterOption2) && FilterOption2 != "ignore")
{
CVS.Filter += new FilterEventHandler(Fitler2);
}
if (!string.IsNullOrEmpty(FilterOption3))
{
CVS.Filter += new FilterEventHandler(Filter3);
}
Each filter that gets added this way only sets e.Accepted to false, if applicable, and leaves it alone if it's true.
Issue:
If I click on [Random] at all, it seems like the FilterEventHandler that gets added there does not go away. This makes it so that selecting [Filter] after [Random] won't work as expected, because it's only going to filter from the current collection of one thing. Additionally, selecting [Random] a second time seems to reuse the same random thing (instead of finding a new one). Interestingly enough, clicking [All] still works just fine. It shows everything.
When I go into those [Filter] and [Random] OnCommand methods and explicitly add a line to remove that SingleRandomFromCollectionFilter handler, everything works as expected.
Why would NameEntriesView.Filter = null; work to clear the filter on [All], but not on [Filter] or [Random]? Is there something about the CollectionViewSource and its implementation that I'm not fully understanding?

How do I position a datagridview to the searched text input

Using Windows forms and linq to Sql, I bound a datagridview to Products Table, I added to the form 1 Textbox to input the searched text.
I wonder how to position the datagridview according to the text entered to find a given ProductName.
Here I do not want to filter rows, I only want to reposition datagrid after each character entered, the used code:
private void textBox1_TextChanged(object sender, EventArgs e)
{
var searchValue = textBox1.Text.Trim().ToUpper();
var qry = (from p in dc.Products
where p.ProductName.ToUpper().StartsWith(searchValue)
select p).ToList();
int itemFound = productBindingSource.Find("ProductName", searchValue);
productBindingSource.Position = itemFound;
}
The execution of code give the next error: System.NotSupportedException was unhandled at the ligne:
int itemFound = productBindingSource.Find("ProductName", searchValue);
Any idea please ?
The MSDN documentation for BindingSource has the answer:
The Find method can only be used when the underlying list is an
IBindingList with searching implemented. This method simply refers the
request to the underlying list's IBindingList.Find method. For
example, if the underlying data source is a DataSet, DataTable, or
DataView, this method converts propertyName to a PropertyDescriptor
and calls the IBindingList.Find method. The behavior of Find, such as
the value returned if no matching item is found, depends on the
implementation of the method in the underlying list.
When you call this method on a BindingSource whose underlying data source does not implement IBindingList then you see the exception (thrown by the default implementation of IBindingList.FindCore:
System.NotSupportedException: The specified method is not supported.
You don't show what you bind the binding source to but clearly it doesn't implement this method.
Annoyingly, BindingList<T> the recommended list type to use for your data source does not provide a FindCore implementation.
If you are using BindingList you will need to create your own custom type. Here is the code for an absolutely bare bones implementation of a BindingList that supports find:
public class FindableBindingList<T> : BindingList<T>
{
public FindableBindingList()
: base()
{
}
public FindableBindingList(List<T> list)
: base(list)
{
}
protected override int FindCore(PropertyDescriptor property, object key)
{
for (int i = 0; i < Count; i++)
{
T item = this[i];
if (property.GetValue(item).Equals(key))
{
return i;
}
}
return -1; // Not found
}
}
You can do lots with your own implementations of BindingList such as supporting sorting. I've left my answer as just the minimum to support the find method. Search for SortableBindingList if you want to know more.
To use this class do something like this:
var qry = (from p in dc.Products
where p.ProductName.ToUpper().StartsWith(searchValue)
select p).ToList();
FindableBindingList<YourType> list = new FindableBindingList<YourType>(qry);
dataGridView1.DataSource = list;

How to maintain ComboBox.SelectedItem reference when DataSource is resorted?

This really seems like a bug to me, but perhaps some databinding gurus can enlighten me? (My WinForms databinding knowledge is quite limited.)
I have a ComboBox bound to a sorted DataView. When the properties of the items in the DataView change such that items are resorted, the SelectedItem in my ComboBox does not keep in-sync. It seems to point to someplace completely random. Is this a bug, or am I missing something in my databinding?
Here is a sample application that reproduces the problem. All you need is a Button and a ComboBox:
public partial class Form1 : Form
{
private DataTable myData;
public Form1()
{
this.InitializeComponent();
this.myData = new DataTable();
this.myData.Columns.Add("ID", typeof(int));
this.myData.Columns.Add("Name", typeof(string));
this.myData.Columns.Add("LastModified", typeof(DateTime));
this.myData.Rows.Add(1, "first", DateTime.Now.AddMinutes(-2));
this.myData.Rows.Add(2, "second", DateTime.Now.AddMinutes(-1));
this.myData.Rows.Add(3, "third", DateTime.Now);
this.myData.DefaultView.Sort = "LastModified DESC";
this.comboBox1.DataSource = this.myData.DefaultView;
this.comboBox1.ValueMember = "ID";
this.comboBox1.DisplayMember = "Name";
}
private void saveStuffButton_Click(object sender, EventArgs e)
{
DataRowView preUpdateSelectedItem = (DataRowView)this.comboBox1.SelectedItem;
// OUTPUT: SelectedIndex = 0; SelectedItem.Name = third
Debug.WriteLine(string.Format("SelectedIndex = {0:N0}; SelectedItem.Name = {1}", this.comboBox1.SelectedIndex, preUpdateSelectedItem["Name"]));
this.myData.Rows[0]["LastModified"] = DateTime.Now;
DataRowView postUpdateSelectedItem = (DataRowView)this.comboBox1.SelectedItem;
// OUTPUT: SelectedIndex = 2; SelectedItem.Name = second
Debug.WriteLine(string.Format("SelectedIndex = {0:N0}; SelectedItem.Name = {1}", this.comboBox1.SelectedIndex, postUpdateSelectedItem["Name"]));
// FAIL!
Debug.Assert(object.ReferenceEquals(preUpdateSelectedItem, postUpdateSelectedItem));
}
}
To clarify:
I understand how I would fix the simple application above--I only included that to demonstrate the problem. My concern is how to fix it when the updates to the underlying data rows could be happening anywhere (on another form, perhaps.)
I would really like to still receive updates, inserts, deletes, etc. to my data source. I have tried just binding to an array of DataRows severed from the DataTable, but this causes additional headaches.
Just add a BindingContext to the ComboBox :
this.comboBox1.DataSource = this.myData.DefaultView;
this.comboBox1.BindingContext = new BindingContext();
this.comboBox1.ValueMember = "ID";
this.comboBox1.DisplayMember = "Name";
By the way, try not keeping auto-generated names for your widgets (comboBox1, ...), it is dirty. :-P
The only promising solution I see at this time is to bind the combo box to a detached data source and then update it every time the "real" DataView changes. Here is what I have so far. Seems to be working, but (1) it's a total hack, and (2) it will not scale well at all.
In form declaration:
private DataView shadowView;
In form initialization:
this.comboBox1.DisplayMember = "Value";
this.comboBox1.ValueMember = "Key";
this.shadowView = new DataView(GlobalData.TheGlobalTable, null, "LastModified DESC", DataViewRowState.CurrentRows);
this.shadowView.ListChanged += new ListChangedEventHandler(shadowView_ListChanged);
this.ResetComboBoxDataSource(null);
And then the hack:
private void shadowView_ListChanged(object sender, ListChangedEventArgs e)
{
this.ResetComboBoxDataSource((int)this.comboBox1.SelectedValue);
}
private void ResetComboBoxDataSource(int? selectedId)
{
int selectedIndex = 0;
var detached = new KeyValuePair<int, string>[this.shadowView.Count];
for (int i = 0; i < this.shadowView.Count; i++)
{
int id = (int)this.shadowView[i]["ID"];
detached[i] = new KeyValuePair<int, string>(id, (string)this.shadowView[i]["Name"]);
if (id == selectedId)
{
selectedIndex = i;
}
}
this.comboBox1.DataSource = detached;
this.comboBox1.SelectedIndex = selectedIndex;
}
Must detach event handler in Dispose:
this.shadowView.ListChanged -= new ListChangedEventHandler(shadowView_ListChanged);
Your example sorts the data on the column it updates. When the update occurs, the order of the rows changes. The combobox is using the index to keep track of it's selected items, so when the items are sorted, the index is pointing to a different row. You'll need to capture the value of comboxBox1.SelectedItem before updating the row, and set it back once the update is complete:
DataRowView selected = (DataRowView)this.comboBox1.SelectedItem;
this.myData.Rows[0]["LastModified"] = DateTime.Now;
this.comboBox1.SelectedItem = selected;
From an architecture perspective, the SelectedItem must be cleared when rebinding the DataSource because the DataBinder don't know if your SelectedItem will persist or not.
From a functional perspective, the DataBinder may not be able to ensure that your SelectedItem from you old DataSource is the same in your new DataSource (it can be a different DataSource with the same SelectedItem ID).
Its more an application feature or a custom control feature than a generic databinding process.
IMHO, you have theses choices if you want to keep the SelectedItem on rebind :
Create a reusable custom control / custom DataBinder with a persistance option which try to set the SelectedItem with all your data validation (using a DataSource / item identification to ensure the item validity)
Persist it specifically on your Form using the Form/Application context (like ViewState for ASP.NET).
Some controls on the .NET market are helping you by rebinding (including selections) the control from their own persisted DataSource if the DataSource is not changed and DataBind not recalled. That's the best pratice.

NHibernate, WinForms, and DataBinding - do they play well together?

I've been using WinForms databinding to display data from a database mapped with Fluent NHibernate, and that's been working great.
For example, I can just set a DataGridView's DataSource property from an entity's IList property, and voila - there's all the data!
But now I need to start adding and saving new data rows, and that's not going so well. I thought I'd be able to just enable the grid's AllowUserToAddRows property, and new rows would get added to the underlying IList in the entity, but that didn't work.
Then, after a little searching, I tried setting the DataSource property to a BindingList that was populated from the IList, but that's not being updated with new rows either.
During the course of my searches, I also came upon a few people reporting difficulty with WinForms and DataBinding in general, which makes me wonder if I should pursue that approach any further.
Is the DataBinding approach worth continuing? If so, can anyone suggest where I'm going wrong?
Or is it better to just handle all the DataGridView events associated with adding a new row, and writing my own code to add new objects to the IList property in my entity?
Other suggestions? (though I don't think switching to WPF is going to be an option, no matter how much better the databinding may be)
Can you load (or copy) your nHibernate entities into a generic List? If so, I have had good success in with two-way binding using a DataGridView bound to a generic List.
The key points are:
The generic list contains list objects where each is an instance of your custom class.
Your custom class must implement public properties for each of the fields to bind. Public fields didn't work for me.
Use a BindingSource to wrap the actual generic list.
The BindingSOurce allows you to set the AllowNew property to true. Binding directly to the List almost works, but the DataGridVieww does not display the "New row" line, even if AllowUsersToAddRows = true.
For example, add this code to a Form with a dataGridView1:
private List<MyObject> m_data = new List<MyObject>();
private BindingSource m_bs =new BindingSource();
private void Form1_Load(object sender, EventArgs e)
{
m_data.Add(new MyObject(0,"One",DateTime.Now));
m_data.Add(new MyObject(1, "Two", DateTime.Now));
m_data.Add(new MyObject(2, "Three", DateTime.Now));
m_bs.DataSource = m_data;
m_bs.AllowNew = true;
dataGridView1.DataSource = m_bs;
dataGridView1.AutoGenerateColumns = true;
dataGridView1.AllowUserToAddRows = true;
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
for (int i = 0; i < m_data.Count ; i++)
{
Console.WriteLine(string.Format("{0} {1} {2}", m_data[i].ID, m_data[i].Name, m_data[i].DOB));
}
}
}
public class MyObject
{
// Default ctor, required for adding new rows in DataGridView
public MyObject()
{
}
public MyObject(int id, string name, DateTime dob)
{
ID = id;
Name = name;
DOB = dob;
}
private int m_id;
public int ID
{
get
{
return m_id;
}
set
{
m_id = value;
}
}
private string m_name;
public string Name
{
get
{
return m_name;
}
set
{
m_name = value;
}
}
private DateTime m_dob;
public DateTime DOB
{
get
{
return m_dob;
}
set
{
m_dob = value;
}
}
}
When the form closes, the contents of the bound List are printed to the Output window.

Resources