How it works...

To use the UWidget class, our module needs to include the UMG module as one of its dependencies, because UWidget is defined inside the UMG module.

The first class that we need to create, however, is our actual SWidget class.

Because we want to aggregate two widgets together into a compound structure, we create our new widget as a CompoundWidget subclass. CompoundWidget allows you to encapsulate a widget hierarchy as a widget itself.

Inside the class, we use the SLATE_BEGIN_ARGS and SLATE_END_ARGS macros to declare an internal struct called FArguments on our new SWidget. Within SLATE_BEGIN_ARGS and SLATE_END_ARGS, the SLATE_ATTRIBUTE and SLATE_EVENT macros are used. SLATE_ATTRIBUTE creates TAttribute for the type we give it. In this class, we declare a TAttribute called _Label, which is more specifically a TAttribute<FString>.

SLATE_EVENT allows us to create member delegates that we can broadcast when something happens internally to the widget.

In SCustomButton, we declare a delegate with the signature FOnClicked, called ButtonClicked.

SLATE_ARGUMENT is another macro (which wasn't used in this recipe) that creates an internal variable with the type and name you provide, appending an underscore to the start of the variable name.

Construct() is the function that widgets implement to self-initialize when they are being instantiated. You'll notice we also create TAttribute and FOnClicked instances ourselves, without the underscores. These are the actual properties of our object into which the arguments that we declared earlier will be copied.

Inside the implementation of Construct, we retrieve the arguments that were passed to us in the FArgumentsstruct, and store them inside our actual member variables for this instance.

We assign Label and ButtonClicked based on what was passed in, and then we actually create our widget hierarchy. We use the same syntax as usual for this with one thing to note, namely the use of Text_Lambda to set the text value of our internal text block. We use a lambda function to retrieve the value of our Label TAttribute using Get(), convert it into FText, and store it as our text block's Text property.

Now that we have our SWidget declared, we need to create a wrapper UWidget object that will expose this widget to the UMG system so that designers can use the widget within the WYSIWYG editor. This class will be called UCustomButtonWidget, and it inherits from UWidget rather than SWidget.

The UWidget object needs a reference to the actual SWidget that it owns, so we place a protected member in the class that will store it as a shared pointer.

A constructor is declared, as well as a ButtonClicked delegate that can be set in Blueprint. We also mirror a Label property that is marked as BlueprintReadWrite so that it can be set in the UMG editor.

Because we want to be able to bind our button's label to a delegate, we add the last of our member variables, which is a delegate that returns a String.

The SynchronizeProperties function applies properties that have been mirrored in our UWidget class across to the SWidget that we are linked with.

RebuildWidget reconstructs the native widget that UWidget is associated with. It uses SNew to construct an instance of our SCustomButton widget, and uses the Slate declarative syntax to bind the UWidget's OnButtonClicked method to the ButtonClicked delegate inside the native widget. This means that when the native widget is clicked, the UWidget will be notified by having OnButtonClicked called.

OnButtonClicked re-broadcasts the clicked event from the native button via the UWidget's ButtonClicked delegate. This means that UObjects and the UMG system can be notified of the button being clicked without having a reference to the native button widget themselves. We can bind to UCustomButtonWidget::ButtonClicked so that we're notified about this.

OnButtonClicked then returns FReply::Handled() to indicate that the event does not need to propagate further. Inside SynchronizeProperties, we call the parent method to ensure that any properties in the parent are also synchronized properly.

We use the OPTIONAL_BINDING macro to link the LabelDelegate delegate in our UWidget class to TAttribute, and, in turn, the native button's label. It is important to note that the OPTIONAL_BINDING macro expects the delegate to be called NameDelegate based on the second parameter to the macro.

OPTIONAL_BINDING allows the value to be overridden by a binding made via UMG, but only if the UMG binding is valid.

This means that when UWidget is told to update itself, for example, because the user customizes a value in the Details panel within UMG, it will recreate the native SWidget if necessary, and then copy the values set in Blueprint/UMG via SynchronizeProperties so that everything continues to work as expected.