How to do it...

  1. We need to add the UMG module to our module's dependencies.
  2. Open up <YourModule>.build.cs, which in our case is Chapter_14.Build.cs, and add UMG to the following code:
using UnrealBuildTool;

public class Chapter_14 : ModuleRules
{
public Chapter_14(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });

PrivateDependencyModuleNames.AddRange(new string[] { });

// Uncomment if you are using Slate UI
PrivateDependencyModuleNames.AddRange(new string[] { "Slate",
"SlateCore", "UMG" });

// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");

// To include OnlineSubsystemSteam, add it to the plugins
// section in your uproject file with the Enabled attribute
// set to true
}
}
  1. Create a new class based on the Slate Widget parent class (SCompoundWidget):

  1. When asked for a name, call it CustomButton.
  1. Once created, add the following code to its declaration:
#pragma once

#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"

class CHAPTER_14_API SCustomButton : public SCompoundWidget
{
SLATE_BEGIN_ARGS(SCustomButton)
: _Label(TEXT("Default Value"))
, _ButtonClicked()
{}
SLATE_ATTRIBUTE(FString, Label)
SLATE_EVENT(FOnClicked, ButtonClicked)
SLATE_END_ARGS()

public:
void Construct(const FArguments& InArgs);
TAttribute<FString> Label;
FOnClicked ButtonClicked;
};

  1. Implement the class with the following in the corresponding .cpp file:
#include "CustomButton.h"
#include "SlateOptMacros.h"
#include "Chapter_14.h"


void SCustomButton::Construct(const FArguments& InArgs)
{
Label = InArgs._Label;
ButtonClicked = InArgs._ButtonClicked;
ChildSlot.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
[SNew(SButton)
.OnClicked(ButtonClicked)
.Content()
[
SNew(STextBlock)
.Text_Lambda([this] {return FText::FromString(Label.Get()); })
]
];
}

  1. Create a second class, this time based on Widget:

  1. Call this new class CustomButtonWidget and press Create Class
  2. Add the bold code in the following snippet to the CustomButtonWidget.h file:
#pragma once

#include "CoreMinimal.h"
#include "Components/Widget.h"
#include "CustomButton.h"
#include "SlateDelegates.h"
#include "CustomButtonWidget.generated.h"

DECLARE_DYNAMIC_DELEGATE_RetVal(FString, FGetString);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FButtonClicked);

UCLASS()
class CHAPTER_14_API UCustomButtonWidget : public UWidget
{
GENERATED_BODY()

protected:
TSharedPtr<SCustomButton> MyButton;

virtual TSharedRef<SWidget> RebuildWidget() override;

public:
UCustomButtonWidget();
//multicast
UPROPERTY(BlueprintAssignable)
FButtonClicked ButtonClicked;

FReply OnButtonClicked();

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString Label;

//MUST be of the form varnameDelegate
UPROPERTY()
FGetString LabelDelegate;

virtual void SynchronizeProperties() override;
};
  1. Now, create the implementation for UCustomButtonWidget:
#include "CustomButtonWidget.h"
#include "Chapter_14.h"

TSharedRef<SWidget> UCustomButtonWidget::RebuildWidget()
{
MyButton = SNew(SCustomButton)
.ButtonClicked(BIND_UOBJECT_DELEGATE(FOnClicked, OnButtonClicked));
return MyButton.ToSharedRef();
}

UCustomButtonWidget::UCustomButtonWidget()
:Label(TEXT("Default Value"))
{

}

FReply UCustomButtonWidget::OnButtonClicked()
{
ButtonClicked.Broadcast();
return FReply::Handled();
}


void UCustomButtonWidget::SynchronizeProperties()
{
Super::SynchronizeProperties();
TAttribute<FString> LabelBinding = OPTIONAL_BINDING(FString, Label);
MyButton->Label = LabelBinding;
}
  1. Save your scripts and compile your code.
  2. Create a new Widget Blueprint by right-clicking on the Content Browser and selecting User Interface and then Widget Blueprint:

You can use the mouse wheel in the context menu to scroll to the User Interface section.
  1. Open your new Widget Blueprint by double-clicking on it.
  2. Find the Custom Button Widget in the Widget Palette:

  1. Drag an instance of it out into the main area.
  1. With the instance selected, change the Label property in the Details panel:

Verify that your button has changed its label.

  1. Now, we will create a binding to demonstrate that we can link arbitrary blueprint functions to the label property on our widget, which, in turn, drives the Widget's textblock label.
  1. Click on Bind to the right of the Label property and select Create Binding:

  1. Within the graph that is now displayed, place a Get Game Time in Seconds node by right-clicking within the main area:

  1. Link the return value from the Get Game Time node to the Return Value pin in the function:

  1. A Convert Float to String node will be automatically inserted for you:

  1. Compile the blueprint to ensure it is working correctly.
  1. Next, open the Level Blueprints by clicking on the Blueprints button on the taskbar and then selecting Open Level Blueprint:

  1. To the right of the Event BeginPlay node, place a Create Widget node into the graph:

  1. Select the Class of widget to spawn it as the new Widget Blueprint that we created a moment ago within the editor:

  1. Click and drag away from the Owning Player pin on the create widget node and place a Get Player Controller node:

  1. Likewise, drag away from the return value of the Create Widget node and place an Add to Viewport node:

  1. Lastly, link the BeginPlay node to the execution pin on the create widget node:

  1. Preview your game and verify that the widget we've displayed onscreen is our new custom button, with its label bound to the number of seconds that have elapsed since the game started:

Button displaying the elapsed time in the level