How to wait until Widget blueprint is constructed / initialized?

Hi everyone,

I am working on a project that uses widget blueprints for UI element layout and C++ for all logic. A simple example is a widget blueprint that consists of a checkbox (UCheckBox) and another widget blueprint.

I created a parent class that has two variables that hold the two UI elements (checkbox and other blueprint) and a method, and I want to manipulate the UI elements from the method. I also created a parent class for the other blueprint the same way.

Here’s how my class looks:

QuestCheckbox.h
UCLASS()
class UNREALQUESTBP56_API UQuestCheckbox : public UUserWidget
{
	GENERATED_BODY()
	
public:
	UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (BindWidget), Category="UI Setup")
	UQuestBasicText* BasicTextWidget_Answer;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (BindWidget), Category="UI Setup")
	UCheckBox* CheckBox_Answer;
	
	UFUNCTION(BlueprintCallable)
	void SetText(FString Answer) const;
};

QuestCheckbox.cpp
void UQuestCheckbox::SetText(const FString Answer) const
{
	BasicTextWidget_Answer->DisplayedText = FText::FromString(Answer);
}

QuestBasicText.h

UCLASS()
class UNREALQUESTBP56_API UQuestBasicText : public UUserWidget
{
	GENERATED_BODY()
	
public:
	UPROPERTY(BlueprintReadWrite)
	FText DisplayedText;
	UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (BindWidget))
	UTextBlock* TextBlock;
};

And here’s how I use those:

UQuestCheckbox* checkBox = CreateWidget<UQuestCheckbox>(this, UQuestCheckbox::StaticClass());
checkBox->SetText("LULZ");

The second call crashes because BasicTextWidget_Answer is null. So I assumed elements in widget blueprints are not constructed immediately when the widget is instanced. I added checkBox->Construct(); before calling SetText() but that did not help. What am I missing here?

You need to do something that adds it to the viewport/widget tree. That’s what causes the widget to be fully constructed and BindWidget properties to have something that can be assigned.

That could be calling AddToViewport (but for a checkbox that’s probably not the right solution) or adding the widget to some sort of container widget like a canvas panel or a vertical/horizontal box.

Thanks for your suggestion. The checkbox is indeed supposed to be added to a HorizontalBox later, so I tried adding it to the box before accessing its content. It didn’t work though. Do you have any idea in which order these things need to happen? I also tried calling Construct() in different places but that didn’t help either.

UQuestCheckbox* checkBox = CreateWidget<UQuestCheckbox>(this, UQuestCheckbox::StaticClass());
HorizontalBox_Content->AddChildToHorizontalBox(checkBox);
checkBox->Construct();
checkBox->BasicTextWidget_Answer->Construct();
checkBox->SetText("TEST");

The above code is currently in a method called from a blueprint. I tried adding that blueprint to the viewport before the method is called to ensure that again is fully constructed, but that didn’t make a difference.

Well you definitely aren’t supposed to be calling Construct manually on widgets. If adding to the horizontal box isn’t working, it may be that the box hasn’t been added to the viewport yet. It’s possible there’s a frame or two of delay.

There is a NativeConstruct virtual function you can implement. Usually this is how these sorts of APIs work with widgets:

  1. QuestCheckbox would have a member variable for the Text. Maybe with the ExposeOnSpawn meta if you wanted to create them like this but from blueprint. Maybe with EditAnywhere if you wanted to use them directly from the Designer view.
  2. QuestCheckbox::SetText would set that internal member variable and only set the data on the BasicTextWidget if it were not nullptr.
  3. In NativeConstruct (when the BindWidget member should have become set) you would use the member variable to set the state of BasicTextWidget.

Thanks again for your reply. Using Construct() directly was suggested in another forum thread as a last resort approach, so I tried it.

I have now implemented QuestCheckBox::NativeConstruct as you suggested. You were right, it gets called when I add the widget to the HorizontalBox. However, BasicTextWidget_Answer is still null in NativeConstruct.

So I guess, like you mentioned, it’s delayed by a frame or two, which is actually something I know from Unity. However, my research on how to delay things until the next frame in Unreal was not successful so far. If you know how to do so from within a UUserWidget, help would be greatly appreciated.

Current code:

void UQuestCheckbox::NativeConstruct()
{
	Super::NativeConstruct();
	if (BasicTextWidget_Answer != nullptr)
	{
		BasicTextWidget_Answer->DisplayedText = AnswerText;
	}
	else
	{
		UE_LOG(LogTemp, Error, TEXT("Checkbox: Could not set text in NativeConstruct"));
	}
}

void UQuestCheckbox::SetText(const FString Answer)
{
	AnswerText = FText::FromString(Answer);
	if (BasicTextWidget_Answer != nullptr)
	{
		BasicTextWidget_Answer->DisplayedText = AnswerText;
	} 
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("Checkbox: Could not set text in SetText"));
	}
}

AnswerText is a member, as you suggested. Both log entries appear in the log and the text is not set. When debugging, I can see that BasicTextWidget_Answer is indeed null in NativeConstruct().

In C++ it’s the TimerManager which you can get from the UWorld (UserWidget implements GetWorld to access that). That has both timer callbacks as well as a one-off ‘next tick’.

Thank you! Unfortunately, even in the next tick or one second later BasicTextWidget_Answer is still null.

Oh wait! It’s because BindWidget requires a blueprint class.

The way BindWidget works is that you can create a C++ class with a BindWidget member. Then you create a blueprint of that C++ class, creating a widget of that type and give it the same name as the BindWidget member. You will even get a blueprint compile error if the class has BindWidget members that don’t have similarly named widgets.

You’re just instantiating a widget of type QuestCheckbox, the native class. Of course it’s not going to have any widgets created for it. There’s nothing that is creating a widget tree, widgets or binding widgets to that member.

Well, that kinda makes sense. I wondered if I should instantiate the blueprint class from C++ instead of the C++ parent class, but I couldn’t figure out how and thought I wasn’t supposed to do that. So I did some more research and tried the following approaches:

.h
UPROPERTY(EditDefaultsOnly,BlueprintReadWrite,Category="BP Classes")
UClass* YourBPClass;
UPROPERTY(EditDefaultsOnly,BlueprintReadWrite,Category="BP Classes")
TSubclassOf<UQuestCheckbox> YourTSub;

.cpp
// I tried all three of these, one at a time
Dummy = NewObject<UQuestCheckbox>(this, YourTSub, NAME_None);
Dummy = NewObject<UQuestCheckbox>(YourTSub);
Dummy = CreateWidget<typeid(YourBPClass)>(this, YourBPClass->StaticClass()); // does not compile, I don't know how I would use YourBPClass in the template

How do I create an instance of a blueprint class? Isn’t this super common? Using C++ classes as parent for blueprint classes seems to be a super common technique. Is there some documentation I am missing?

UQuestCheckbox* box = CreateWidget< UQuestCheckbox >( Owner, YourTSub );

Owner is a little weird because it’s limited to some specific types: UWidget, UWidgetTree, APlayerController, UGameInstance, UWorld. You can’t pass in just any UObject as a WorldContext.

That is good to know. However, the object I work on and that gets passed in this is derived from UUserWidget, which is derived from UWidget, so that should not be the issue, right?

Yeah, that should be fine to use as the Owner then.

The line I gave you should work just fine then with using the this pointer for Owner in that context.

Thank you so much for your help. The line you provided does indeed work, looks like I didn’t try that combination yet. No waiting for next tick seems to be required, it really came down to correctly instancing the blueprint class instead of just the C++ class.