One of the most common questions you'll have if you start making C++-based UIs is this:
How can I control Blueprint-created widgets from C++?
The answer to this is the BindWidget
meta property.
Basic BindWidget example
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UTextBlock* TitleLabel;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UImage* IconImage;
While it's not mentioned on the UPROPERTY()
documentation,
it's one of the most useful tags for you as a UI developer.
As an aside, check out my full UPROPERTY documentation that includes all
properties for BindWidget
and others.
By marking a pointer to a widget as BindWidget
, you can create an
identically-named widget in a Blueprint subclass of your C++ class, and at
run-time access it from the C++.
Here's a step-by-step process to getting a test working:
- Create a C++ subclass of
UUserWidget
. - In it add a member variable that is
UWidget*
or a subclass of it (e.g.UImage
,UTextBlock
etc.) - Mark it with
UPROPERTY(meta=(BindWidget))
. - Run the editor and create a Blueprint subclass of your C++ class.
- Create a widget with the same type and exact name as your member variable.
- You can now access the widget from C++.
If any of these steps don't make sense, check out my introductory series on making UIs with Unreal.
Example Code
BindExample.h
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "BindExample.generated.h"
UCLASS(Abstract)
class UBindExample : public UUserWidget
{
GENERATED_BODY()
protected:
virtual void NativeConstruct() override;
UPROPERTY(BlueprintReadOnly, meta=(BindWidget))
class UTextBlock* ItemTitle;
};
BindExample.cpp
#include "BindExample.h"
#include "Components/TextBlock.h"
void UBindExample::NativeConstruct()
{
Super::NativeConstruct();
// ItemTitle can be nullptr if we haven't created it in the
// Blueprint subclass
if (ItemTitle)
{
ItemTitle->SetText(FText::FromString(TEXT("Hello world!")));
}
}
Now compile the C++ and open up your Blueprint subclass of the C++ class where
you added the ItemTitle
property. If you compile your Blueprint, you will be
shown an error if there is no TextBlock widget named ItemTitle
inside your
UserWidget.
Compiling a Blueprint with a missing BindWidget-marked property
If we now add a Text widget and change its name to match our C++ file, this compilation error will go away, and when we run the game, the text will be changed to say "Hello world!".
Blueprint with correctly-named Text Block
Pros & Cons of using BindWidget
and C++
Why would we want to use BindWidget
and C++ instead of just writing
all our logic in Blueprints?
- Easier to maintain complex logic in C++. No spaghetti-fighting in Blueprints.
- Easier for collaboration, no worries about locking Blueprint assets.
- Requires re-compile to see changes. This time cost can be avoided to some extent by using **Live Coding**.
- Harder for non-programmers to see how data is being populated as the logic is moved from Blueprints to C++.
Optional Widgets
There are some situations where you may have a base C++ class and many different Blueprint subclasses of it. For example a base button class could have common logic in C++, but you could create many different Blueprint subclasses, one for each visual style. Some visual styles might have icons, others might have text, so it would make sense to make the text and icon image widgets optional.
To make a widget optional use meta=(BindWidgetOptional)
. With this there
will be no error shown if the Blueprint class does not have a widget with that
name.
BindWidgetOptional example
UPROPERTY(BlueprintReadWrite, meta=(BindWidgetOptional))
UTextBlock* ButtonLabel;
UPROPERTY(BlueprintReadWrite, meta=(BindWidgetOptional))
UImage* IconImage;
Quirks of BindWidget
- Compiling a Blueprint without a
BindWidget
-marked widget will show an error. However it is still possible to run your game. If you want to avoid crashes in this situation, you will need to check that the variable is notnullptr
. - Normally when ticking the "Is Variable" flag on a UserWidget would make it
available in the Graph tab of the editor. However, if there is a property
with that name, marked as
BindWidget
, the only way to make it accessible in the blueprint is addBlueprintReadOnly
orBlueprintReadWrite
to itsUPROPERTY()
tag. - By default, variables defined in a parent C++ class are only shown in the Variables list if "Show Inherited Variables" is checked (see screenshot).
- Widgets marked with
BindWidget
are null in the C++ constructor, they are initialized later on in the lifecycle. If you need to do constructor-like setup use theNativeConstruct()
function.
Showing variables defined in a parent C++ class
What next?
- It's also possible to control UMG animations from C++ with
meta=(BindWidgetAnim)
- You can combine this with the
SynchronizeProperties
function to create UserWidgets whose appearance updates dynamically in the editor.