Download

Trying to use style mixing c++ and BP

Hello Everyone,

First of all, I am terribly sorry for this huge wall of a post, but i don’t know how to make it shorter without falling in the usual “how do i use 3D widgets ?” questions.
I have tried, sincerely, for a whole week, and just gathered problems and questions, while slowly becoming crazy :slight_smile:

So let’s start.

All i want to do is define one style and use it everywhere.

I am trying to use 3D widget as (i understood) they should be when coding: define the layout in the editor, and set all the logic in c++.

So, having a lot of elements, i naturally need to centralize all the styling and would like to use some WidgetStyle for that.

I’m not going to show all my trials and errors, but i ended up with the current workflow that sort of works:

  • C++: define a structure inheriting from FSlateWidgetStyle and containing all the styles i need, such as:

// button style
UPROPERTY(EditAnywhere, Category = "Yag")
	FButtonStyle YagButtonStyle;
// normal text
UPROPERTY(EditAnywhere, Category = "Yag")
	FTextBlockStyle YagTextStyle;
// editable test brush
UPROPERTY(EditAnywhere, Category = "Yag")
	FSlateBrush YagEditableTextBrush;

  • Editor: create a WidgetStyle BP inheriting from it, and set all my styles inside:

  • C++: create a c++ StyleSet using code from this wiki (adapted from menuStyle.h and menuStyle.cpp):
    https://wiki.unrealengine.com/Slate_Style_Sets_Part_2

  • C++: create a “mother widget” class inheriting from UUserWidget (ALL my BP widget will inherit from this class).

  • C++: in my mother widget class, define the following SetStyle function:


void UYagMainWidget::SetStyle()
{
	UE_LOG(LogTemp, Warning, TEXT("SetStyle"));

	if (!WidgetTree) return;

	UE_LOG(LogTemp, Warning, TEXT("SetStyle Widget OK"));

	// get styles
	YagGlobalStyle = FYagGlobalStyle::Get().GetWidgetStyle<FYagStyle>("YagGlobalStyle");
	YagButtonStyle = YagGlobalStyle.YagButtonStyle;
	YagTextStyle = YagGlobalStyle.YagTextStyle;

	// set style on every child widget
	WidgetTree->ForEachWidget(&](UWidget* Widget)
	{
		UButton* ThisButton = Cast<UButton>(Widget);
		if (ThisButton)
		{
			ThisButton->SetStyle(YagButtonStyle);
			UE_LOG(LogTemp, Warning, TEXT("UButton style OK"));
		}

		UTextBlock* ThisTextBlock = Cast<UTextBlock>(Widget);
		if (ThisTextBlock)
		{
			ThisTextBlock->SetColorAndOpacity(YagTextStyle.ColorAndOpacity);
			ThisTextBlock->SetFont(YagTextStyle.Font);
			UE_LOG(LogTemp, Warning, TEXT("UTextBlock style OK"));
		}

		UEditableTextBox* ThisEditableTextBox = Cast<UEditableTextBox>(Widget);
		if (ThisEditableTextBox)
		{
			ThisEditableTextBox->WidgetStyle.SetForegroundColor(YagTextStyle.ColorAndOpacity);
			ThisEditableTextBox->WidgetStyle.SetFont(YagTextStyle.Font);
			UE_LOG(LogTemp, Warning, TEXT("UEditableTextBox style OK"));
		}
	});

}

  • C++: create daughter class widget inheriting from the mother, that will contain specific logic for each UI

  • Editor: create BP 3D widget inheriting from the daughter classes, and make incredibly great UIs using buttons and texts :slight_smile:
    In the following capture, YagSettingsWidget is a c++ (daughter) class inheriting from my mother UUserWidget class (which is called YagMainWidget)

742b6774beefb8343608c8a9a97f6ece8d2665bd.jpeg

