Have you ever wanted to build the contents of a UUserWidget in the editor using C++? For example imagine you want to create a gallery widget and you need 20 buttons with correct names. You could create them by hand, but it's slow and error-prone.

The example below shows how to create widgets inside a User Widget blueprint, from C++, and correctly update the widget tree so the new widgets can be selected.

Huge thanks to @Sharundaar for sharing how to do this in my Discord, and allowing me to share it here.

Edit: It's worth mentioning that if you're trying to fill out a list, tree or grid in the editor, to give designers an idea of how it will look, you should consider using ListView.

WBP_EditorTestWidget

The final result, notice the tree contains the text widgets created, as well as the "Fill Text Blocks" button in the details panel.

Solution

First, we'll need to add "UMGEditor" and "UnrealEd" to your dependencies in your Build.cs file.

We're doing something a little weird here, we're going to be adding some editor-specific code to a non-editor class. In order to use the editor functions, we need to add UnrealEd and UMGEditor to our list of modules in MyProject.Build.cs, but only when building the editor.

MyProject.Build.cs snippet

if (Target.bBuildEditor)
{
	PrivateDependencyModuleNames.AddRange(new string[]
	{
		"UMGEditor",
		"UnrealEd"
	});
}

BUIEditorTestWidget.h

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "BUIEditorTestWidget.generated.h"

UCLASS()
class UBUIEditorTestWidget : public UUserWidget
{
	GENERATED_BODY()

protected:
	// Marking a parameterless function with CallInEditor makes a button
	// show up in editor with that name (see screenshot)
	UFUNCTION(CallInEditor, Category="Editor Fill Test")
	void FillTextBlocks();

#if WITH_EDITORONLY_DATA
	UPROPERTY(EditAnywhere, Category="Editor Fill Test", meta=(UIMin=1, UIMax=10, ClampMin=1, ClampMax=10))
	int32 Width = 2;
	UPROPERTY(EditAnywhere, Category="Editor Fill Test", meta=(UIMin=1, UIMax=10, ClampMin=1, ClampMax=10))
	int32 Height = 2;
#endif

	// We don't use GridPanel directly in this example, but it being BindWidget means Unreal
	// will display an error if there is no correctly-named variable.
	UPROPERTY(meta=(BindWidget))
	class UGridPanel* GridPanel;
};

BUIEditorTestWidget.cpp

#include "BUIEditorTestWidget.h"

#if WITH_EDITOR
#include "WidgetBlueprint.h"
#include "Blueprint/WidgetBlueprintGeneratedClass.h"
#include "Blueprint/WidgetTree.h"
#include "Components/GridPanel.h"
#include "Components/TextBlock.h"
#include "Kismet2/BlueprintEditorUtils.h"
#endif

void UBUIEditorTestWidget::FillTextBlocks()
{
#if WITH_EDITOR
	if (!GridPanel)
		return;

	UWidgetBlueprintGeneratedClass* WidgetBlueprintGeneratedClass = Cast<UWidgetBlueprintGeneratedClass>(GetClass());

	UPackage* Package = WidgetBlueprintGeneratedClass->GetPackage();
	UWidgetBlueprint* MainAsset = Cast<UWidgetBlueprint>(Package->FindAssetInPackage());

	// We *cannot* use the BindWidget-marked GridPanel, instead we need to get the widget in the asset's widget tree.
	// However thanks to the BindWidget, we can be relatively sure that FindWidget will be successful.
	UGridPanel* AssetGridPanel = Cast<UGridPanel>(MainAsset->WidgetTree->FindWidget("GridPanel"));

	AssetGridPanel->ClearChildren();
	for (int32 Y = 0; Y < Height; ++Y)
	{
		for (int32 X = 0; X < Width; ++X)
		{
			const FString WidgetName = FString::Printf(TEXT("Text_X%d_Y%x"), X, Y);
			// Create widget with ConstructWidget, not NewObject
			UTextBlock* Text = MainAsset->WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), FName(WidgetName));
			Text->SetText(FText::FromString(WidgetName));

			AssetGridPanel->AddChildToGrid(Text, X, Y);
		}
	}
	AssetGridPanel->Modify();

	MainAsset->Modify();
	FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(MainAsset);
#endif
}

Posted: