Property categorization

When creating content, properties are assigned categories that are shown together in a property grid:

Property grid

There are multiple sources for a property's category and several ways to override them or control their nesting through presentation rules.

Direct property categories

A property may or may not have a category and there are several sources it may be taken from (by priority):

  1. A property override specification with property category overrides.
  2. An ECPropertyCategory assigned to the ECProperty in ECSchema.
  3. If a property is related with a RelationshipMeaning.SameInstance relationship and that related class contains the property.

If a property has no direct category, it is displayed as if it belonged to the parent category. See the next section for more details on category nesting.

Category nesting

Categories group properties so they're easier for users to find and manage. However, it's often necessary to group already categorized properties into some other group, for example 'favorites'. This is where nested categorization comes into play.

Generally, since a property grid requests properties for the current selection, we put all properties into the default Selected Item(s) category:

Selected Item(s) category

The default category can be overriden using DefaultPropertyCategoryOverride rule.

If properties have direct categories of their own, their categories appear nested under this top-level category, e.g.:

Nested categories

Additionally, when presentation rules specify related properties, it is possible to create new top-level categories. See the Controlling Category Nesting of Related Properties for more details.

Finally, category nesting can be achieved using property category overrides - see the Controlling Category Nesting Through Property Category Overrides section for more details.

RelatedPropertiesSpecification has an attribute relationshipMeaning which tells whether the related properties should be displayed as if they belong to the selected instance or a separate (related) instance. In the latter case, a new nested property category group is created based on the related class. Example:

ECSchema:

<ECEntityClass typeName="A">
  <ECProperty propertyName="PropA" typeName="string" />
</ECEntityClass>
<ECEntityClass typeName="B">
  <ECProperty propertyName="PropB" typeName="string" />
</ECEntityClass>
<ECEntityClass typeName="C">
  <ECProperty propertyName="PropC" typeName="string" />
</ECEntityClass>
<ECRelationshipClass typeName="A_To_B" strength="referencing" strengthDirection="forward">
  <Source multiplicity="(0..1)" roleLabel="A to B" polymorphic="True">
    <Class class="A" />
  </Source>
  <Target multiplicity="(0..1)" roleLabel="B from A" polymorphic="True">
    <Class class="B" />
  </Target>
</ECRelationshipClass>
<ECRelationshipClass typeName="A_To_C" strength="referencing" strengthDirection="forward">
  <Source multiplicity="(0..1)" roleLabel="A to C" polymorphic="True">
    <Class class="A" />
  </Source>
  <Target multiplicity="(0..1)" roleLabel="C from A" polymorphic="True">
    <Class class="C" />
  </Target>
</ECRelationshipClass>

ContentModifier used in Presentation Rules for ECClass A:

{
  "ruleType": "ContentModifier",
  "class": { "schemaName": "MySchema", "className": "A" },
  "propertyCategories": [{
    "id": "my_custom_category",
    "label": "My Custom Category"
  }],
  "relatedProperties": [{
    "propertiesSource": {
      "relationship": {"schemaName": "MySchema", "className": "A_To_B"},
      "direction": "Forward"
    },
    "relationshipMeaning": "SameInstance",
    "properties": [{
      "name": "PropB",
      "categoryId": "my_custom_category"
    }]
  }, {
    "propertiesSource": {
      "relationship": {"schemaName": "MySchema", "className": "A_To_C"},
      "direction": "Forward"
    },
    "relationshipMeaning": "RelatedInstance",
    "properties": [{
      "name": "PropC",
      "categoryId": "my_custom_category"
    }]
  }]
}

Result:

'Relationship meaning on related properties'

In the above example both PropB and PropC have a custom property category assigned through presentation rules and they're both specified as related properties for class A. Because PropB is set to be related with a SameInstance relationship, it will be shown as nested under the other properties of class A. PropC, on the other hand, is nested under another top-level category created from class C, because it's related with a RelatedInstance relationship.

Here is a more complex example with nested related properties:

ECSchema:

<ECEntityClass typeName="A">
  <ECProperty propertyName="PropA" typeName="string" />
</ECEntityClass>
<ECEntityClass typeName="B">
  <ECProperty propertyName="PropB" typeName="string" />
</ECEntityClass>
<ECEntityClass typeName="C">
  <ECProperty propertyName="PropC" typeName="string" />
</ECEntityClass>
<ECEntityClass typeName="D">
  <ECProperty propertyName="PropD" typeName="string" />
</ECEntityClass>
<ECEntityClass typeName="E">
  <ECProperty propertyName="PropE" typeName="string" />
</ECEntityClass>
<ECRelationshipClass typeName="A_To_B" strength="referencing" strengthDirection="forward">
  <Source multiplicity="(0..1)" roleLabel="A to B" polymorphic="True">
    <Class class="A" />
  </Source>
  <Target multiplicity="(0..1)" roleLabel="B from A" polymorphic="True">
    <Class class="B" />
  </Target>
</ECRelationshipClass>
<ECRelationshipClass typeName="B_To_C" strength="referencing" strengthDirection="forward">
  <Source multiplicity="(0..1)" roleLabel="B to C" polymorphic="True">
    <Class class="B" />
  </Source>
  <Target multiplicity="(0..1)" roleLabel="C from B" polymorphic="True">
    <Class class="C" />
  </Target>
</ECRelationshipClass>
<ECRelationshipClass typeName="B_To_D" strength="referencing" strengthDirection="forward">
  <Source multiplicity="(0..1)" roleLabel="B to D" polymorphic="True">
    <Class class="B" />
  </Source>
  <Target multiplicity="(0..1)" roleLabel="D from B" polymorphic="True">
    <Class class="D" />
  </Target>
