Unreal Engine 5 List View Widgets in C++ and Blueprints
List views can difficult to understand in UE5. This post should help with some of the pain points while also providing some helpful C++ code.
Software Versions: Unreal Engine 5.5.0 | Rider 2024.3
Project Name: MyProject
Unreal Engine's list view inside the UserWidget
s can be very interesting to get up and running. List views need entries that need to fire an interface when added to the list view. Let's get started.
Before beginning ensure your build.cs
file has UMG
added to the dependcies and uncomment the Slate UI private dependency modules.
MyProject.Build.cs
using UnrealBuildTool;
public class MyProject : ModuleRules
{
public MyProject(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "UMG" });
PrivateDependencyModuleNames.AddRange(new string[] { });
PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
}
}
First, create a new UserWidget
class.
The new widget class will include the building blocks for the list view. Name the class whatever you want, I called it MyWidget_List
. The new class will include a UCanvasPanel
, UListView
, and a TArray
of actors to loop through. In the header file we'll also add the NativeConstruct()
function with an override
, NativeConstruct
is the widget's C++ equivalent to Event Construct
that can be used in the Blueprint.
MyWidget_List.h
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "MyWidget_List.generated.h"
class UCanvasPanel;
class UListView;
UCLASS()
class MYPROJECT_API UMyWidget_List : public UUserWidget
{
GENERATED_BODY()
protected:
virtual void NativeConstruct() override;
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget))
UCanvasPanel* MyPanel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget))
UListView* MyList;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn="true"))
TArray<AActor*> MyActors;
};
The .cpp
file for MyWidget_List
is very basic. We'll be setting the MyActors
array on spawn and then when the widget gets constructed we'll add the items to the list using AddItem
.
MyWidget_List.cpp
#include "MyWidget_List.h"
#include "Components/ListView.h"
void UMyWidget_List::NativeConstruct()
{
for (AActor* MyActor : MyActors)
{
MyList->AddItem(MyActor);
}
}
Remaining in the IDE we'll continue with creating the ListEntry
that is required for a ListView
. Create a new UserWidget
class, I called mine MyWidget_ListEntry
. Since this will be used as an entry class for the ListView
we need to include the UserObjectListEntry
and extend to the class to inherit the interface.
#include "Blueprint/IUserObjectListEntry.h"
#include "Blueprint/UserWidget.h"
#include "MyWidget_ListEntry.generated.h"
...
UCLASS()
class MYPROJECT_API UMyWidget_ListEntry : public UUserWidget, public IUserObjectListEntry
...
The MyWidget_ListEntry
will be used to create an example of player data in the area. The entry will include an UImage
for profile pic, along with a name and health progress bar. I added three setter functions to transform incoming data to the expected UI inputs. Most notably in this entry class is the OnListItemObjectSet
interface function that we'll trigger to run with _Implementation
in the .cpp
file. Also, this file is using the meta tag BindWidget
so in the Blueprint the variable names must match.
MyWidget_ListEntry.h
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/IUserObjectListEntry.h"
#include "Blueprint/UserWidget.h"
#include "MyWidget_ListEntry.generated.h"
class UCanvasPanel;
class UImage;
class UProgressBar;
class UTextBlock;
UCLASS()
class MYPROJECT_API UMyWidget_ListEntry : public UUserWidget, public IUserObjectListEntry
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget))
UCanvasPanel* Panel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget))
UImage* Image;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget))
UTextBlock* Name;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget))
UProgressBar* HealthBar;
UFUNCTION(BlueprintCallable)
void SetHealth(float NewHealth);
UFUNCTION(BlueprintCallable)
void SetName(FString NewName);
UFUNCTION(BlueprintCallable)
void SetImage(UTexture2D* NewImage);
UFUNCTION(BlueprintNativeEvent)
void OnListItemObjectSet(UObject* ListItemObject);
};
The setter functions in the .cpp
are simple enough in their implementation to get the data and set it. Focusing on OnListItemObjectSet
, by using _Implementation
will indicate that we want it to be called when the ListView
runs AddItem
. We start the function by casting to AMyStructPlayer
, this actor will be created later, and if that cast is successful we can use the actor's data to set our UI variables.
MyWidget_ListEntry.cpp
#include "MyWidget_ListEntry.h"
#include "Components/Image.h"
#include "Components/ProgressBar.h"
#include "Components/TextBlock.h"
#include "MyProject/Structs/MyStructPlayer.h"
void UMyWidget_ListEntry::SetHealth(float NewHealth)
{
float NewHealthPct = NewHealth / 100.f;
HealthBar->SetPercent(NewHealthPct);
}
void UMyWidget_ListEntry::SetName(FString NewName)
{
FText NewNameText = FText::FromString(NewName);
Name->SetText(NewNameText);
}
void UMyWidget_ListEntry::SetImage(UTexture2D* NewImage)
{
if (NewImage != nullptr)
{
Image->SetBrushFromTexture(NewImage);
}
}
void UMyWidget_ListEntry::OnListItemObjectSet_Implementation(UObject* ListItemObject)
{
AMyStructPlayer* MyPlayer = Cast<AMyStructPlayer>(ListItemObject);
if (MyPlayer != nullptr)
{
SetName(MyPlayer->MyStruct.Name);
SetHealth(MyPlayer->MyStruct.Health);
SetImage(MyPlayer->MyStruct.Texture);
}
}
The MyStructPlayer
class was made in a previous post. The main points are the skeletal mesh and widget, once configured it will represent a demo player with a health bar above their head. The struct on the player will contain all the data points we'll consume for our UI.
MyStructPlayer.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyStruct.h"
#include "MyStructPlayer.generated.h"
class USkeletalMeshComponent;
class UWidgetComponent;
UCLASS()
class MYPROJECT_API AMyStructPlayer : public AActor
{
GENERATED_BODY()
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Sets default values for this actor's properties
AMyStructPlayer();
UPROPERTY(EditAnywhere)
USceneComponent* Root;
UPROPERTY(EditAnywhere)
USkeletalMeshComponent* SkeletalMesh;
UPROPERTY(EditAnywhere)
UWidgetComponent* Widget;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FMyStruct MyStruct;
};
MyStructPlayer.cpp
#include "MyStructPlayer.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/WidgetComponent.h"
#include "MyProject/Widgets/MyWidget_Health.h"
// Sets default values
AMyStructPlayer::AMyStructPlayer()
{
Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
Root->bVisualizeComponent = true;
RootComponent = Root;
SkeletalMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("SkeletalMesh"));
SkeletalMesh->SetupAttachment(RootComponent);
Widget = CreateDefaultSubobject<UWidgetComponent>(TEXT("Widget"));
Widget->SetupAttachment(RootComponent);
MyStruct.Name = TEXT("Name");
MyStruct.Health = 100.f;
}
void AMyStructPlayer::BeginPlay()
{
Super::BeginPlay();
if (Widget != nullptr && Widget->GetWidgetClass() != nullptr)
{
UUserWidget* UserWidgetObject = Widget->GetUserWidgetObject();
if (UserWidgetObject != nullptr)
{
UMyWidget_Health* MyHealthWidget = Cast<UMyWidget_Health>(UserWidgetObject);
if (MyHealthWidget != nullptr)
{
MyHealthWidget->SetName(MyStruct.Name);
MyHealthWidget->SetHealth(MyStruct.Health);
}
}
}
}
Sticking in C++, for this example I added a trigger box that adds the UI element to the viewport on overlap and also sets the player actors that will be passed to the list view's NativeConstruct
event. I named the actor MyStructBoundingBox
and has a GetPlayers
function that returns all the MyStructPlayers
inside the UBoxCollision
component.
MyStructBoudingBox.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyStructBoundingBox.generated.h"
class UBoxComponent;
class UMyWidget_List;
UCLASS()
class MYPROJECT_API AMyStructBoundingBox : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AMyStructBoundingBox();
UPROPERTY(EditAnywhere)
USceneComponent* Root;
UPROPERTY(EditAnywhere)
UBoxComponent* Box;
UFUNCTION(BLueprintCallable)
TArray<AActor*> GetPlayers();
};
MyStructBoundingBox.cpp
#include "MyStructBoundingBox.h"
#include "MyStructPlayer.h"
#include "Components/BoxComponent.h"
#include "GameFramework/PlayerController.h"
// Sets default values
AMyStructBoundingBox::AMyStructBoundingBox()
{
Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
Root->bVisualizeComponent = true;
RootComponent = Root;
Box = CreateDefaultSubobject<UBoxComponent>(TEXT("Box"));
Box->SetHiddenInGame(false);
Box->SetupAttachment(RootComponent);
}
TArray<AActor*> AMyStructBoundingBox::GetPlayers()
{
TArray<AActor*> OutOverlappingActors;
Box->GetOverlappingActors(OutOverlappingActors, AMyStructPlayer::StaticClass());
return OutOverlappingActors;
}
We're done with C++, we can now use our newly created classes to create new Blueprints that consume all the great things we just created.
Let's start with the entry class widget. Create a new Blueprint using MyWidget_ListEntry
as the parent. I called the Blueprint W_MyWidget_ListEntry
. Since the parent class is MyWidget_ListEntry
we'll need to all the UI elements declared in the header to widget tree and name each element as they are labeled in the .h
file. In the graph section of the widget you'll want to confirm that the interface was implemented correctly and it should appear in Class Settings
under interfaces.
We can now add this list entry to a list view. Create a new Blueprint class inheriting from MyWidget_List
, I called it W_MyWidget_List
. Same as above, complete the widget tree with the elements declared in the header file matching the names with variables. You should be able to find W_MyWidget_ListEntry
in the dropdown. All the logic for both W_MyWidget_List
and W_MyWidget_ListEntry
is in their C++ files. Other designer changes for both widgets can be seen by cloning the project and viewing the widgets in the editor.
For testing I created a Blueprint that inherits from MyStructPlayer
to act as a group of players to build up our actors array. Once the Blueprint is dragged into the world the settings can updated with unique names, health values, and textures.
Next, create a Blueprint using the MyStructBoundingBox
class. This Blueprint will use the GetPlayers
function we created earlier, pass that array to the list widget and then add it the viewport. When the player leaves the box the widget will be removed.
Finally, with everything created, the Blueprints can be dragged into the world. You can adjust the size of the bounding box to encompass all the players. When configuring the players I used existing texture icons from the engine for profile pics.
Below is the final result.
I hope this helps. To get a better understanding of this post clone or download the repo for a full understanding of the content.
Harrison McGuire