Initialization of Named Slot Contents

We’ve noticed a bit of an oddity with the way that Named Slots have User Widgets contained within initialized.

Since named slot contents are spliced into the widget tree after child instanced User Widgets are initialized in UUserWidget::DuplicateAndInitializeFromWidgetTree, if their widget trees contain User Widgets then they won’t be initialized until something external triggers initialization. For us, this is often adding the high level widget to a panel or the viewport, which triggers UUserWidget::RebuildWidget() and what appears to be a failsafe initialization call (given the comment present there).

We’ve experimented with running the same instanced subobject initialization for named slots within the SetContentWidgetForNamedSlot lambda, copying the pattern from from earlier in the function, and simply operating on the named slot instancing graph:

`auto SetContentWidgetForNamedSlot = [this,&ConflictingWidgetNames](FName NamedSlotName, UWidget* TemplateSlotContent)
{
FObjectInstancingGraph NamedSlotInstancingGraph;
NamedSlotInstancingGraph.AddNewObject(WidgetTree, TemplateSlotContent->GetTypedOuter());

FName TemplateSlotContentName = TemplateSlotContent->GetFName();
if (ConflictingWidgetNames == nullptr || !ConflictingWidgetNames->Contains(TemplateSlotContentName))
{
UWidget* Content = NewObject(WidgetTree, TemplateSlotContent->GetClass(), TemplateSlotContentName, RF_Transactional, TemplateSlotContent, false, &NamedSlotInstancingGraph);
Content->SetFlags(RF_Transient | RF_DuplicateTransient);

//$$ - BEGIN: Modified here to perform the same logic earlier in UUserWidget::DuplicateAndInitializeFromWidgetTree that the owning widget does
NamedSlotInstancingGraph.ForEachObjectInstance([this](UObject* Instanced) {
#if WITH_EDITOR
if (UWidget* InstancedWidget = Cast(Instanced))
{
InstancedWidget->SetDesignerFlags(GetDesignerFlags());
}
#endif

if (UUserWidget* InstancedSubUserWidget = Cast(Instanced))
{
InstancedSubUserWidget->SetPlayerContext(GetPlayerContext());
InstancedSubUserWidget->Initialize();
}
});
//$$ - END
SetContentForSlot(NamedSlotName, Content);
}
else
{
SetContentForSlot(NamedSlotName, nullptr);
}
};`

Does this seem like a reasonable approach to ensure named slot content gets initialized? Or is named slot initialization intentionally left out of this flow for some reason?

Steps to Reproduce

  1. Create a widget that makes use of a named slot
    1. WBP_NamedSlotParent in the repro project
  2. Create a “component” widget that will be used within the named slot
    1. WBP_SlotContents in the repro project
  3. Create a child of the named slot widget, and set the “component” widget as the content
    1. WBP_NamedSlotChild in the repro project
  4. Construct a child named slot widget and call some function on it to do “work”. Observe that calling into the “component” widget as part of this will result in all widgets within its hierarchy being unavailable as its initialization logic has not yet been run.
    1. In the repro project, HUD_Test constructs the widget and then simply calls a text setting functions.
    2. In our project this pattern is often used with per-player widgets, such as loadout, teammate display, etc. And the “work” is often initializing the widget display for a given player.

Hi Zachary,

Thank you for reaching out. This seems like a fair fix to the problem. Alternatively, you could run the required setup/work during the Construct call of WBP_NamedSlotChild to make sure that the namedslots are properly initialized.

Thanks,

Zahra