</ECRelationshipClass>
<ECRelationshipClass typeName="D_To_E" strength="referencing" strengthDirection="forward">
  <Source multiplicity="(0..1)" roleLabel="D to E" polymorphic="True">
    <Class class="D" />
  </Source>
  <Target multiplicity="(0..1)" roleLabel="E from D" polymorphic="True">
    <Class class="E" />
  </Target>
</ECRelationshipClass>

ContentModifier used in Presentation Rules for ECClass A:

{
  "ruleType": "ContentModifier",
  "class": { "schemaName": "MySchema", "className": "A" },
  "relatedProperties": [{
    "propertiesSource": {
      "relationship": {"schemaName": "MySchema", "className": "A_To_B"},
      "direction": "Forward"
    },
    "relationshipMeaning": "RelatedInstance",
    "nestedRelatedProperties": [{
      "propertiesSource": {
        "relationship": {"schemaName": "MySchema", "className": "B_To_C"},
        "direction": "Forward"
      },
      "relationshipMeaning": "RelatedInstance"
    }, {
      "propertiesSource": {
        "relationship": {"schemaName": "MySchema", "className": "B_To_D"},
        "direction": "Forward"
      },
      "relationshipMeaning": "RelatedInstance",
      "nestedRelatedProperties": [{
        "propertiesSource": {
          "relationship": {"schemaName": "MySchema", "className": "D_To_E"},
          "direction": "Forward"
        },
        "relationshipMeaning": "RelatedInstance"
      }]
    }]
  }]
}

Result:

'Relationship meaning on nested related properties'

Controlling category nesting through property category overrides

PropertyCategorySpecification has an attribute parentId which can point to another custom category to become nested inside it:

ECSchema:

<ECEntityClass typeName="A">
  <ECProperty propertyName="PropA" typeName="string" />
  <ECProperty propertyName="PropB" typeName="string" />
</ECEntityClass>

ContentModifier used in Presentation Rules for ECClass A:

{
  "ruleType": "ContentModifier",
  "class": { "schemaName": "MySchema", "className": "A" },
  "propertyCategories": [{
    "id": "parent_category",
    "label": "Parent Category"
  }, {
    "id": "child_category",
    "parentId": "parent_category",
    "label": "Child Category"
  }],
  "propertyOverrides": [{
    "name": "PropA",
    "categoryId": "parent_category"
  }, {
    "name": "PropB",
    "categoryId": "child_category"
  }]
}

Result:

'Custom nested properties'

Occasionally there may be a need to move the categories' hierarchy to a specific place, especially when creating it for nested related properties. A typical example would be a requirement to merge nested related properties into the parent related properties' list. By default, all nested related properties get their own category:

'Default related properties nesting'

{
  "ruleType": "ContentModifier",
  "class": { "schemaName": "MySchema", "className": "A" },
  "relatedProperties": [{
    "propertiesSource": {
      "relationship": {"schemaName": "MySchema", "className": "AB"},
      "direction": "Forward",
      "targetClass": {"schemaName": "MySchema", "className": "B"}
    },
    "nestedRelatedProperties": [{
      "propertiesSource": {
        "relationship": {"schemaName": "MySchema", "className": "BC"},
        "direction": "Forward",
        "targetClass": {"schemaName": "MySchema", "className": "C"}
      },
      "properties": [{
        "name": "*",
        "categoryId": { "type": "DefaultParent" }
      }]
    }]
  }]
}

Applying the above rule moves C properties into the parent category:

'Related properties nesting customized with default parent category identifier'

Or, we can also move the properties into the root category by using Root category identifier:

'Related properties nesting customized with root category identifier'

Creating nested class categories

IdCategoryIdentifier has an attribute createClassCategory which specifies whether an additional class category should be created under a category pointed to by IdCategoryIdentifier.categoryId.

'Related properties nesting customized with id category identifier and createClassCategory attribute'

{
  "ruleType": "ContentModifier",
  "class": { "schemaName": "MySchema", "className": "A" },
  "propertyCategories": [{
    "id": "custom-category",
    "label": "Custom category"
  }],
  "relatedProperties": [{
    "propertiesSource": {
      "relationship": {"schemaName": "MySchema", "className": "AB"},
      "direction": "Forward",
      "targetClass": {"schemaName": "MySchema", "className": "B"}
    },
    "properties": [{
      "name": "*",
      "categoryId": { "type": "Id", "categoryId": "custom-category", "createClassCategory": true }
    }]
  }]
}

Below is a table of how category identifiers work in different situations:

Direct Property Direct Categorized Property Related Property (SameInstance meaning) Related Property (RelatedInstance meaning) Nested Related Property (RelatedInstance & SameInstance) Nested Related Property (RelatedInstance & RelatedInstance) Nested Related Categorized Property (RelatedInstance & SameInstance)
No category override Selected Item(s) Selected Item(s) -> ECPropertyCategory Selected Item(s) -> Related class Related class First level related class -> Second level related class First level related class -> Second level related class First level related class -> ECPropertyCategory
DefaultParent Selected Item(s) Selected Item(s) Selected Item(s) Related class First level related class First level related class -> Second level related class First level related class
Root Selected Item(s) Selected Item(s) Selected Item(s) Related class First level related class First level related class First level related class
Id Custom Custom Custom Custom Custom Custom Custom

Last Updated: 18 August, 2023