At this point i get a first problem that seems to hapen only with EditableTextBox: the style is correctly defined but not applied.
In the herabove capture, you can that both the UTextBlock (connect and listen) are correctly styled, but the EditableTextBox is not, despite having the correctly defined style (large red letters).
If i modify the editable text (changing any property, including the text), then the style applies correctly:

93acefa47ff24edbf7c2ec2fa13faf3ce052a83e.jpeg

But when i compile, it comes back to its original style:

5e6d53772033d874ed51a5c2bd16e0b3f1a71b29.jpeg

All that said, when i click play in the editor, it works:

70fcadd6560f59c0f788c655997937ca3d9e9a35.jpeg

Horray ? Not so fast.

I tried to package my game and here’s what i get in the packaged game:

c12ac3656fe4ffe165c3e8d2ec70705b6157dcf3.jpeg

As you can see, the buttons not only have no style (no transparency and incorrect font and color) but are super small.

I am absolutely sure that the BP widgetstyle is available because:

  • i force its packaging in the editor settings:

7bf4b99cae499a1575aa08c790633f4f281aa783.jpeg

  • i have put logs in the setstyle function and i go through all the ifs, as showed by this extract (log from the packaged game):

653a09efcb3d49260624c99a03fc37e3dc597b55.jpeg

So in the packaged game, the SetStyle() function is called and seems to succeed in everything it tries to do, but with no visible effect.

One important question IMO is where should i call the SetStyle function ?
I have tried to put it in Initialize()/constructor (=>editor crash), and even in various tick functions (widgets, pawn, playercontroller…) (=> no visible effect).
The only way i can have it working in PIE is to call it in the OnWidgetRebuilt() function:


UYagMainWidget::UYagMainWidget(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	//SetStyle(); // editor crash
}
bool UYagMainWidget::Initialize()
{
	Super::Initialize();

	//SetStyle(); // editor crash

	return true;
}
void UYagMainWidget::OnWidgetRebuilt()
{
	Super::OnWidgetRebuilt();

	SetStyle(); // works in PIE only

}

So here am i.

Am i completely out of range here ? What would be the correct/official way to achieve a style centralization/automation ?

Thanks !

Cedric

You need to override SynchronizeProperties and call your SetStyle function before calling the parent function



void UUserWidgetTest::SynchronizeProperties()
{
	SetStyle();
        Super::SynchronizeProperties
}


Also, you need to manually call synchronize properties function for each umg widget type



void UYagMainWidget::SetStyle()
{
	UE_LOG(LogTemp, Warning, TEXT("SetStyle"));

	if (!WidgetTree) return;

	UE_LOG(LogTemp, Warning, TEXT("SetStyle Widget OK"));

	// get styles
	YagGlobalStyle = FYagGlobalStyle::Get().GetWidgetStyle<FYagStyle>("YagGlobalStyle");
	YagButtonStyle = YagGlobalStyle.YagButtonStyle;
	YagTextStyle = YagGlobalStyle.YagTextStyle;

	// set style on every child widget
	WidgetTree->ForEachWidget(&](UWidget* Widget)
	{
		UButton* ThisButton = Cast<UButton>(Widget);
		if (ThisButton)
		{
			ThisButton->SetStyle(YagButtonStyle);
                        **ThisButton->SynchronizeProperties();**
			UE_LOG(LogTemp, Warning, TEXT("UButton style OK"));
		}

		UTextBlock* ThisTextBlock = Cast<UTextBlock>(Widget);
		if (ThisTextBlock)
		{
			ThisTextBlock->SetColorAndOpacity(YagTextStyle.ColorAndOpacity);
			ThisTextBlock->SetFont(YagTextStyle.Font);
                        **ThisTextBlock->SynchronizeProperties();**
			UE_LOG(LogTemp, Warning, TEXT("UTextBlock style OK"));
		}

		UEditableTextBox* ThisEditableTextBox = Cast<UEditableTextBox>(Widget);
		if (ThisEditableTextBox)
		{
			ThisEditableTextBox->WidgetStyle.SetForegroundColor(YagTextStyle.ColorAndOpacity);
			ThisEditableTextBox->WidgetStyle.SetFont(YagTextStyle.Font);
                        **ThisEditableTextBox->SynchronizeProperties();**
			UE_LOG(LogTemp, Warning, TEXT("UEditableTextBox style OK"));
		}
	});

}


