How to handle object collections with custom data factories and data accessors?

Grids for WPF Forum

Posted 5 years ago by Ernie Bahr - Founder and Chief Software Architect, ECMG, LLC.
Version: 13.1.0058
Avatar

I have a custom control that incorporates a tree view and a property grid for editing a set of operations. Each operation may have one or more parameters. For the parameters that only hold a single value the property grid is working perfectly. The trouble I am having is properly displaying the collection values. In the example below the extensions are a collection parameter.

 Process Editor

 

The behavior I am trying to achieve in the property grid is similar to List1 in the Collection Read-Only Items quick start as shown below.

Collection Sample

Here is the xaml for the property grid.

<propgrid:PropertyGrid Name="pgOperationCustom" Grid.Column="2"
                       CollectionDisplayMode="EditableInline"
                       SummaryCanAutoSize="True" 
                       SummaryHeight="Auto" >     
  <propgrid:PropertyGrid.DataFactory>
    <CtsControls:OperableTypeReflectionFactory />
  </propgrid:PropertyGrid.DataFactory>
</propgrid:PropertyGrid>

 Here is an xml representation of the operation object shown in the first screen shot above.

<ContentExtensionDecisionOperation Name="ContentExtensionDecision" Description="" LogResult="True" Scope="Source">
	<Parameters>
		<SingletonEnumParameter Name="VersionScope" DisplayName="Version Scope" Type="ecmEnum" EnumType="VersionScopeEnum" Description="Specifies which version(s) to use for the evaluation." Value="AllVersions" />
		<SingletonLongParameter Name="ContentElementIndex" DisplayName="Content Element Index" Type="ecmLong" Description="" Value="0" />
		<SingletonEnumParameter Name="Mode" DisplayName="Mode" Type="ecmEnum" EnumType="ModeEnum" Description="Specifies whether the mode is valid or invalid." Value="Valid" />
		<SingletonEnumParameter Name="EvaluationAction" DisplayName="Evaluation Action" Type="ecmEnum" EnumType="EvaluationActionEnum" Description="Specifies the behavior following the evaluation." Value="Default" />
		<MultiValueStringParameter Name="Extensions" DisplayName="Extensions" Type="ecmString" Description="The list of extensions to test for." Cardinality="ecmMultiValued">
			<Values>
				<Value>pdf</Value>
				<Value>docx</Value>
			</Values>
		</MultiValueStringParameter>
	</Parameters>
	<RunBeforeBegin />
	<RunAfterComplete />
	<RunOnFailure />
	<TrueOperations />
	<FalseOperations />
</ContentExtensionDecisionOperation>

 Here is the code for the custom data factory

Public Class OperableTypeReflectionFactory
  Inherits TypeReflectionFactory
 
#Region "Public Methods"
 
  Protected Overrides Function GetProperties(ByVal value As Object, ByVal options As DataFactoryOptions) As IList(Of IPropertyDataAccessor)
    Try
      Dim lobjDataAccessors As IList(Of IPropertyDataAccessor)
      Dim lobjOperable As IOperable = TryCast(value, IOperable)

      If lobjOperable IsNot Nothing Then
        ' Create a list of property data accessor results
        lobjDataAccessors = New List(Of IPropertyDataAccessor)()
        If TypeOf lobjOperable Is IProcess Then
          'Return MyBase.GetProperties(value, options)
          For Each lobjOperation In CType(lobjOperable, IProcess).Operations
            lobjDataAccessors.Add(New OperableDataAccessor(lobjOperation, "Operations"))
          Next
 
        ElseIf TypeOf value Is IOperations Then
          Dim lobjOperationAccessor As IPropertyDataAccessor = Nothing
          Dim lobjDummyProperty As IProperty = PropertyFactory.Create(PropertyType.ecmObject, lobjOperable.Name, String.Empty)
          For Each lobjOperation As IOperable In CType(lobjOperable, Cts.Operations.Operations)
            lobjOperationAccessor = New CtsPropertyDataAccessor(lobjDummyProperty, "Operations")
            lobjDataAccessors.Add(lobjOperationAccessor)
          Next
 
        ElseIf TypeOf lobjOperable Is IOperation Then
 
          ' Add the Scope
          Dim lobjOperation As IOperation = lobjOperable
          lobjDataAccessors.Add(New CustomPropertyDataAccessor(Of IOperation, String) _
            (lobjOperation, Function(o) lobjOperation.ScopeString, "Scope", "Misc", _
             Nothing, GetType(OperationScope), Helper.EnumerationDictionary(GetType(OperationScope)).Keys, _
             "Specifies whether the operation executes against the source or destination document."))
        End If
 
        ' Add the parameters
        For Each lobjParameter As Parameter In lobjOperable.Parameters
          Dim lobjDataAccessor As New CtsPropertyDataAccessor(lobjParameter, "Parameters")
          lobjDataAccessors.Add(lobjDataAccessor)
        Next
 
      Else
   
        ' Fall back to using the base method's results for nested objects
          lobjDataAccessors = MyBase.GetProperties(value, options)
  
      End If

      Return lobjDataAccessors

    Catch ex As Exception
      ApplicationLogging.LogException(ex, Reflection.MethodBase.GetCurrentMethod)
      ' Re-throw the exception to the caller
      Throw
    End Try
  End Function
 
