Some logic could be shared between different widget blueprint, like:
- pull some text from somewhere and show it on this
TextBlock
widget
So I want to bring Component
to UUserWidget
, just like Actor - Component
Which provides an important benefit:
- layout and logic can be separate completely (all logic can be implemented in components and reused everywhere!)
The key problem is how to let component manipulate widgets in owner widget while they are decoupled
We all know that a widget property marked with BindWidget
or BindWidgetOptional
will be bind to the widget with same name
By looking at its implementation (UWidgetBlueprintGeneratedClass::InitializeWidgetStatic
),
I found it utilizing the property name to find the widget and set back to the widget property at runtime to accomplish the binding process
So if we can retrieve the member name, we could do the same process as BindWidget
to solve this problem
But how could we store the member name? Define a user struct to deal with it? like:
struct FWidgetBinding
{
UPROPERTY()
FName MemberName;
UPROPERTY()
TWeakObjectPtr<UObject> Object;
};
There is a better choice that do the "two things" at once : TSoftObjectPtr
. It provides several benefits:
- It Stores object full path rather than object pointer, which allows the object address to change for being replaced or re-instanced because of recompiling or reopen the editor without losing the reference
- When the relative object get renamed, it should also "renamed" automatically
MemberName
is included in object full pathWeakPtr
is included as a data member- Other engine features comes with it
but with extra overhead:
SubPathString
is a FString
Which looks acceptable
The FWidgetTypeCustomization
that engine provides almost do the job for me, but it has several problems:
- It only customize the raw widget object pointer ;)
- It uses the preview widget of blueprint editor as the "data source", so the reference is not persistent (reference lost after recompile)
- It does not customize things in
Instanced Objects
;) (seems like neither of the two types of detail customizations work withInstanced Objects
)
the last two problem is "deadly" to this, because we need the component to be instanced
, so I have to write detail customization on my own ;) :
WidgetComponentEditor
The name might be bit of misleading, it only customized the Components
property (not a hard coded member name) of any given class at the moment, not a brand new editor :)
@see WidgetComponentStatics::LinkSoftObjectToRuntimeVariable, what it does:
- build the
WidgetName
toWidgetObject
map - set the object to the
TSoftObjectPtr
with sameName
It should be called when widget is initialized, but this is already handled with a component~ 🍺
Luckily, I avoid to reinvent the wheel with the engine provided UUserWidgetExtension
class, it has all the interface i need,
and it is feature complete 🎉
- Initialize (BeginPlay)
- Destruct (EndPlay)
- RequiresTick (IsTickEnabled)
- Tick (Tick)
Done!
Basically, to adopt your custom widget class with this component mechanism:
- copy-paste all the code within this class, it does several things for you:
- add an
UWidgetComponentAsExtension
component to your widget, it will handle most of the work for you- Dealing with detail customization
WidgetComponentStatics::LinkSoftObjectToRuntimeVariable
- specify the
Components
property (the instanced components object container) WidgetComponentStatics::AddComponentsToWidgetExtension
- add an
- add your c++ class of the custom widget to the
UWidgetComponentEditorSetting::WidgetClassToCustomize
through project setting - Done!