|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionWhen I create a new control, it often has to display some data in it. Usually, the data comes from a database or a If you bind more than one control at the same data source (for example, if you want to create a details-form with some "Next" and "Previous" buttons), you will notice that they will all have the "Current-Ability", meaning, all controls display the data from the same row. If you click "Next" (or click another row in a At least, I want to implement the ability to change the data of the current row at any column. OverviewFor all these to be implemented, there is a simple solution: use the Implementing DataSource and DataMemberEvery control written by Microsoft with the support for complex data binding ( private object dataSource;
[TypeConverter("System.Windows.Forms.Design.DataSourceConverter, System.Design")]
[Category("Data")]
[DefaultValue(null)]
public object DataSource
{
get
{
return this.dataSource;
}
set
{
if (this.dataSource != value)
{
this.dataSource = value;
tryDataBinding();
}
}
}
The attribute
If you don't need this function, you can leave this attribute away. The method Implementing the private string dataMember;
[Category("Data")]
[Editor("System.Windows.Forms.Design.DataMemberListEditor,
System.Design", "System.Drawing.Design.UITypeEditor,
System.Drawing")]
[DefaultValue("")]
public string DataMember
{
get
{
return this.dataMember;
}
set
{
if (this.dataMember != value)
{
this.dataMember = value;
tryDataBinding();
}
}
}
Here, you have to define the attribute Using the BindingContext PropertyThe protected override void OnBindingContextChanged(EventArgs e)
{
this.tryDataBinding();
base.OnBindingContextChanged(e);
}
How to Get a CurrencyManagerI have implemented the part of getting the To wire the private ListChangedEventHandler listChangedHandler;
private EventHandler positionChangedHandler;
You have to initialize these fields in your constructor: listChangedHandler = new ListChangedEventHandler(dataManager_ListChanged);
positionChangedHandler = new EventHandler(dataManager_PositionChanged);
I will show the implementation of these methods later. The next code shows the method private CurrencyManager dataManager;
private void tryDataBinding()
{
if (this.DataSource == null ||
base.BindingContext == null)
return;
CurrencyManager cm;
try
{
cm = (CurrencyManager)
base.BindingContext[this.DataSource,
this.DataMember];
}
catch (System.ArgumentException)
{
// If no CurrencyManager was found
return;
}
if (this.dataManager != cm)
{
// Unwire the old CurrencyManager
if (this.dataManager != null)
{
this.dataManager.ListChanged -=
listChangedHandler;
this.dataManager.PositionChanged -=
positionChangedHandler;
}
this.dataManager = cm;
// Wire the new CurrencyManager
if (this.dataManager != null)
{
this.dataManager.ListChanged +=
listChangedHandler;
this.dataManager.PositionChanged +=
positionChangedHandler;
}
// Update metadata and data
calculateColumns();
updateAllData();
}
}
Getting the Available ColumnsTo get the columns the data source provides, you can use the method private void calculateColumns()
{
this.Columns.Clear();
if (dataManager == null)
return;
foreach (PropertyDescriptor prop in
dataManager.GetItemProperties())
{
ColumnHeader column = new ColumnHeader();
column.Text = prop.Name;
this.Columns.Add(column);
}
}
Retrieving the DataFor retrieving the data, the In the method private void updateAllData()
{
this.Items.Clear();
for (int i = 0; i < dataManager.Count; i++ )
{
addItem(i);
}
}
private void addItem(int index)
{
ListViewItem item = getListViewItem(index);
this.Items.Insert(index, item);
}
private ListViewItem getListViewItem(int index)
{
object row = dataManager.List[index];
PropertyDescriptorCollection propColl =
dataManager.GetItemProperties();
ArrayList items = new ArrayList();
// Fill value for each column
foreach(ColumnHeader column in this.Columns)
{
PropertyDescriptor prop = null;
prop = propColl.Find(column.Text, false);
if (prop != null)
{
items.Add(prop.GetValue(row).ToString());
}
}
return new ListViewItem((string[])items.ToArray(typeof(string)));
}
Keeping Your Data CurrentAt this point, your control displays the data after initializing. But what happens when the data changes after initializing by any other control? When this happens, the
In addition to those types, there are three types which tells you that the schema has been changed ( So you can use this information to show the current data without recreating the full list every time something changes. The information of which item was changed can be obtained by the property private void updateItem(int index)
{
if (index >= 0 &&
index < this.Items.Count)
{
ListViewItem item = getListViewItem(index);
this.Items[index] = item;
}
}
private void deleteItem(int index)
{
if (index >= 0 &&
index < this.Items.Count)
this.Items.RemoveAt(index);
}
In The implementation of private void dataManager_ListChanged(object sender, ListChangedEventArgs e)
{
if (e.ListChangedType == ListChangedType.Reset ||
e.ListChangedType == ListChangedType.ItemMoved)
{
// Update all data
updateAllData();
}
else if (e.ListChangedType == ListChangedType.ItemAdded)
{
// Add new Item
addItem(e.NewIndex);
}
else if (e.ListChangedType == ListChangedType.ItemChanged)
{
// Change Item
updateItem(e.NewIndex);
}
else if (e.ListChangedType == ListChangedType.ItemDeleted)
{
// Delete Item
deleteItem(e.NewIndex);
}
else
{
// Update metadata and all data
calculateColumns();
updateAllData();
}
}
Because I was a little bit lazy, I didn't implement " At this time, your control always shows the current data. This includes the order of the items (sorted by anything). In this case, the How to Get and Set the Current ItemWhen you use a details-form, you are editing a specific row. The information on which row is current is provided by the If the data source changes its position, you get the private void dataManager_PositionChanged(object sender, EventArgs e)
{
if (this.Items.Count > dataManager.Position)
{
this.Items[dataManager.Position].Selected = true;
this.EnsureVisible(dataManager.Position);
}
}
This method only selects the current index of the items, and scrolls to it, if needed. If you want to set the position when your user clicks at one item in your control, you first have to implement private void ListViewDataBinding_SelectedIndexChanged(object sender, EventArgs e)
{
try
{
if (this.SelectedIndices.Count > 0 &&
dataManager.Position != this.SelectedIndices[0])
dataManager.Position = this.SelectedIndices[0];
}
catch
{
// Could appear, if you change the position
// while someone edits a row with invalid data.
}
}
Note that it is impossible to set the position to -1 because data binding does not allow "nothing selected". Also, if you try to set the position out of the list-range, it won't do it. You will not get an exception. The At this point, your control shows the current data and position. If you only want to show these points, you are done! If you also want to change data, continue reading. Changing the Data From Your Control to the DataSourceIf you want to write changes to the data in your control to the data source, you can use the After using
protected override void OnAfterLabelEdit(LabelEditEventArgs e)
{
base.OnAfterLabelEdit(e);
if (e.Label == null)
{
// If you press ESC while editing.
e.CancelEdit = true;
return;
}
if (dataManager.List.Count > e.Item)
{
object row = dataManager.List[e.Item];
// In a ListView you are only able to edit the first Column.
PropertyDescriptor col =
dataManager.GetItemProperties().Find(this.Columns[0].Text, false);
try
{
if (row != null &&
col != null)
col.SetValue(row, e.Label);
dataManager.EndCurrentEdit();
}
catch(Exception ex)
{
// If you try to enter strings in number-columns,
// too long strings or something
// else wich is not allowed by the DataSource.
MessageBox.Show("Edit failed:\r\n" + ex.Message,
"Edit failed", MessageBoxButtons.OK,
MessageBoxIcon.Error);
dataManager.CancelCurrentEdit();
e.CancelEdit = true;
}
}
}
That's all! Now your control is data bound like a SummaryYou saw that it is not difficult to implement complex data binding. Thanks to the If you want to use this code in your own control, you will need History
| ||||||||||||||||||||