#End Region
 
End Class

 Here is the code for the data accessor I am using for the parameters.

Public Class CtsPropertyDataAccessor
  Inherits CachedPropertyDataAccessorBase
 
#Region "Class Variables"
 
  Private mobjCtsProperty As IProperty
  Private mstrCategory As String = String.Empty
  Private mintValueIndex As Integer = -1
 
  Dim mobjConverter As ExpandableCollectionConverter = Nothing
 
#End Region
 
#Region "Public Properties"
 
  Public ReadOnly Property CtsProperty As IProperty
    Get
      Try
        Return mobjCtsProperty
      Catch ex As Exception
        ApplicationLogging.LogException(ex, Reflection.MethodBase.GetCurrentMethod)
        ' Re-throw the exception to the caller
        Throw
      End Try
    End Get
  End Property
 
#End Region
 
#Region "Constructors"
 
  Public Sub New(ByVal lpCtsProperty As IProperty, ByVal lpCategory As String)
    Me.New(lpCtsProperty, lpCategory, -1)
  End Sub
 
  Public Sub New(ByVal lpCtsProperty As IProperty, ByVal lpCategory As String, lpValueIndex As Integer)
    Try
      If lpCtsProperty Is Nothing Then
        Throw New ArgumentNullException("lpCtsProperty")
      End If
 
      mobjCtsProperty = lpCtsProperty
      mintValueIndex = lpValueIndex
 
      If Not String.IsNullOrEmpty(lpCategory) Then
        mstrCategory = lpCategory
      End If
 
    Catch ex As Exception
      ApplicationLogging.LogException(ex, Reflection.MethodBase.GetCurrentMethod)
      ' Re-throw the exception to the caller
      Throw
    End Try
  End Sub
 
#End Region
 
