Custom slate widget not updating in editor.

I have a custom UWidget that correctly calls SynchronizeProperties to update the backing SWidgets proeprties when the properties are changed in the UMG editor.

However despite setting up the SLATE_ARGUMENT, TSlateAttribute, and PrivateRegisterAttributes declarations the widget doesn’t visually update until you recompile.

I’ve stepped through the code at runtime, and the underlying slate’s property does update. It’s just the EInvalidateWidgetReason::Layout doesn’t seem to have any impact. I thought that invalidating the layour would re call construct

What am I missing in order to get the widget to update in the editor without having to recompile every time?

/* In the UWidget */

void UCarousel::SynchronizeProperties() {
	Super::SynchronizeProperties();
	
	if (slateBacking.IsValid()) {
		slateBacking->SetIsTest(isTest);
	}
}

/* In the SWidget's .h */

class VR_BASE_API SCarousel : public SCompoundWidget {
	SLATE_DECLARE_WIDGET(SCarousel, SCompoundWidget)
public:
	SLATE_BEGIN_ARGS(SCarousel)
		:_isTest(false){
			_Visibility = EVisibility::SelfHitTestInvisible;
		}

		SLATE_ATTRIBUTE(bool, isTest)
	SLATE_END_ARGS()

private:
	TSlateAttribute<bool> isTest;

public:

	/* Constructor/Destructor */
	SCarousel();
	~SCarousel();

	void Construct(const FArguments& args);

	const bool& GetIsTest() const;
	void SetIsTest(bool value);
};
/* In the SWidgets .cpp */

SLATE_IMPLEMENT_WIDGET(SCarousel)
void SCarousel::PrivateRegisterAttributes(FSlateAttributeInitializer& AttributeInitializer) {
	SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, isTest, EInvalidateWidgetReason::Layout);
}


SCarousel::SCarousel() 
	: isTest(*this, false) {
	SetCanTick(false);
}

void SCarousel::Construct(const FArguments& args) {
	isTest.Assign(*this, args._isTest);

	//Green if True, Red if False
	FColor imgColor = IsTest.Get() ? FColor(0, 255, 0) : FColor(255, 0, 0);

	ChildSlot
		.HAlign(HAlign_Fill)
		.VAlign(VAlign_Fill)
		[
			SNew(SOverlay)
				.Clipping(EWidgetClipping::ClipToBoundsAlways)
				+ SOverlay::Slot()
				.HAlign(HAlign_Fill)
				.VAlign(VAlign_Fill)[
					SNew(SImage)
						.ColorAndOpacity(FSlateColor(imgColor))
				]
		];
}

const bool& SCarousel::GetIsTest() const {
	if (isTest.IsBound(*this)) {
		SCarousel& mutableSelf = const_cast<SCarousel&>(*this);
		mutableSelf.isTest.UpdateNow(mutableSelf);
	}
	return isTest.Get();
}

void SCarousel::SetIsTest(bool value) {
	isTest.Assign(*this, value);
}

I’m aware that layout invalidation is overkill for simply changing a color but it was the smallest example i could knock out.

You should probably be overriding “PostEditChangeProperty” instead. This is called when you change a property on the editor panel.

Also, rebuilding widgets when not absolutely required causes hard to debug misbehavior.

Take a look at my code:

Ferrefy-Plugin-UI-Additions/UIAdditionsPlugin/Source/UIAdditionsPlugin/Private/UI/SlateWidgets/Lazy/LazyWidgetSlot.cpp at master · Seda145/Ferrefy-Plugin-UI-Additions · GitHub

#if WITH_EDITOR

