This issue has been reproduced in UE 5.3 to 5.7 and source versions. A repro project is available. Below are the findings from an initial investigation of the problem.
Normally, when a UserWidget property is edited in UMG, the following call chain will execute:
FPropertyNode::NotifyPostChange()
SWidgetDetailsView::NotifyPostChange()
FWidgetBlueprintEditor::MigrateFromChain()
PropagateDefaultPropertyChange()
Function MigrateFromChain() contains the following comment:
"(...) we need to copy the values back to the CDO - but we ALSO need to (...) copy the values to any live instances of the widget with matching previous values so that defaults stay in-sync with the CDO."
Later on, right before calling PropagateDefaultPropertyChange(), there is another comment:
"(...) we've got the actual unmodified CDO still - but we're going to propagate the proxy's value that users modified instead of the CDO's value, then MigratePropertyValue will update the CDO's value."
Function PropagateDefaultPropertyChange() then copies the modified property to all Archetype Instances of the widget's CDO, including any existing instances inside the WidgetTree of other widgets' UWidgetBlueprintGeneratedClass. As usual, the copy only takes place if the current value of the property on the instance matches the old value of the modified property.
Now, when the changed UserWidget property is "DesiredFocusWidget", some special handling is needed, since each WidgetTree has its own internal instance of the referenced widget. This appears to be currently handled in UUserWidget::PostEditChangeProperty(), which is triggered by FPropertyNode::NotifyPostChange() before the operation described above, and it does this:
// We cannot use the Widget Ptr as we need to find the widget with the same name in the CDO
UserWidgetCDO->SetDesiredFocusWidget(DesiredFocusWidget.GetFName());
Unfortunately, this violates the premises stated by the comments inside MigrateFromChain(). Because of that, function PropagateDefaultPropertyChange() fails the following check:
const bool bShouldImport = ResolvedComparisonPath.GetFProperty()->Identical(CDOPristineValueAddr, Instance_DestValueAddr, PPF_DeepComparison);
As a result, the property is not propagated properly.
A quick (but probably not ideal) workaround is to change UUserWidget::PostEditChangeProperty() so that it performs the full property change propagation assuming that all instances always have this property in sync with the CDO:
// We cannot use the Widget Ptr as we need to find the widget with the same name in the CDO
UserWidgetCDO->SetDesiredFocusWidget(DesiredFocusWidget.GetFName());
TArray<UObject*> ArchetypeInstances;
UserWidgetCDO->GetArchetypeInstances(ArchetypeInstances);
for (UObject* ArchetypeInstance : ArchetypeInstances)
{
((UUserWidget*)ArchetypeInstance)->SetDesiredFocusWidget(DesiredFocusWidget.GetFName());
}
=== Repro Project ===
Start on step 10 below.
=== Blank Project ===
1. Create 5 widget blueprints based on the UserWidget class: WidgetA, WidgetB, WidgetC, WidgetD, WidgetE
2. On the Hierarchy of each widget:
2.1. Add a "Horizontal Box" as content root
2.2. Add an "Image" as first child of the box
2.2.1. WidgetA – Horizontal Box – Image
2.2.2. WidgetB – Horizontal Box – Image
2.2.3. WidgetC – Horizontal Box – Image
2.2.4. WidgetD – Horizontal Box – Image
2.2.5. WidgetE – Horizontal Box – Image
3. On the Details Panel for each widget
3.1. Select the widget itself on the hierarchy
3.1.1. Set "Is Focusable" to "True"
3.1.2. Set "Visibility" to "Visible"
3.2. Select the Image widget
3.2.1. Set it to a distinct color on each widget
4. Do the following operations on each widget, starting with WidgetD and working up to WidgetA (order is important):
4.1. Add the next-letter widget as second child of the horizontal box
4.2. Select the root of the hierarchy and, on the details panel, set "Desired Focus Widget" to that child
4.3. Compile and save
5. The hierarchies should now be:
5.1. WidgetA – Horizontal Box –
5.2. WidgetB – Horizontal Box –
{Image, WidgetC}5.3. WidgetC – Horizontal Box –
{Image, WidgetD}5.4. WidgetD – Horizontal Box –
{Image, WidgetE}5.5. WidgetE – Horizontal Box –
{Image}6. Compile, save and close all widgets
7. Restart the Editor
8. Create a new level from the Basic template
9. Edit the Level Blueprint's Event BeginPlay:
9.1. SetInputModeUIOnly(PlayerController0, DoNotLock)
9.2. Create Widget "WidgetA"
9.3. Add to Viewport(WidgetA)
10. Open the Widget Reflector (Tools menu, Debug category)
10.1. Change its mode from "Pick Hit-Testable Widgets" to "Show Focus"
10.2. Disable "Flags – Focus On Pick"
11. Keep the Level Editor and the Widget Reflector visible side-by-side
12. PIE, then click on the leftmost colored image (which is inside WidgetA)
12.1. In the Widget Reflector, note that focus went straight to WidgetE
13. Open WidgetB
13.1. Set its "Desired Focus Widget" from "WidgetC" to "Self"
13.2. Compile, save, close
14. PIE, then click on the leftmost colored image (which is inside WidgetA)
14.1. In the Widget Reflector, note that focus still went straight to WidgetE
15. In the Content Browser:
15.1. Right-click WidgetA (not WidgetB)
15.2. Asset Actions – Reload
16. PIE, then click on the leftmost colored image (which is inside WidgetA)
16.1. In the Widget Reflector, note that focus not stopped at WidgetB
17. Repeat from step 13 as necessary alternating "Desired Focus Widget" between "Self" and "WidgetC"
Note: If the order on step 4 is different, the setup will not make the focus go straight to WidgetE until step 4 is re-done in the correct order.
There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-353920 in the post.