#Region "CachedPropertyDataAccessorBase Overrides"
 
  Public Overrides Sub Reset()
    ' No-op
  End Sub
 
  Public Overrides ReadOnly Property CanAddChild() As Boolean
    Get
      Try
        If CtsProperty IsNot Nothing AndAlso CtsProperty.Cardinality = Cardinality.ecmMultiValued Then
          Return True
        Else
          Return False
        End If
      Catch ex As Exception
        ApplicationLogging.LogException(ex, Reflection.MethodBase.GetCurrentMethod)
        ' Re-throw the exception to the caller
        Throw
      End Try
    End Get
  End Property
 
  Public Overrides ReadOnly Property CanReset As Boolean
    Get
      Return False
    End Get
  End Property
 
  Public Overrides ReadOnly Property Category As String
    Get
      Try
        If String.IsNullOrEmpty(mstrCategory) Then
          Return MyBase.Category
        Else
          Return mstrCategory
        End If
      Catch ex As Exception
        ApplicationLogging.LogException(ex, Reflection.MethodBase.GetCurrentMethod)
        ' Re-throw the exception to the caller
        Throw
      End Try
    End Get
  End Property
 
  Public Overrides ReadOnly Property Description As String
    Get
      Try
        If Not String.IsNullOrEmpty(Me.CtsProperty.Description) Then
          Return Me.CtsProperty.Description
        Else
          Return String.Format("No description available for property {0}.", Me.CtsProperty.Name)
        End If
      Catch ex As Exception
        ApplicationLogging.LogException(ex, Reflection.MethodBase.GetCurrentMethod)
        ' Re-throw the exception to the caller
        Throw
      End Try
    End Get
  End Property
 
  Public Overrides ReadOnly Property DisplayName As String
    Get
      Try
        If mintValueIndex = -1 Then
          Return Me.CtsProperty.DisplayName
        Else
          Return String.Empty
        End If
 
      Catch ex As Exception
        ApplicationLogging.LogException(ex, Reflection.MethodBase.GetCurrentMethod)
        ' Re-throw the exception to the caller
        Throw
      End Try
    End Get
  End Property
 
  Public Overrides ReadOnly Property Target As Object
    Get
      Try
        Return Me.CtsProperty
      Catch ex As Exception
        ApplicationLogging.LogException(ex, Reflection.MethodBase.GetCurrentMethod)
        ' Re-throw the exception to the caller
        Throw
      End Try
    End Get
  End Property
 
  Protected Overrides ReadOnly Property IsLimitedToStandardValuesInternal As Boolean
    Get
      Try
        Return Me.CtsProperty.HasStandardValues
      Catch ex As Exception
        ApplicationLogging.LogException(ex, Reflection.MethodBase.GetCurrentMethod)
        ' Re-throw the exception to the caller
        Throw
      End Try
    End Get
  End Property
 
  Protected Overrides ReadOnly Property IsMergableInternal As Boolean
    Get
      Return False
    End Get
  End Property
 
  Protected Overrides ReadOnly Property IsModifiedInternal As Boolean
    Get
      Return True
    End Get
  End Property
 
  Protected Overrides ReadOnly Property IsReadOnlyInternal As Boolean
    Get
      Return False
    End Get
  End Property
 
  Protected Overrides ReadOnly Property StandardValuesInternal() As IEnumerable
    Get
      Try
        If TypeOf Me.CtsProperty Is SingletonEnumParameter Then
          Return Helper.CreateDisplayNames(Me.CtsProperty.StandardValues)
        Else
          Return Me.CtsProperty.StandardValues
        End If
 
      Catch ex As Exception
        ApplicationLogging.LogException(ex, Reflection.MethodBase.GetCurrentMethod)
        ' Re-throw the exception to the caller
        Throw
      End Try
    End Get
  End Property
 
  Protected Overrides ReadOnly Property ConverterInternal As System.ComponentModel.TypeConverter
    Get
      Try
        If TypeOf Me.CtsProperty Is IProperty Then
          Dim lobjProperty As IProperty = Me.CtsProperty
          If lobjProperty.Cardinality = Cardinality.ecmMultiValued Then
            If mobjConverter Is Nothing Then
              mobjConverter = New ExpandableCollectionConverter()
            End If
            Return mobjConverter
          Else
            Return Nothing
          End If
        End If
      Catch ex As Exception
        ApplicationLogging.LogException(ex, Reflection.MethodBase.GetCurrentMethod)
        ' Re-throw the exception to the caller
        Throw
      End Try
    End Get
  End Property
 
  Protected Overrides Property ValueInternal As Object
    Get
      Try
        If TypeOf Me.CtsProperty Is SingletonBooleanParameter Then
          Return Boolean.Parse(Me.CtsProperty.Value)
        ElseIf TypeOf Me.CtsProperty Is SingletonEnumParameter Then
          Return Helper.CreateDisplayName(Me.CtsProperty.Value.ToString)
        ElseIf TypeOf Me.CtsProperty Is IProperty Then
          Dim lobjProperty As IProperty = Me.CtsProperty
          If lobjProperty.Cardinality = Cardinality.ecmMultiValued Then
            'Return Nothing
            If Me.CtsProperty.HasValue Then
              Return Me.CtsProperty.Values
            Else
              Return Nothing
            End If
          Else
            Return Me.CtsProperty.Value
          End If
        Else
          Return Nothing
        End If
      Catch ex As Exception
        ApplicationLogging.LogException(ex, Reflection.MethodBase.GetCurrentMethod)
        ' Re-throw the exception to the caller
        Throw
      End Try
    End Get
    Set(value As Object)
      Try
        If TypeOf Me.CtsProperty Is SingletonBooleanParameter Then
          Me.CtsProperty.Value = Boolean.Parse(value.ToString)
        ElseIf TypeOf Me.CtsProperty Is SingletonEnumParameter Then
          Me.CtsProperty.Value = Helper.NameFromDisplayName(value.ToString)
        Else
          Me.CtsProperty.Value = value
        End If
      Catch ex As Exception
        ApplicationLogging.LogException(ex, Reflection.MethodBase.GetCurrentMethod)
        ' Re-throw the exception to the caller
        Throw
      End Try
    End Set
  End Property
 
  Protected Overrides ReadOnly Property ValueNameInternal As String
    Get
      Try
        If mintValueIndex = -1 Then
          Return Me.CtsProperty.Name
        Else
          Return String.Empty
        End If
 
      Catch ex As Exception
        ApplicationLogging.LogException(ex, Reflection.MethodBase.GetCurrentMethod)
        ' Re-throw the exception to the caller
        Throw
      End Try
    End Get
  End Property
 
  Protected Overrides ReadOnly Property ValuesInternal As Object()
    Get
      Try
        If Me.CtsProperty.Cardinality = Cardinality.ecmMultiValued Then
          If Me.CtsProperty.Values IsNot Nothing Then
            If Me.CtsProperty.Type = PropertyType.ecmString Then
              Dim lobjValues As New List(Of Object)
              If mobjConverter Is Nothing Then
                mobjConverter = New ExpandableCollectionConverter()
              End If
              For Each lobjValue As Object In Me.CtsProperty.Values
                Dim lobjItem As Object = mobjConverter.CreateItem(Me)
                mobjConverter.AddItem(Me, lobjItem)
                lobjValues.Add(lobjValue.ToString)
              Next
              Return lobjValues.ToArray
              
            Else
              Dim lobjValues As New List(Of Object)
              lobjValues.AddRange(Me.CtsProperty.Values)
              Return lobjValues.ToArray
            End If
          Else
            Return MyBase.ValuesInternal
          End If
        Else
          Return MyBase.ValuesInternal
        End If
      Catch ex As Exception
        ApplicationLogging.LogException(ex, Reflection.MethodBase.GetCurrentMethod)
        ' Re-throw the exception to the caller
        Throw
      End Try
    End Get
  End Property
 
  Protected Overrides ReadOnly Property ValueTypeInternal As Type
    Get
      Try
        Select Case Me.CtsProperty.Cardinality
          Case Cardinality.ecmSingleValued
            Select Case Me.CtsProperty.Type
              Case PropertyType.ecmString, PropertyType.ecmHtml, PropertyType.ecmUri, _
                PropertyType.ecmXml, PropertyType.ecmUndefined
                Return GetType(String)
 
              Case PropertyType.ecmBoolean
                Return GetType(Boolean)
 
              Case PropertyType.ecmDate
                Return GetType(DateTime)
 
              Case PropertyType.ecmDouble
                Return GetType(Double)
 
              Case PropertyType.ecmEnum
                Return GetType([Enum])
 
              Case PropertyType.ecmGuid
                If TypeOf Me.CtsProperty.Value Is Guid Then
                  Return GetType(Guid)
                Else
                  Return GetType(String)
                End If
 
              Case PropertyType.ecmObject, PropertyType.ecmBinary
                Return GetType(Object)
 
              Case Else
                Return GetType(String)
 
            End Select
          Case Cardinality.ecmMultiValued
            Select Case Me.CtsProperty.Type
              Case PropertyType.ecmString, PropertyType.ecmHtml, PropertyType.ecmUri, _
                PropertyType.ecmXml, PropertyType.ecmUndefined
                Return GetType(List(Of String))
 
              Case Else
                Return GetType(List(Of Object))
 
            End Select
 
        End Select
 
      Catch ex As Exception
        ApplicationLogging.LogException(ex, Reflection.MethodBase.GetCurrentMethod)
        ' Re-throw the exception to the caller
        Throw
      End Try
    End Get
  End Property
 
