Bind a geodatabase table to a .NET control
ArcDataBinding\FieldPropertyDescriptor.vb
' Copyright 2012 ESRI
' 
' All rights reserved under the copyright laws of the United States
' and applicable international laws, treaties, and conventions.
' 
' You may freely redistribute and use this sample code, with or
' without modification, provided you include the original copyright
' notice and use restrictions.
' 
' See the use restrictions.
' 

Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.ComponentModel
Imports ESRI.ArcGIS.Geodatabase
Imports ESRI.ArcGIS.GeomeTry

Namespace ArcDataBinding
    '<summary>
    'This class provides a PropertyDescriptor for a single field of an IRow
    '</summary>
    '<remarks>
    'This class can be used by an ITypedList implementation to provide a property
    'description for a single field in an ITable.
    '</remarks>
    Friend Class FieldPropertyDescriptor
        Inherits PropertyDescriptor
#Region "Private Members"
        '<summary>
        'Store the index of the IField that this property descriptor describes
        '</summary>
        Private wrappedFieldIndex As Integer

        '<summary>
        'Store the .NET type of the value stored in the IField this property
        'represents
        '</summary>
        Private netType As Type

        '<summary>
        'This is used to store the actual .NET type of a field that uses a CV
        'domain. It retains the type allowing as to restore it when the UseCVDomain
        'property is false;
        '</summary>
        Private actualType As Type

        '<summary>
        'Store the esri type of the value stored in the IField this property
        'represents
        '</summary>
        Private esriType As esriFieldType

        '<summary>
        'Indicates whether this field is editable or not.
        '</summary>
        '<remarks>
        'This will determined by looking at the Editable property of the IField
        'and the type of the field. We currently don't support the editing of
        'blob or geometry fields.
        '</remarks>
        Dim isEditable As Boolean = True

        '<summary>
        'Used to start and stop editing when adding/updating/deleting rows
        '</summary>
        Private wkspcEdit As IWorkspaceEdit

        '<summary>
        'The coded value domain for the field this instance represents, if any
        '</summary>
        Private cvDomain As ICodedValueDomain

        '<summary>
        'This will be true if we are currently using the string values for the
        'coded value domain and false if we are using the numeric values.
        '</summary>
        Private useCVDomain As Boolean

        '<summary>
        'This type converter is used when the field this instance represents has
        'a coded value domain and we are displaying the actual domain values
        '</summary>
        Private actualValueConverter As TypeConverter

        '<summary>
        'This type converter is used when the field this instance represents has
        'a coded value domain and we are displaying the names of the domain values
        '</summary>
        Private cvDomainValDescriptionConverter As TypeConverter
#End Region

#Region "Construction/Destruction"
        '<summary>
        'Initializes a new instance of the <see cref="FieldPropertyDescriptor"/> class.
        '</summary>
        '<param name="wrappedTable">The wrapped table.</param>
        '<param name="fieldName">Name of the field within wrappedTable.</param>
        '<param name="fieldIndex">Index of the field within wrappedTable.</param>
        Public Sub New(ByVal wrappedTable As ITable, ByVal fieldName As String, ByVal fieldIndex As Integer)
            : MyBase.New(fieldName, Nothing)

            wrappedFieldIndex = fieldIndex

            'Get the field this property will represent. We will use it to
            'get the field type and determine whether it can be edited or not. In
            'this case, editable means the field's editable property is true and it
            'is not a blob, geometry or raster field.
            Dim wrappedField As IField = DirectCast(wrappedTable.Fields.Field(fieldIndex), IField)
            esriType = wrappedField.Type
            isEditable = wrappedField.Editable AndAlso _
              (esriType <> esriFieldType.esriFieldTypeBlob) AndAlso _
              (esriType <> esriFieldType.esriFieldTypeRaster) AndAlso _
              (esriType <> esriFieldType.esriFieldTypeGeometry)
            actualType = EsriFieldTypeToSystemType(wrappedField)
            netType = actualType
            wkspcEdit = DirectCast((DirectCast(wrappedTable, IDataset)).Workspace, IWorkspaceEdit)
        End Sub

