UMG RichTextBlock hyperlink / href markup

Hello, I was hoping someone could point me towards some documentation / example code where hyperlinks are being included in a UMG RichTextBlock.

According to the Unreal documentation (here) it is possible to add hyperlinks. However there is no indication of how to do this.

Is this possible to do out of the box?

I have a feeling I have to create a custom decorator but again, I was not able to find any documentation on how to do this. It looks like you need to override URichTextBlockDecorator and FRichTextBlockDecorator but it’s all a bit over my head at this point and I was hoping I could find some example code.

I think the article I posted earlier suggests that one can look at the code for RichTextBlockImageDecorator as an example, but there are some steps missing that I can’t quite figure out.

I tried to create a custom decorator but I wasn’t sure what to do with it afterwards. Does one include both URichTextBlockDecorator and FRichTextBlockDecorator in a single file? Which Methods should I override, and how do I just create a new UButton with a child UTextBlock as the return for the create() function? And then add a onclick listener or something?

Thank you in advance for your help.

Edit:

Currently fumbling my way through the process of creating a custom decorator, but I’m getting the error “Class FRichTextDecorator not found”.

Here is my current attempt:

<removed as I got it working, see below>

Edit2: Nevermind the first edit, I got it to compile by moving the the entire class that inherits FRichTextDecorator over to the .cpp file like they have done in the RichTextBlockImageDecorator

create a .h file:

#include "CoreMinimal.h"
#include "Components/RichTextBlockDecorator.h"
#include "HyperLinkRichTextBlockDecorator.generated.h"

class FRichInlineHyperLinkDecorator : public FRichTextDecorator
{
public:
	FRichInlineHyperLinkDecorator(URichTextBlock* InOwner, UHyperLinkRichTextBlockDecorator* decorator);
	virtual bool Supports(const FTextRunParseResults& RunParseResult, const FString& Text) const override;
protected:
	virtual TSharedPtr<SWidget> CreateDecoratorWidget(const FTextRunInfo& RunInfo, const FTextBlockStyle& TextStyle) const override;
	FHyperlinkStyle linkstyle;
	FSimpleDelegate del;
	mutable FString idString;
};
//////////////////////////////////////////////////////////////////////////

UCLASS()
class MYTEST_API UHyperLinkRichTextBlockDecorator : public URichTextBlockDecorator
{
	GENERATED_BODY()
public:
	UHyperLinkRichTextBlockDecorator(const FObjectInitializer& ObjectInitializer);

	virtual TSharedPtr<ITextDecorator> CreateDecorator(URichTextBlock* InOwner) override;

	UPROPERTY(EditAnywhere, Category=Appearance)
	FHyperlinkStyle style;

	UFUNCTION(BlueprintNativeEvent)
	void ClickFun();
};

and a .cpp file:

#include "HyperLinkRichTextBlockDecorator.h"

#include <Components/RichTextBlock.h>
#include <SRichTextHyperlink.h>

FRichInlineHyperLinkDecorator::FRichInlineHyperLinkDecorator(URichTextBlock* InOwner, UHyperLinkRichTextBlockDecorator* decorator)
	: FRichTextDecorator(InOwner)
{
	linkstyle = decorator->style;
	del.BindLambda([=]() {
		decorator->ClickFun();
	});
}

bool FRichInlineHyperLinkDecorator::Supports(const FTextRunParseResults& RunParseResult, const FString& Text) const

{
	if (RunParseResult.Name == TEXT("a") && RunParseResult.MetaData.Contains(TEXT("id")))
	{
		const FTextRange& IdRange = RunParseResult.MetaData[TEXT("id")];
		idString = Text.Mid(IdRange.BeginIndex, IdRange.EndIndex - IdRange.BeginIndex);
		return true;
	}
	return false;
}

TSharedPtr<SWidget> FRichInlineHyperLinkDecorator::CreateDecoratorWidget(const FTextRunInfo& RunInfo, const FTextBlockStyle& TextStyle) const
{
	const bool bWarnIfMissing = true;

	TSharedPtr<FSlateHyperlinkRun::FWidgetViewModel> model = MakeShareable(new FSlateHyperlinkRun::FWidgetViewModel);

	TSharedPtr<SRichTextHyperlink> link = SNew(SRichTextHyperlink, model.ToSharedRef())
		.Text(FText::FromString("link"))
		.Style(&linkstyle)
		.OnNavigate(del);

	return link;
}

//////////////////////////////////////////////////////////////////////////

TSharedPtr<ITextDecorator> UHyperLinkRichTextBlockDecorator::CreateDecorator(URichTextBlock* InOwner)
{
	return MakeShareable(new FRichInlineHyperLinkDecorator(InOwner, this));
}

void UHyperLinkRichTextBlockDecorator::ClickFun_Implementation()
{
	if (GEngine) {
		GEngine->AddOnScreenDebugMessage(-1, 20, FColor::Red, "click link");
	}
}

UHyperLinkRichTextBlockDecorator::UHyperLinkRichTextBlockDecorator(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
}

and then use this decorator to create a blueprint

290998-tim图片20191024195615.png

and then use this blueprint decorator like RichTextBlockImageDecorator:

good luck!

291000-tim图片20191024200027.png

BTW,you can rewrite the click function in the blueprint and change the link style :

1 Like

this is awesome. Just what I was looking for. However, I am very new to C++ in Unreal, and after following your directions, I am unable to see the HyperLinkRichTextBlockDecorator in the available classes when I go to create the Blueprint class. Do I need to perform some additional steps like Generate Project files or something? One C++ tutorial said to do that when creating a blueprint node to access a function that exists already in C++

