Updates to DesiredFocusWidget for a widget are not reflected for existing instances of it inside other widgets

Updates made to the DesiredFocusWidget property of a widget do not seem to propagate to existing instances of that widget inside other widgets. See the repro steps for details on the issue.

I put a breakpoint inside UUserWidget::NativeOnFocusReceived and noticed that when the issue was happening, DesiredFocusWidget had a ‘None’ WidgetName and an empty WidgetPtr. After reinstancing the widget in the hierarchy, DesiredFocusWidget resolves to the appropriate widget at runtime as expected.

As it stands relying on DesiredFocusWidget for focus forwarding feels risky and error-prone, and probably affects whether the internal button is focused within UCommonButtonBase::Initialize.

Steps to Reproduce

  1. Create 3x user widgets (WidgetA and WidgetB), and set them all as focusable
  2. Add an instance of WidgetC to WidgetB, and add an instance of WidgetB to WidgetA
  3. Save and compile all blueprints
  4. Update DesiredFocusWidget of WidgetA to point to WidgetB
  5. Update DesiredFocusWidget of WidgetB to point to WidgetC
  6. Save and compile all blueprints
  7. Add an instance of WidgetA to the HUD, run PIE, and set focus to WidgetA
  8. Observe that focus is not forwarded to WidgetC, it stops at WidgetA
  9. Stop PIE
  10. Recreate the instance of WidgetB within WidgetA (ie. delete it and then add it again)
  11. Update DesiredFocusWidget of WidgetA to point to WidgetB
  12. Save and compile all blueprints
  13. Add an instance of WidgetA to the HUD, run PIE, and set focus to WidgetA
  14. Observe that focus now gets forwarded to WidgetC

Hi Joel,

Thank you for the report and repro steps. I was able to repro this issue here on UE 5.5 up to the latest source version. One thing I noticed is that restarting the editor, or better yet simply reloading the parent widget asset (right click on content browser -- asset actions -- reload) seems to fix the problem. For example, if you change “DesiredFocusWidget” for WidgetB, just reload WidgetA’s asset. Let me know if that works for you!

I’ll now do a preliminary investigation on the problem to see if there is an easy source-code fix, and then file an internal bug report for the engine devs to take a look. I should get back to you soon with a tracking number for the bug report.

Best regards,

Vior

Hi Joel,

After an initial investigation of this issue, I have come up with a source-code change that might serve as a temporary workaround for you. Look for function UUserWidget::PostEditChangeProperty() in file [Engine\Source\Runtime\UMG\Private\UserWidget.cpp]. You’ll find the following lines:

// We cannot use the Widget Ptr as we need to find the widget with the same name in the CDO
UserWidgetCDO->SetDesiredFocusWidget(DesiredFocusWidget.GetFName());

Please try changing that code to the following:

// We cannot use the Widget Ptr as we need to find the widget with the same name in the CDO
UserWidgetCDO->SetDesiredFocusWidget(DesiredFocusWidget.GetFName());
 
// Manually propagate the change to all instances of the CDO, including those inside the 
// WidgetTree of other widgets. This assumes that all instances of the widget have this
// property in sync with the CDO at all times (no overrides).
TArray<UObject*> ArchetypeInstances;
UserWidgetCDO->GetArchetypeInstances(ArchetypeInstances);
for (UObject* ArchetypeInstance : ArchetypeInstances)
{
	((UUserWidget*)ArchetypeInstance)->SetDesiredFocusWidget(DesiredFocusWidget.GetFName());
}

The change above is somewhat hacky, but it seems to work in my tests so far. If you try it, please let me know how it goes.

In reality, this propagation should be done in function PropagateDefaultPropertyChange(), which is called by FWidgetBlueprintEditor::MigrateFromChain() in file [Engine\Source\Editor\UMGEditor\Private\WidgetBlueprintEditor.cpp]. However, the current code is not working because it assumes that the CDO has not been changed yet (see comments inside FWidgetBlueprintEditor::MigrateFromChain()). The special handling done in UUserWidget::PostEditChangeProperty(), which intends to cope with the fact that each WidgetTree contains a different instance of the same widget, ends up violating this assumption and de-syncing the property between the CDO and its instances.

In any case, I have filed an internal bug report so that the engine devs that own this system can take a close look. Here’s the tracking number for it: UE-353920. The link should become accessible once the devs mark it as public.

Let me know if you need any further assistance.

Best regards,

Vitor

Hey Vitor! Restarting the editor and reloading the asset didn’t appear to resolve the issue for me, but maybe I was trying it with a weird setup.

That said, the fix you provided seems to work! I’ll have that integrated right away, thank you :slight_smile: