Skip to content

Default layout fallback

When a user opens the drilldown for the first time, there is no saved layout to render. Without a fallback, the resulting view would be empty — no visible columns, no grouping, no useful information. The drilldown detects this case and applies a sensible default layout.

How the empty state is detected

The check uses the existing GetVisibleOrGroupedCount() helper on CLayoutInterface:

If PrimaryLayoutInterface.GetVisibleOrGroupedCount() = 0 Then
    ApplyDefaultDrilldownLayout(PrimaryLayoutInterface)
End If

A return value of zero means the saved layout has no entries with m_nVisibleIndex >= 0 and no entries with m_nGroupIndex >= 0 — that is, nothing the renderer would surface to the user. This is the right test for "no usable layout" because:

  • It treats a layout file that exists but has been emptied as still empty.
  • It does not produce false positives for layouts that have visible columns and a deliberate grouping.

When the check happens

The check runs before DisplayDrillDown is called. This is intentional. DisplayDrillDown triggers LayoutInterface.Execute(), which in turn calls RequestElements against the optimized table to populate the cache for everything that the layout needs. By configuring the layout first, the optimized table fetches the correct columns on its first pass and the gridview renders the layout without further intervention.

sequenceDiagram
    participant U as User
    participant D as OnDrillDown
    participant L as PrimaryLayoutInterface
    participant F as FDrilldown
    participant G as GridView

    U->>D: double-click row
    D->>L: build criteria
    D->>L: GetVisibleOrGroupedCount()
    alt Count = 0
        D->>L: ApplyDefaultDrilldownLayout
    end
    D->>F: DisplayDrillDown(L)
    F->>L: AttachToLayoutInterface
    F->>L: Execute()
    L->>L: RequestElements (cache columns)
    L->>G: bind data + apply layout
    D->>G: AddHandler CustomDrawCell
    D->>G: AutoExpandAllGroups = True
    D->>G: RefreshData()

What the default layout looks like

The default groups by item_no and shows seventeen columns covering the standard MRP detail set. The grouping column is not also added as a regular column — it appears only in the group header rows ("Item #: BCABASSY", "Item #: BIKEMP", and so on).

The visible columns, in left-to-right order, are:

# Field name Caption
1 imloc_byr_plnr Byr Plnr
2 item_desc_1 Item Desc 1
3 item_loc Loc
4 pur_or_mfg Item Pur or MFG
5 mrp_ord_type MRP - Ord Type
6 mrp_trx_dt MRP - Demand/Replenish Date
7 qty_on_hand_il O/H Qty (Loc)
8 mrp_trx_qty MRP - Qty Demand/Replenish
9 mrp_new_qty_il MRP - New Qty On-Hand (Loc)
10 mrp_ord_no MRP Ord #
11 safety_stk Safety Stk
12 po_lead_tm PO Lead Tm
13 cus_no Cust #
14 oe_ord_no OE Ord #
15 par_item_no Par Item #
16 org_ord_no Org Ord #
17 po_ord_no PO Ord #

The grouped column (not shown in the data area) is item_no.

How the layout is applied

The implementation works directly on the layout model, mutating Layout.m_LayoutElements. It does not touch the gridview directly — the gridview is rendered from the layout, so configuring the layout is sufficient.

Private Sub ApplyDefaultDrilldownLayout(ByVal layoutInterface As PulseDevTools.DynamicQuery.CLayoutInterface)
    If layoutInterface Is Nothing OrElse layoutInterface.Layout Is Nothing Then Exit Sub

    'Group by item_no (group header only - item_no is not also added as a visible column).
    AddLayoutElement(layoutInterface, "item_no", -1, 0)

    Dim szVisibleOrder As String() = New String() {
        "imloc_byr_plnr", "item_desc_1", "item_loc", "pur_or_mfg",
        "mrp_ord_type", "mrp_trx_dt", "qty_on_hand_il", "mrp_trx_qty",
        "mrp_new_qty_il", "mrp_ord_no", "safety_stk", "po_lead_tm",
        "cus_no", "oe_ord_no", "par_item_no", "org_ord_no", "po_ord_no"
    }

    For i As Integer = 0 To szVisibleOrder.GetUpperBound(0)
        AddLayoutElement(layoutInterface, szVisibleOrder(i), i, -1)
    Next
End Sub

AddLayoutElement is a small helper that resolves a field name to a matrix element ID and either updates an existing layout entry for that ID or appends a new one.

The duplicate-entry pitfall

Layout.m_LayoutElements may already contain a layout entry for a field even when nothing is visible or grouped — typically with Visible = -1, Group = -1. If the helper blindly appends a second entry for the same element ID, two distinct lookup paths inside CLayoutInterface see different entries:

  • ElementLayoutByID (used by CachedElements and AllElements) returns the first matching entry it finds.
  • GroupedElements and VisibleElements walk the full m_LayoutElements list and pick up every entry that has the relevant index set.

If those two paths disagree about which CElementLayout is "the" entry for a given element, the rendering can break in subtle ways. The most visible symptom is grouping not showing up, even though the layout list has a m_nGroupIndex >= 0 entry.

The helper avoids this by checking for an existing entry and updating it in place:

Private Sub AddLayoutElement(ByVal layoutInterface As PulseDevTools.DynamicQuery.CLayoutInterface,
                             ByVal fieldName As String,
                             ByVal visibleIndex As Integer,
                             ByVal groupIndex As Integer)
    Dim core As DynamicQuery.CQueryMatrix.CSD_2.CElementCore = layoutInterface.Layout.GetElementCoreByFieldName(fieldName)
    If core Is Nothing Then Exit Sub

    Dim szID As String = core.ID

    Dim existing As PulseDevTools.DynamicQuery.CQueryView.CSD_1.CLayoutSet.CElementLayout = Nothing
    For Each cur As PulseDevTools.DynamicQuery.CQueryView.CSD_1.CLayoutSet.CElementLayout In layoutInterface.Layout.m_LayoutElements
        If cur.m_szID = szID Then
            existing = cur
            Exit For
        End If
    Next

    If existing Is Nothing Then
        existing = New PulseDevTools.DynamicQuery.CQueryView.CSD_1.CLayoutSet.CElementLayout
        existing.m_szID = szID
        layoutInterface.Layout.m_LayoutElements.Add(existing)
    End If

    existing.m_nVisibleIndex = visibleIndex
    existing.m_nGroupIndex = groupIndex
    If groupIndex >= 0 Then
        existing.m_nSortOrder = 1
    End If
End Sub

Reusable pattern

AddLayoutElement is generic — it does not depend on anything specific to MRP Orders Causing Shortages. It would be a good candidate to lift onto a shared base class if a second module ever needs a similar programmatic default layout.

Group expansion behavior

After the drilldown is displayed, one runtime behavior is set on the gridview:

tempgv.OptionsBehavior.AutoExpandAllGroups = True

This is a DevExpress gridview behavior, not part of the saved layout. With it on, any group rows that materialize when data changes (initial load, sort, filter, refresh) auto-expand. The user can still collapse a group manually, but a refresh will not collapse groups that were already expanded.

Why not put it in the layout?

The serializable CLayoutSet does not have a member for "auto-expand groups." It is a runtime-only gridview option. Setting it on tempgv after DisplayDrillDown is the right place for it.