Hello Chris528,

Thanks a lot for your answer, unfortunately (if i understood it correctly) it doesn’t seem to solve my problem.

All the buttons and things aimed in the SetStyle function are the ones defined in various (widget)blueprints.
That is why i don’t have any reference to them and have to parse the whole widgetTree in SetStyle() to reach them.
And hence i can’t override any of their functions.
And when trying to call it without overriding, i get a crash when launching the game (PIE).

The whole purpose of this awkward mechanic is merely to compensate the fact that i can’t assign a default widgetStyle in blueprint.

Thanks though, i hadn’t heard about SynchronizeProperties, i’ll make some tests/research with this one !

Cedric

Another thing you can do is derive a c++ class from UDataAsset and put your custom styles in that. Then make a bp that uses that custom class, and Then its just a matter of passing in a reference to your UDataAsset bp you made into your UUserWidget c++ class

Chris528, thanks a million times, it works now (at least the packaged part) !

Your suggestion first pushed me to simply reference the BP WidgetStyle asset (after all, why use UDataAsset ?) but i couldn’t transform the USlateWidgetStyleAsset into anything useful.

Then i had the idea to rename my WidgetStyle asset (in editor), to be sure i couldn’t be found, and i got exactly the same (wrong) result in PIE as in packaged.
So my problem was that, despite all my checkings and precautions during cooking, the packaged game just didn’t see the WidgetAsset.

So, being sure that it was a simple reference problem and being unable to use a referenced USlateWidgetStyleAsset, i went for your suggestion of using UDataAsset.

Not only does it work very well, but it is much more simple and elegant than my previous attempt !

So, as always, in case anyone comes here with the same problem, here is how i did it:

I defined a UDataAsset in my header:


UCLASS()
class YAG_API UYagStyles : public UDataAsset
{
	GENERATED_BODY()
public:
	// button style
	UPROPERTY(EditAnywhere, Category = "Yag")
	FButtonStyle YagButtonStyle;

	// normal text
	UPROPERTY(EditAnywhere, Category = "Yag")
		FTextBlockStyle YagTextStyle;

	// editable test brush
	UPROPERTY(EditAnywhere, Category = "Yag")
		FSlateBrush YagEditableTextBrush;


};

UCLASS()
class YAG_API UYagMainWidget : public UUserWidget
{
	GENERATED_BODY()
public:
	UYagMainWidget(const FObjectInitializer& ObjectInitializer);

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Yag")
		UYagStyles* YagGlobalStyle;

..]
}

Then created a UDataAsset inheriting from it in the editor:

8da6a03706fa9bbbf8eb3a467df7aa3a29779055.jpeg

And finally, referenced this data asset in the widget constructor:


UYagMainWidget::UYagMainWidget(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	static ConstructorHelpers::FObjectFinder<UYagStyles> YagStyleFinder(TEXT("YagStyles'/Game/Yag/UI/Styles/DA_YagGlobalStyle.DA_YagGlobalStyle'"));
	YagGlobalStyle = YagStyleFinder.Object;
}

And i can now access any style defined in it in the SetStyle function very simply: FButtonStyle ThisYagButtonStyle = YagGlobalStyle->YagButtonStyle, etc.

The UAssetData being correctly referenced in a constructor, it is cooked and hence this works in the packaged game as well.

Now i still have the problem with the UEditableBox (the style being correctly defined but not applied), but i think it’s a different problem.

Again, thanks a lot Chris, your DA suggestion unlocked the main problem :slight_smile:

Cheers

Cedric