void ULazyWidgetSlot::PostEditChangeProperty(FPropertyChangedEvent& InPropertyChangedEvent) {
	Super::PostEditChangeProperty(InPropertyChangedEvent);

	static bool IsReentrant = false;

	if (!IsReentrant) {
		IsReentrant = true;

		if (InPropertyChangedEvent.Property) {
			static const FName PaddingName("Padding");
			static const FName HorizontalAlignmentName("HorizontalAlignment");
			static const FName VerticalAlignmentName("VerticalAlignment");

			if (ULazyWidget* ParentWidget = CastChecked<ULazyWidget>(Parent)) {
				if (InPropertyChangedEvent.Property->GetFName() == PaddingName) {
					FObjectEditorUtils::MigratePropertyValue(this, PaddingName, ParentWidget, PaddingName);
				}
				else if (InPropertyChangedEvent.Property->GetFName() == HorizontalAlignmentName) {
					FObjectEditorUtils::MigratePropertyValue(this, HorizontalAlignmentName, ParentWidget, HorizontalAlignmentName);
				}
				else if (InPropertyChangedEvent.Property->GetFName() == VerticalAlignmentName) {
					FObjectEditorUtils::MigratePropertyValue(this, VerticalAlignmentName, ParentWidget, VerticalAlignmentName);
				}
			}
		}

		IsReentrant = false;
	}
}

#endif

I’ve looked into utilizing the PostEditChangeProperty to manually force a rebuild of the slate structure but I don’t think that’s the solution to my underlying issue. I’ve come to this conclusion after reviewing much of the UMG/Slate source code as the PostEditChangeProperty is generally only used to perform data validation and then (usually) calls SynchronizeProperties itself which places me in the same position.

The reality is despite using TSlateAttribute and SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION with my synced properties, they’re not triggering their invalidation/dirty correctly. If I were to manually force a rebuild in the editor it wouldn’t address the underlying issue which is likely to pop up at runtime too.

I’m certainly missing some macro, declaration, or overridable function I’m just not able to locate it.

Ironically the most success I’ve had was to revert to the old way of managing slate attributes by not using TArrtibute or TSlateAttribute at all…

(All this pain because widget switchers are fundamentally flawed and don’t set their non-visible children as disabled/collapsed or trigger the “Construct”/“Destruct” events when they’re shown/hidden like other slots.)

For future search & reference:

In addition to what I had in my first post, I also needed to do the following:

  1. Change the function void SCarousel::SetIsTest(bool value); to void SCarousel::SetIsTest(TAttribute<bool> value);
  2. Created a TSlateAttribute<FSlateColor> color; attribute to bind the changed color to as isTest was a bool flag. (only needed as i used color, the existing assign would work file if passing the color in instead of a bool.)
  3. In SCarousel::Construct don’t use args._isTest or isTest directly, instead pass args._isTest into the SetIsTest(TAttribute<bool> value) function and use a TAttribute constructed with a lambda in any property assignments. EG:.ColorAndOpacity(TAttribute<FSlateColor>::Create([this]() { return color.Get(); }))
  4. In the SCarousel::SetIsTest(TAttribute<bool> value); function, add an assign for the new color attribute using a TAttribute::Create lambda expression to perform the actual color selection.

Example below also added a padding parameter to show direct consumption unlike the color which required an extra parameter.

void SCarousel::Construct(const FArguments& args) {
	//Update TSlateAttributes to persist the values
	SetPadding(args._padding);
	SetIsRed(args._isTest);

	//Build the slate structure
	ChildSlot
		.HAlign(HAlign_Fill)
		.VAlign(VAlign_Fill)
		.Padding(TAttribute<FMargin>::Create([this]() { return padding.Get(); }))
		[
			SNew(SOverlay)
			.Clipping(EWidgetClipping::ClipToBoundsAlways)

			+ SOverlay::Slot()
			.HAlign(HAlign_Fill)
			.VAlign(VAlign_Fill)
				
				SNew(SImage)
				.ColorAndOpacity(TAttribute<FSlateColor>::Create([this]() { return color.Get(); }))
		];
}

void SCarousel::SetPadding(TAttribute<FMargin> value) {
	padding.Assign(*this, MoveTemp(value));
}

void SCarousel::SetIsTest(TAttribute<bool> value) {
	isTest.Assign(*this, value);

	color.Assign(*this, TAttribute<FSlateColor>::Create(
		[this]() {
		return isTest.Get() ? FSlateColor(FColor(200, 0, 0)) : FSlateColor(FColor(0, 200, 0));
	}
	));
}

After these additional changes, when i make changes to the properties in the editor, they update immediately. I don’t know if it’s ‘correct’, but it functions the same as the other widgets and doesn’t require manually updating the widget tree via editor only functions.