1 Like

Had to do it this way to get it to work in 4.23:

BUILD.CS file:

// Fill out your copyright notice in the Description page of Project Settings.

using UnrealBuildTool;

public class YourProjectName : ModuleRules
{
public YourProjectName(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
}

}


.H FILE:
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Components/RichTextBlockDecorator.h"
#include "HyperLinkRichTextBlockDecorator.generated.h"

class FRichInlineHyperLinkDecorator : public FRichTextDecorator
{
public:
	FRichInlineHyperLinkDecorator(URichTextBlock* InOwner, UHyperLinkRichTextBlockDecorator* decorator);
	virtual bool Supports(const FTextRunParseResults& RunParseResult, const FString& Text) const override;
protected:
	virtual TSharedPtr<SWidget> CreateDecoratorWidget(const FTextRunInfo& RunInfo, const FTextBlockStyle& TextStyle) const override;
	FHyperlinkStyle linkstyle;
	FSimpleDelegate del;
	mutable FString idString;
};
//////////////////////////////////////////////////////////////////////////

UCLASS()
class YOURPROJECTNAME_API UHyperLinkRichTextBlockDecorator : public URichTextBlockDecorator
{
	GENERATED_BODY()
public:
	UHyperLinkRichTextBlockDecorator(const FObjectInitializer& ObjectInitializer);

	virtual TSharedPtr<ITextDecorator> CreateDecorator(URichTextBlock* InOwner) override;

	UPROPERTY(EditAnywhere, Category = Appearance)
		FHyperlinkStyle style;

	UFUNCTION(BlueprintNativeEvent)
		void ClickFun();
};

CPP file:
// Fill out your copyright notice in the Description page of Project Settings.

#include "HyperLinkRichTextBlockDecorator.h"						
#include "Components/RichTextBlockDecorator.h"
#include "Widgets/Input/SRichTextHyperlink.h"


UHyperLinkRichTextBlockDecorator::UHyperLinkRichTextBlockDecorator(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
}

FRichInlineHyperLinkDecorator::FRichInlineHyperLinkDecorator(URichTextBlock* InOwner, UHyperLinkRichTextBlockDecorator* decorator)
	: FRichTextDecorator(InOwner)
{
	linkstyle = decorator->style;
	del.BindLambda([=]() {
		decorator->ClickFun();
	});
}

bool FRichInlineHyperLinkDecorator::Supports(const FTextRunParseResults& RunParseResult, const FString& Text) const

{
	if (RunParseResult.Name == TEXT("a") && RunParseResult.MetaData.Contains(TEXT("id")))
	{
		const FTextRange& IdRange = RunParseResult.MetaData[TEXT("id")];
		idString = Text.Mid(IdRange.BeginIndex, IdRange.EndIndex - IdRange.BeginIndex);
		return true;
	}
	return false;
}

TSharedPtr<SWidget> FRichInlineHyperLinkDecorator::CreateDecoratorWidget(const FTextRunInfo& RunInfo, const FTextBlockStyle& TextStyle) const
{
	const bool bWarnIfMissing = true;

	TSharedPtr<FSlateHyperlinkRun::FWidgetViewModel> model = MakeShareable(new FSlateHyperlinkRun::FWidgetViewModel);

	TSharedPtr<SRichTextHyperlink> link = SNew(SRichTextHyperlink, model.ToSharedRef())
		.Text(FText::FromString("link"))
		.Style(&linkstyle)
		.OnNavigate(del);

	return link;
}

//////////////////////////////////////////////////////////////////////////

TSharedPtr<ITextDecorator> UHyperLinkRichTextBlockDecorator::CreateDecorator(URichTextBlock* InOwner)
{
	return MakeShareable(new FRichInlineHyperLinkDecorator(InOwner, this));
}

void UHyperLinkRichTextBlockDecorator::ClickFun_Implementation()
{
	if (GEngine) {
		GEngine->AddOnScreenDebugMessage(-1, 20, FColor::Red, "click link");
	}
}
1 Like

And then I had to create a reference to the outside Decorator in order to be able to bind the id strings of each individual link to their individual click handler lambdas.

Wrap does not work for the hyperlink, how to fix it?

1 Like

Sorry I have no idea :\

I hate to bring up an old post but I am working through this same thing. How can I pass the text in-between the tags to the created hyperlink? Or even like the idString? It seems it is hardcoded to only display “link”. I tried modifying it from this: .Text(FText::FromString("link")) to .Text(idString) but it throws a compile error and I can’t figure out a way around it.

Try passing TEXT(idString) instead of just idString.

Also what is the compile error you’re receiving? Knowing what the error is about is usually vital to being able to solve the problem.

Ah yes. Wow I looked at it for so long I didn’t realize I was trying to pass a string as a text. Also I did figure out if you want the text in-between the tags to be what is affected you use this bit of code: .Text(RunInfo.Content)

This is great stuff, thanks for sharing!

I got same question, how to solve auto-wrapping?

Here the SRichTextHyperlink is the subclass of SHyperlink, while SHyperlink is the subclass of SButton, which means it impossible to do auto-wrapping like normal text. Furthermore, this solution is almost equivalent to use a URichTextBlock under the UButton in UMG’s Designer view windows which is more easy to understand and implement. BUT this also doesn’t support text’s auto-wrapping.
So the key problem is the wrong and controversial description in official docs, that the RichTextBlock DOESN’T support hyperlink at all since there’s not easy way to hook user custom callback with text’s auto-wrapping functioning.