#End Region
 
End Class

 What am I missing?

Comments (4)

Posted 5 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Ernie,

Generally collections are created when the DataFactory calls its CreateCollectionPropertyDataAccessor in the factory's GetProperties method.  For that particular collection property, you probably want to use our built-in functionality or at least make another custom DataAccessor that inherits our CollectionPropertyDescriptorDataAccessor class.

Check into those things.  If you need further help afterwards, please email our support address with a new simple sample project so that we have something we can debug with.  Reference this thread and rename the .zip file extension of what you send so it doesn't get spam blocked.  Thanks!


Actipro Software Support

Posted 5 years ago by Ernie Bahr - Founder and Chief Software Architect, ECMG, LLC.
Avatar

Thanks for the tip.  I will look into the CollectionPropertyDescriptorDataAccessor class and see where that takes me.

Posted 5 years ago by Ernie Bahr - Founder and Chief Software Architect, ECMG, LLC.
Avatar

I was looking at the sample application and there is no case in there that inherits from your CollectionPropertyDescriptorDataAccessor class.  Do you have any samples I can reference for this?  It is not clear to me which methods need to be overriden.

Posted 5 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Ernie,

Sorry we don't have any samples related to that class.  But I believe as long as you are calling the base GetProperties method for the property that is a collection, it should end up using CollectionPropertyDescriptorDataAccessor.


Actipro Software Support

The latest build of this product (v2018.1 build 0675) was released 11 days ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.