Bind a geodatabase table to a .NET control
[C#]
TableWrapper.cs
using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using System.ComponentModel; using ESRI.ArcGIS.Geodatabase; namespace ArcDataBinding { /// <summary> /// This class provides a wrapper for an ITable that allows it to be bound to /// a .NET control. /// </summary> /// <remarks> /// This class inherits from <see cref="BindingList"/> to provide a default /// implementation of a list of objects that can be bound to a .NET control. /// For the purposes of this sample, it is easier to use BindingList and add /// IRows to it than it is to implement all the interfaces required for a /// bindable list. A more correct implementation would allow direct access to /// the wrapped ITable rather than simply adding all of its rows to a list. /// The class also implements <see cref="ITypedList"/> to allow a control to /// query it for any properties required to correctly display the data in a /// control. Normally properties are determined by using reflection. We want /// the individual fields in the given ITable to look like properties of an /// IRow. As this is not the case, we need to create a collection of 'fake' /// properties with one for each field in the ITable. This is contained in the /// fakePropertiesList member and is used by the ITypedList implementation. /// </remarks> [Guid("5a239147-b06a-49e5-aa1c-e47f81adc10e")] [ClassInterface(ClassInterfaceType.None)] [ProgId("ArcDataBinding.TableWrapper")] public class TableWrapper: BindingList<IRow>, ITypedList { #region Private Members /// <summary> /// Reference to the table we are wrapping /// </summary> private ITable wrappedTable; /// <summary> /// This is a list of <see cref="PropertyDescriptor"/> instances with each one /// representing one field of the wrapped ITable. /// </summary> private List<PropertyDescriptor> fakePropertiesList = new List<PropertyDescriptor>(); /// <summary> /// Used to start and stop editing when adding/updating/deleting rows /// </summary> private IWorkspaceEdit wkspcEdit; #endregion Private Members #region Construction/Destruction /// <summary> /// This constructor stores a reference to the wrapped ITable and uses it to /// generate a list of properties before adding the ITable's data to the binding /// list. /// </summary> /// <param name="tableToWrap">ITable that we wish to bind to .NET controls</param> public TableWrapper(ITable tableToWrap) { wrappedTable = tableToWrap; GenerateFakeProperties(); AddData(); wkspcEdit = ((IDataset)wrappedTable).Workspace as IWorkspaceEdit; AllowNew = true; AllowRemove = true; } #endregion Construction/Destruction #region ITypedList Members /// <summary> /// Returns the <see cref="T:System.ComponentModel.PropertyDescriptorCollection"></see> /// that represents the properties on each item used to bind data. /// </summary> /// <param name="listAccessors">An array of <see cref="T:System.ComponentModel.PropertyDescriptor"></see> /// objects to find in the collection as bindable. This can be null.</param> /// <returns> /// The <see cref="T:System.ComponentModel.PropertyDescriptorCollection"></see> /// that represents the properties on each item used to bind data. /// </returns> public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) { PropertyDescriptorCollection propCollection = null; if (null == listAccessors) { // Return all properties propCollection = new PropertyDescriptorCollection(fakePropertiesList.ToArray()); } else { // Return the requested properties by checking each item in listAccessors // to make sure it exists in our property collection. List<PropertyDescriptor> tempList = new List<PropertyDescriptor>(); foreach (PropertyDescriptor curPropDesc in listAccessors) { if (fakePropertiesList.Contains(curPropDesc)) { tempList.Add(curPropDesc); } } propCollection = new PropertyDescriptorCollection(tempList.ToArray()); } return propCollection; } /// <summary> /// Returns the name of the list. /// </summary> /// <param name="listAccessors">An array of <see cref="T:System.ComponentModel.PropertyDescriptor"></see> /// objects, the list name for which is returned. This can be null.</param> /// <returns>The name of the list.</returns> public string GetListName(PropertyDescriptor[] listAccessors) { return ((IDataset)wrappedTable).Name; } #endregion ITypedList Members public bool UseCVDomains { set { foreach (FieldPropertyDescriptor curPropDesc in fakePropertiesList) { if (curPropDesc.HasCVDomain) { // Field has a coded value domain so turn the usage of this on or off // as requested curPropDesc.UseCVDomain = value; } } } } #region Protected Overrides /// <summary> /// Raises the <see cref="E:System.ComponentModel.BindingList`1.AddingNew"></see> event. /// </summary> /// <remarks> /// This override sets the NewObject property of the event arguments parameter /// to be a new IRow. /// </remarks> /// <param name="e">An <see cref="T:System.ComponentModel.AddingNewEventArgs"></see> /// that contains the event data.</param> protected override void OnAddingNew(AddingNewEventArgs e) { // Check that we can still add rows, this property could have been changed if (AllowNew) { // Need to create a new IRow IRow newRow = wrappedTable.CreateRow(); e.NewObject = newRow; // Loop through fields and set default values for (int fieldCount = 0; fieldCount < newRow.Fields.FieldCount; fieldCount++) { IField curField = newRow.Fields.get_Field(fieldCount); if (curField.Editable) { newRow.set_Value(fieldCount, curField.DefaultValue); } } // Save default values bool weStartedEditing = StartEditOp(); newRow.Store(); StopEditOp(weStartedEditing); base.OnAddingNew(e); } } /// <summary> /// Removes the item at the specified index. /// </summary> /// <remarks> /// This override calls the Delete method of the IRow that is being removed /// </remarks> /// <param name="index">The zero-based index of the item to remove.</param> protected override void RemoveItem(int index) { // Check that we can still delete rows, this property could have been changed if (AllowRemove) { // Get the corresponding IRow IRow itemToRemove = Items[index]; bool weStartedEditing = StartEditOp(); // Delete the row itemToRemove.Delete(); StopEditOp(weStartedEditing); base.RemoveItem(index); } } #endregion Protected Overrides #region Private Methods /// <summary> /// Generates 'fake' properties. /// </summary> /// <remarks> /// We need this method to create a list of properties for each field in the /// ITable as an IRow does not have a property for each field. /// </remarks> private void GenerateFakeProperties() { // Loop through fields in wrapped table for (int fieldCount = 0; fieldCount < wrappedTable.Fields.FieldCount; fieldCount++) { // Create a new property descriptor to represent the field FieldPropertyDescriptor newPropertyDesc = new FieldPropertyDescriptor( wrappedTable, wrappedTable.Fields.get_Field(fieldCount).Name, fieldCount); fakePropertiesList.Add(newPropertyDesc); } } /// <summary> /// Adds the data to the binding list. /// </summary> /// <remarks> /// Note that this is a pretty inefficient way of accessing the data to be /// bound to a control. If we implemented each of the interfaces required for /// a bindable list rather than using BindingList, we could write code that /// only reads rows from the ITable as they need to be displayed rather than /// reading all of them. /// </remarks> private void AddData() { // Get a search cursor that returns all rows. Note we do not want to recycle // the returned IRow otherwise all rows in the bound control will be identical // to the last row read... ICursor cur = wrappedTable.Search(null, false); IRow curRow = cur.NextRow(); while (null != curRow) { Add(curRow); curRow = cur.NextRow(); } } /// <summary> /// Starts an edit operation. /// </summary> /// <remarks> /// This method is used to start an edit operation before changing any data. /// It checks to see if we are in an edit session or not and starts a new /// one if appropriate. If we do start an edit session, the method will return /// true to indicate that we started an edit session and should therefore also /// stop it. /// </remarks> /// <returns>True if we started an edit session, false if we didn't</returns> private bool StartEditOp() { bool retVal = false; // Check to see if we're editing if (!wkspcEdit.IsBeingEdited()) { // Not being edited so start here wkspcEdit.StartEditing(false); retVal = true; } // Start operation wkspcEdit.StartEditOperation(); return retVal; } /// <summary> /// Stops the edit operation. /// </summary> /// <remarks> /// This method stops an edit operation started with a call to /// <see cref="StartEditOp"/>. If the weStartedEditing parameter is true, this /// method will also end the edit session. /// </remarks> /// <param name="weStartedEditing">if set to <c>true</c> [we started editing].</param> private void StopEditOp(bool weStartedEditing) { // Stop edit operation wkspcEdit.StopEditOperation(); if (weStartedEditing) { // We started the edit session so stop it here wkspcEdit.StopEditing(true); } } #endregion Private Methods } }
[Visual Basic .NET]
TableWrapper.vb
Imports System Imports System.Collections.Generic Imports System.Text Imports System.Runtime.InteropServices Imports System.ComponentModel Imports ESRI.ArcGIS.Geodatabase Namespace ArcDataBinding '<summary> 'This class provides a wrapper for an ITable that allows it to be bound to 'a .NET control. '</summary> '<remarks> 'This class inherits from <see cref="BindingList"/> to provide a default 'implementation of a list of objects that can be bound to a .NET control. 'For the purposes of this sample, it is easier to use BindingList and add 'IRows to it than it is to implement all the interfaces required for a 'bindable list. A more correct implementation would allow direct access to 'the wrapped ITable rather than simply adding all of its rows to a list. 'The class also implements <see cref="ITypedList"/> to allow a control to 'query it for any properties required to correctly display the data in a 'control. Normally properties are determined by using reflection. We want 'the individual fields in the given ITable to look like properties of an 'IRow. As this is not the case, we need to create a collection of 'fake' 'properties with one for each field in the ITable. This is contained in the 'fakePropertiesList member and is used by the ITypedList implementation. '</remarks> <Guid("5a239147-b06a-49e5-aa1c-e47f81adc10e")> _ <ClassInterface(ClassInterfaceType.None)> _ <ProgId("ArcDataBinding.TableWrapper")> _ Public Class TableWrapper Inherits BindingList(Of IRow) Implements ITypedList #Region "Private Members" '<summary> 'Reference to the table we are wrapping '</summary> Private wrappedTable As ITable '<summary> 'This is a list of <see cref="PropertyDescriptor"/> instances with each one 'representing one field of the wrapped ITable. '</summary> Private fakePropertiesList As List(Of PropertyDescriptor) = New List(Of PropertyDescriptor) '<summary> 'Used to start and stop editing when adding/updating/deleting rows '</summary> Private wkspcEdit As IWorkspaceEdit #End Region #Region "Construction/Destruction" '<summary> 'This constructor stores a reference to the wrapped ITable and uses it to 'generate a list of properties before adding the ITable's data to the binding 'list. '</summary> '<param name="tableToWrap">ITable that we wish to bind to .NET controls</param> Public Sub New(ByVal tableToWrap As ITable) wrappedTable = tableToWrap GenerateFakeProperties() AddData() wkspcEdit = DirectCast((DirectCast(wrappedTable, IDataset)).Workspace, IWorkspaceEdit) AllowNew = True AllowRemove = True End Sub #End Region #Region "ITypedList Members" '<summary> 'Returns the <see cref="T:System.ComponentModel.PropertyDescriptorCollection"></see> 'that represents the properties on each item used to bind data. '</summary> '<param name="listAccessors">An array of <see cref="T:System.ComponentModel.PropertyDescriptor"></see> 'objects to find in the collection as bindable. This can be null.</param> '<returns> 'The <see cref="T:System.ComponentModel.PropertyDescriptorCollection"></see> 'that represents the properties on each item used to bind data. '</returns> Public Function GetItemProperties(ByVal listAccessors As PropertyDescriptor()) As PropertyDescriptorCollection Implements ITypedList.GetItemProperties Dim propCollection As PropertyDescriptorCollection = Nothing If (Nothing Is listAccessors) Then ' Return all properties propCollection = New PropertyDescriptorCollection(fakePropertiesList.ToArray()) Else ' Return the requested properties by checking each item in listAccessors ' to make sure it exists in our property collection. Dim tempList As List(Of PropertyDescriptor) = New List(Of PropertyDescriptor) Dim curPropDesc As PropertyDescriptor For Each curPropDesc In listAccessors If (fakePropertiesList.Contains(curPropDesc)) Then tempList.Add(curPropDesc) End If Next propCollection = New PropertyDescriptorCollection(tempList.ToArray()) End If Return propCollection End Function '<summary> 'Returns the name of the list. '</summary> '<param name="listAccessors">An array of <see cref="T:System.ComponentModel.PropertyDescriptor"></see> 'objects, the list name for which is returned. This can be null.</param> '<returns>The name of the list.</returns> Public Function GetListName(ByVal listAccessors As PropertyDescriptor()) As String Implements ITypedList.GetListName GetListName = (DirectCast(wrappedTable, IDataset)).Name End Function #End Region Public WriteOnly Property UseCVDomains() As Boolean Set(ByVal Value As Boolean) Dim curPropDesc As FieldPropertyDescriptor For Each curPropDesc In fakePropertiesList If (curPropDesc.HasCVDomain) Then ' Field has a coded value domain so turn the usage of this on or off ' as requested curPropDesc.SetUseCVDomain = Value End If Next End Set End Property #Region "Protected Overrides" '<summary> 'Raises the <see cref="E:System.ComponentModel.BindingList`1.AddingNew"></see> event. '</summary> '<remarks> 'This override sets the NewObject property of the event arguments parameter 'to be a new IRow. '</remarks> '<param name="e">An <see cref="T:System.ComponentModel.AddingNewEventArgs"></see> 'that contains the event data.</param> Protected Overrides Sub OnAddingNew(ByVal e As AddingNewEventArgs) ' Check that we can still add rows, this property could have been changed If (AllowNew) Then ' Need to create a new IRow Dim NewRow As IRow = wrappedTable.CreateRow() e.NewObject = NewRow ' Loop through fields and set default values Dim fieldCount As Integer For fieldCount = 0 To NewRow.Fields.FieldCount - 1 Step fieldCount + 1 Dim curField As IField = NewRow.Fields.Field(fieldCount) If (curField.Editable) Then NewRow.Value(fieldCount) = curField.DefaultValue End If Next ' Save default values Dim weStartedEditing As Boolean = StartEditOp() NewRow.Store() StopEditOp(weStartedEditing) MyBase.OnAddingNew(e) End If End Sub '<summary> 'Removes the item at the specified index. '</summary> '<remarks> 'This override calls the Delete method of the IRow that is being removed '</remarks> '<param name="index">The zero-based index of the item to remove.</param> Protected Overrides Sub RemoveItem(ByVal index As Integer) ' Check that we can still delete rows, this property could have been changed If (AllowRemove) Then ' Get the corresponding IRow Dim itemToRemove As IRow = Items(index) Dim weStartedEditing As Boolean = StartEditOp() ' Delete the row itemToRemove.Delete() StopEditOp(weStartedEditing) MyBase.RemoveItem(index) End If End Sub #End Region #Region "Private Methods" '<summary> 'Generates 'fake' properties. '</summary> '<remarks> 'We need this method to create a list of properties for each field in the 'ITable as an IRow does not have a property for each field. '</remarks> Private Sub GenerateFakeProperties() ' Loop through fields in wrapped table Dim fieldCount As Integer For fieldCount = 0 To wrappedTable.Fields.FieldCount - 1 Step fieldCount + 1 ' Create a new property descriptor to represent the field Dim fieldName As String = wrappedTable.Fields.Field(fieldCount).Name.ToString() Dim newPropertyDesc As FieldPropertyDescriptor = New FieldPropertyDescriptor( _ wrappedTable, fieldName, fieldCount) fakePropertiesList.Add(newPropertyDesc) Next End Sub '<summary> 'Adds the data to the binding list. '</summary> '<remarks> 'Note that this is a pretty inefficient way of accessing the data to be 'bound to a control. If we implemented each of the interfaces required for 'a bindable list rather than using BindingList, we could write code that 'only reads rows from the ITable as they need to be displayed rather than 'reading all of them. '</remarks> Private Sub AddData() ' Get a search cursor that returns all rows. Note we do not want to recycle ' the returned IRow otherwise all rows in the bound control will be identical ' to the last row read... Dim cur As ICursor = wrappedTable.Search(Nothing, False) Dim curRow As IRow = cur.NextRow() While Not (Nothing Is curRow) Add(curRow) curRow = cur.NextRow() End While End Sub '<summary> 'Starts an edit operation. '</summary> '<remarks> 'This method is used to start an edit operation before changing any data. 'It checks to see if we are in an edit session or not and starts a new 'one if appropriate. If we do start an edit session, the method will return 'true to indicate that we started an edit session and should therefore also 'stop it. '</remarks> '<returns>True if we started an edit session, false if we didn't</returns> Private Function StartEditOp() As Boolean StartEditOp = False ' Check to see if we're editing If (Not wkspcEdit.IsBeingEdited()) Then ' Not being edited so start here wkspcEdit.StartEditing(False) StartEditOp = True End If ' Start operation wkspcEdit.StartEditOperation() End Function '<summary> 'Stops the edit operation. '</summary> '<remarks> 'This method stops an edit operation started with a call to '<see cref="StartEditOp"/>. If the weStartedEditing parameter is true, this 'method will also end the edit session. '</remarks> '<param name="weStartedEditing">if set to <c>true</c> [we started editing].</param> Private Sub StopEditOp(ByVal weStartedEditing As Boolean) ' Stop edit operation wkspcEdit.StopEditOperation() If (weStartedEditing) Then ' We started the edit session so stop it here wkspcEdit.StopEditing(True) End If End Sub #End Region End Class End Namespace