#End Region

        '<summary>
        'Gets a value indicating whether the field represented by this property 
        'has a CV domain.
        '</summary>
        '<value>
        '  <c>true</c> if this instance has a CV domain; otherwise, <c>false</c>.
        '</value>
        Public ReadOnly Property HasCVDomain() As Boolean
            Get
                HasCVDomain = Not (Nothing Is cvDomain)
            End Get
        End Property

        '<summary>
        'Sets a value indicating whether [use CV domain].
        '</summary>
        '<value><c>true</c> if [use CV domain]; otherwise, <c>false</c>.</value>
        Public WriteOnly Property SetUseCVDomain() As Boolean
            Set(ByVal Value As Boolean)
                useCVDomain = Value
                If (Value) Then
                    ' We want the property type for this field to be string
                    netType = GetType(String)
                Else
                    ' Restore the original type
                    netType = actualType
                End If
            End Set
        End Property

#Region "Public Overrides"
        '<summary>
        'Gets the type converter for this property.
        '</summary>
        '<remarks>
        'We need to override this property as the base implementation sets the
        'converter once and reuses it as required. We can't do this if the field
        'this instance represents has a coded value domain and we change from
        'using the value to using the name or vice versa. The reason for this is
        'that if we are displaying the domain name, we need a string converter and
        'if we are displaying the domain value, we will need one of the numeric
        'converters.
        '</remarks>
        '<returns>A <see cref="T:System.ComponentModel.TypeConverter"></see> 
        'that is used to convert the <see cref="T:System.Type"></see> of this 
        'property.</returns>
        '<PermissionSet><IPermission class="System.Security.Permissions.SecurityPermission, 
        'mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" 
        'version="1" Flags="UnmanagedCode"/></PermissionSet>
        Public Overrides ReadOnly Property Converter() As TypeConverter
            Get
                Dim retVal As TypeConverter = Nothing

                If Not (Nothing Is cvDomain) Then

                    If (useCVDomain) Then
                        If (Nothing Is cvDomainValDescriptionConverter) Then
                            ' We want a string converter
                            cvDomainValDescriptionConverter = TypeDescriptor.GetConverter(GetType(String))
                        End If

                        retVal = cvDomainValDescriptionConverter
                    Else
                        If (Nothing Is actualValueConverter) Then
                            ' We want a converter for the type of this field's actual value
                            actualValueConverter = TypeDescriptor.GetConverter(actualType)
                        End If

                        retVal = actualValueConverter
                    End If

                Else

                    ' This field doesn't have a coded value domain, the base implementation
                    ' works fine.
                    retVal = MyBase.Converter
                End If

                Converter = retVal
            End Get
        End Property

        '<summary>
        'Returns whether resetting an object changes its value.
        '</summary>
        '<param name="component">The component to test for reset capability.
        'This will be an IRow</param>
        '<returns>
        'true if resetting the component changes its value; otherwise, false.
        '</returns>
        Public Overrides Function CanResetValue(ByVal component As Object) As Boolean
            CanResetValue = False
        End Function

        '<summary>
        'Gets the type of the component this property is bound to.
        '</summary>
        '<value></value>
        '<returns>A <see cref="T:System.Type"></see> that represents the type of 
        'component this property is bound to. When the 
        '<see cref="M:System.ComponentModel.PropertyDescriptor.GetValue(System.Object)"></see> 
        'or <see cref="M:System.ComponentModel.PropertyDescriptor.SetValue(System.Object,System.Object)"></see> 
        'methods are invoked, the object specified might be an instance of this type.</returns>
        Public Overrides ReadOnly Property ComponentType() As Type
            Get
                ComponentType = GetType(IRow)
            End Get
        End Property

        '<summary>
        'Gets the current value of the property on a component.
        '</summary>
        '<param name="component">The component (an IRow) with the property for 
        'which to retrieve the value.</param>
        '<remarks>
        'This will return the field value for all fields apart from geometry, raster and Blobs.
        'These fields will return the string equivalent of the geometry type.
        '</remarks>
        '<returns>
        'The value of a property for a given component. This will be the value of
        'the field this class instance represents in the IRow passed in the component
        'parameter.
        '</returns>
        Public Overrides Function GetValue(ByVal component As Object) As Object
            Dim retVal As Object = Nothing

            Dim givenRow As IRow = DirectCast(component, IRow)
            Try
                ' Get value
                Dim value As Object = givenRow.Value(wrappedFieldIndex)

                If (Not (Nothing Is cvDomain) AndAlso useCVDomain) Then
                    value = cvDomain.Name(Convert.ToInt32(value))
                End If


                Select Case esriType
                    Case esriFieldType.esriFieldTypeBlob
                        retVal = "Blob"
                        Exit Select
                    Case esriFieldType.esriFieldTypeGeometry

                        retVal = GetGeomeTryTypeAsString(value)
                        Exit Select
                    Case esriFieldType.esriFieldTypeRaster

                        retVal = "Raster"
                        Exit Select
                    Case Else

                        retVal = value
                        Exit Select
                End Select


            Catch e As Exception
                System.Diagnostics.Debug.WriteLine(e.Message)
            End Try

            GetValue = retVal
        End Function

        '<summary>
        'Gets a value indicating whether this property is read-only or not.
        '</summary>
        '<value></value>
        '<returns>true if the property is read-only; otherwise, false.</returns>
        Public Overrides ReadOnly Property IsReadOnly() As Boolean
            Get
                IsReadOnly = Not isEditable
            End Get
        End Property

        '<summary>
        'Gets the type of the property.
        '</summary>
        '<value></value>
        '<returns>A <see cref="T:System.Type"></see> that represents the type 
        'of the property.</returns>
        Public Overrides ReadOnly Property PropertyType() As Type
            Get
                PropertyType = netType
            End Get
        End Property

        '<summary>
        'Resets the value for this property of the component to the default value.
        '</summary>
        '<param name="component">The component (an IRow) with the property value 
        'that is to be reset to the default value.</param>
        Public Overrides Sub ResetValue(ByVal component As Object)
        End Sub

        '<summary>
        'Sets the value of the component to a different value.
        '</summary>
        '<remarks>
        'If the field this instance represents does not have a coded value domain,
        'this method simply sets the given value and stores the row within an edit
        'operation. If the field does have a coded value domain, the method first
        'needs to check that the given value is valid. If we are displaying the 
        'coded values, the value passed to this method will be a string and we will
        'need to see if it is one of the names in the cv domain. If we are not
        'displaying the coded values, we will still need to check that the given
        'value is within the domain. If the value is not within the domain, an
        'error will be displayed and the method will return.
        'Note that the string comparison is case sensitive.
        '</remarks>
        '<param name="component">The component (an IRow) with the property value 
        'that is to be set.</param>
        '<param name="value">The new value.</param>
        Public Overrides Sub SetValue(ByVal component As Object, ByVal value As Object)
            Dim givenRow As IRow = DirectCast(component, IRow)

            If Not (Nothing Is cvDomain) Then
                ' This field has a coded value domain
                If (Not useCVDomain) Then
                    ' Check value is valid member of the domain
                    If (Not (DirectCast(cvDomain, IDomain)).MemberOf(value)) Then _
                      System.Windows.Forms.MessageBox.Show(String.Format( _
                        "Value {0} is not valid for coded value domain {1}", value.ToString(), (DirectCast(cvDomain, IDomain)).Name))
                    Return
                Else
                    ' We need to convert the string value to one of the cv domain values
                    ' Loop through all the values until we, hopefully, find a match
                    Dim foundMatch As Boolean = False
                    Dim valueCount As Integer
                    For valueCount = 0 To cvDomain.CodeCount - 1 Step valueCount + 1
                        If (value.ToString() = cvDomain.Name(valueCount)) Then
                            foundMatch = True
                            value = valueCount
                        End If
                        Exit For
                    Next


                    ' Did we find a match?
                    If (Not foundMatch) Then
                        System.Windows.Forms.MessageBox.Show(String.Format( _
                        "Value {0} is not valid for coded value domain {1}", value.ToString(), (DirectCast(cvDomain, IDomain)).Name))
                        Return
                    End If
                End If
            End If

            givenRow.Value(wrappedFieldIndex) = value

            ' Start editing if we aren't already editing
            Dim weStartedEditing As Boolean = False
            If (Not wkspcEdit.IsBeingEdited()) Then
                wkspcEdit.StartEditing(False)
                weStartedEditing = True
                ' Store change in an edit operation
                wkspcEdit.StartEditOperation()
                givenRow.Store()
                wkspcEdit.StopEditOperation()
            End If
            ' Stop editing if we started here
            If (weStartedEditing) Then
                wkspcEdit.StopEditing(True)
            End If

        End Sub

        '<summary>
        'When overridden in a derived class, determines a value indicating whether 
        'the value of this property needs to be persisted.
        '</summary>
        '<param name="component">The component (an IRow) with the property to be examined for 
        'persistence.</param>
        '<returns>
        'true if the property should be persisted; otherwise, false.
        '</returns>
        Public Overrides Function ShouldSerializeValue(ByVal component As Object) As Boolean
            ShouldSerializeValue = False
        End Function

