I have a minimal project which declares a C++ UUserWidget class called USimpleBase, like this:
UCLASS()
class MY_API USimpleBase : public UUserWidget
{
GENERATED_BODY()
protected:
virtual TSharedRef<SWidget> RebuildWidget() override;
};
It implements RebuildWidget() like this:
TSharedRef<SWidget> USimpleBase::RebuildWidget()
{
auto UnderlyingWidget = Super::RebuildWidget();
auto Canvas = Cast<UCanvasPanel>(GetRootWidget());
auto RetainerBox = WidgetTree->ConstructWidget<URetainerBox>(URetainerBox::StaticClass());
auto CanvasSlot = Cast<UCanvasPanelSlot>(Canvas->AddChild(RetainerBox));
// Anchor the retainer box to stretch fill the canvas.
// This way, when the user resizes us, it will resize the retainer box the same way.
CanvasSlot->SetAnchors(FAnchors(0.0f, 0.0f, 1.0f, 1.0f));
CanvasSlot->SetAlignment(FVector2D(0.0f, 0.0f));
CanvasSlot->SetOffsets(FMargin(0));
// Add a text block inside the retainer box.
auto TextBlock = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), "TextBlock");
RetainerBox->SetContent(TextBlock);
TextBlock->SetText(FText::FromString(TEXT("Hello")));
return UnderlyingWidget;
}
I create a new widget in the content browser called BP_Simple, open it, and in the UMG Designer,
reparent it to USimpleBase.
I create another widget called BP_TestView, open it, and in the Designer,
place an instance of BP_Simple on the layout.
The widget shows up okay, but if I move it, an ensure() check fails in FWidgetBlueprintEditor::DestroyPreview
on this line:
ensure(!PreviewSlateWidgetWeak.IsValid());
and this log message occurs:
LogBlueprint: Warning: [Compiler ] Leak Detected! CanvasPanel_0 ( Canvas Panel ) still has living Slate widgets, it or the parent widget is keeping them in memory. Release all Slate resources in ReleaseSlateResources().
Placing a different widget (like an OverlayPanel) inside the RetainerBox is fine.
Placing the TextBlock inside the Canvas is fine.
It’s only when placing the TextBlock inside the RetainerBox that the problem occurs.
The problem also happens if we have this widget hierarchy:
RetainerBox
OverlayPanel
TextBlock
Suggesting that if a TextBlock is anywhere a child of a RetainerBox, it’s a problem.
I’ve verified with dumps of the USimpleBase’s WidgetTree that it has exactly
the widgets created and nothing else (Canvas, RetainerBox, TextBlock). It also
looks okay in the Widget Reflector tool at runtime.
I’ve looked at the ReleaseSlateResources() implementations for UUserWidget, Canvas, RetainerBox, and TextBlock
and they all seem okay. If I had to guess, TextBlock or the Slate prepass system is making some assumption about
the TextBlock’s container?