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.

Unreal Engine 5 mannequins standing with health widget above their head with the showcasing their data in a UI List View
Quinn and the players

Software Versions: Unreal Engine 5.5.0 | Rider 2024.3

Project Name: MyProject

Unreal Engine's list view inside the UserWidgets 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.

Unreal Engine 5 new C++ user widget class
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.

Unreal Engine 5 user widget designer for W_MyWidget_ListEntry
W_MyWidget_ListEntry Designer
Unreal Engine 5 user widget graph view with class settings selected for for W_MyWidget_ListEntry
W_MyWidget_ListEntry Graph

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.

Unreal Engine 5 user widget W_MyWidget_List with entry class circled in red
W_MyWidget_List

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.

Unreal Engine 5 Blueprint viewport of BP_MyStructPlayer
BP_MyStructPlayer

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.

Unreal Engine 5 Blueprint event graph of BP_MyStructBoundingBox showing overlap events
BP_MyStructBoundingBox

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.

Unreal Engine 5 Quinn triggering the user widget list view with all the mannequin players in a line
Quinn triggering the list view

I hope this helps. To get a better understanding of this post clone or download the repo for a full understanding of the content.

Loading...