#End Region

#Region "Private Methods"
        '<summary>
        'Converts the specified ESRI field type to a .NET type.
        '</summary>
        '<param name="esriType">The ESRI field type to be converted.</param>
        '<returns>The appropriate .NET type.</returns>
        Function EsriFieldTypeToSystemType(ByVal field As IField) As Type
            Dim esriType As esriFieldType = field.Type

            ' Does this field have a domain?
            cvDomain = TryCast(field.Domain, ICodedValueDomain)
            If (Not (Nothing Is cvDomain) AndAlso useCVDomain) Then
                EsriFieldTypeToSystemType = GetType(String)
                Exit Function
            End If

            Try
                Select Case esriType
                    Case esriFieldType.esriFieldTypeBlob
                        'beyond scope of sample to deal with blob fields
                        EsriFieldTypeToSystemType = GetType(String)
                    Case esriFieldType.esriFieldTypeDate
                        EsriFieldTypeToSystemType = GetType(DateTime)
                    Case esriFieldType.esriFieldTypeDouble
                        EsriFieldTypeToSystemType = GetType(Double)
                    Case esriFieldType.esriFieldTypeGeometry
                        EsriFieldTypeToSystemType = GetType(String)
                    Case esriFieldType.esriFieldTypeGlobalID
                        EsriFieldTypeToSystemType = GetType(String)
                    Case esriFieldType.esriFieldTypeGUID
                        EsriFieldTypeToSystemType = GetType(Guid)
                    Case esriFieldType.esriFieldTypeInteger
                        EsriFieldTypeToSystemType = GetType(Int32)
                    Case esriFieldType.esriFieldTypeOID
                        EsriFieldTypeToSystemType = GetType(Int32)
                    Case esriFieldType.esriFieldTypeRaster
                        'beyond scope of sample to correctly display rasters
                        EsriFieldTypeToSystemType = GetType(String)
                    Case esriFieldType.esriFieldTypeSingle
                        EsriFieldTypeToSystemType = GetType(Single)
                    Case esriFieldType.esriFieldTypeSmallInteger
                        EsriFieldTypeToSystemType = GetType(Int16)
                    Case esriFieldType.esriFieldTypeString
                        EsriFieldTypeToSystemType = GetType(String)
                    Case Else
                        EsriFieldTypeToSystemType = GetType(String)
                End Select
            Catch ex As Exception
                System.Diagnostics.Debug.WriteLine(ex.Message)
                EsriFieldTypeToSystemType = GetType(String)
            End Try
        End Function

        '<summary>
        'Gets the geometry type as string.
        '</summary>
        '<param name="value">The value.</param>
        '<returns>The string equivalent of the geometry type</returns>
        Private Function GetGeomeTryTypeAsString(ByVal value As Object) As String
            Dim retVal As String = ""
            Dim geomeTry As IGeometry = TryCast(value, IGeometry)
            If Not (geomeTry Is Nothing) Then
                retVal = geomeTry.GeometryType.ToString()
            End If
            GetGeomeTryTypeAsString = retVal
        End Function
#End Region
    End Class
End Namespace