Download

How to set a slate attribute dynamically

Hello, I have made a custom slate widget. It has a TAttribute which I want to set each time I create the slate widget. Something like this


    class SURVIVALGAME_API SItemWidget : public SCompoundWidget
    {
    public:
    	SLATE_BEGIN_ARGS(SItemWidget)
    		:_GameHUD()
    	{}
    	SLATE_ARGUMENT(TWeakObjectPtr<class ATestHUD>, GameHUD)
    	SLATE_ATTRIBUTE(int32, Test)
    
    	SLATE_END_ARGS()
    
    public:
    	/** Constructs this widget with InArgs */
    	void Construct(const FArguments& InArgs);
    
    private:
    	TWeakObjectPtr<class ATestHUD> GameHUD;
    
    	TAttribute<int32> Test;
    
    	TWeakObjectPtr<class ATestHUD> ParentInventoryWidget;
    };

I create this slate widget inside another slate widget something like this


    	TSharedPtr<SVerticalBox> Col;
    	SAssignNew(Col, SVerticalBox);
    	for (int32 i = 0; i < 4; i++)
    	{
    		Col->AddSlot()
    		.HAlign(HAlign_Fill)
    		.VAlign(VAlign_Fill)
    		
    			SNew(SItemWidget)
    			.GameHUD(GameHUD)
    			.Test(i)
    		];
    	}

But when I access the slate values all values appear to be zeros



    	ChildSlot
    	
    		SNew(SBox)
    		.Padding(5)
    		.HeightOverride(100)
    		.WidthOverride(100)
    		
    			SNew(SBox)
    			.HAlign(HAlign_Center)
    			.VAlign(VAlign_Center)
    			
    				SNew(STextBlock)
    				.Text(FText::FromString(FString::FromInt(Test.Get())))
    			]
    		]
    	];

But this doesn’t work and all values appear to be zeroes.

Linked answerhub question How to set a slate attribute dynamically - UE4 AnswerHub

----------------------------------------Post answered I had just left a simple thing out.---------------------------------------

First up, a minor unrelated niggle: don’t pass things around as weak pointers. It’s a concept that only has meaning for storage, so by using it for parameters you’re only giving yourself more syntax overhead when you could just pass as pointer, store as weak pointer. (Unless you actually expect to be providing an invalid weak pointer, which I highly doubt is the case.)


SLATE_ARGUMENT(class ATestHUD*, GameHUD)

As for your second question from answerhub:

TAttributes (used in the underylying SLATE_ATTRIBUTE macro) provide a bit of extra functionality in order to pass a delegate instead of a value. Slate favours polling over invalidation and attributes are at the core of this. A quick example to illustrate:

Suppose you have a HP bar on your HUD. Instead of writing a Tick function for your HUD that pushes a new HP value each update, you could write the following polling function:


float ATestHUD::OnGetHPPercent() const
{
	if( PlayerOwner->GetPawn() )
	{
		return Cast<ATestCharacter>( PlayerOwner->GetPawn() )->GetHealth();
	}

	return 0.0f;
}

And then, when constructing your HUD widget:



	SNew( SProgressBar )
	.Percent( GameHUD, &ATestHUD::OnGetHPPercent )


The SLATE_ATTRIBUTE macro adds a big list of shortcuts to your attribute’s declarative syntax in order to support all delegate types without having to manually create your TAttribute and the delegate for it. The actual longhand syntax is rather redundant and unwieldy:



	SNew( SProgressBar )
	.Percent( TAttribute<float>::Create( TAttribute<float>::FGetter::CreateUObject( this, &AGlimpseHUD::OnGetHPPercent ) ) )


Unfortunately, this shortcut only applies for Slate declarative syntax. Sometimes you need to update an attribute after a widget is already constructed, in which case you’ll have to use the longhand form. Slots also sometimes accept attributes but don’t have the full set of shortcuts.

This segues into your second question:


And is it possible to change the value of test during run time by the use of a simple function?

Yes, you can create a setter for a TAttribute which will retain the delegate functionality. You just have to explicitly spell it out, as mentioned. There is an intermediate shortcut that doesn’t require going through the FGetter, but only for shared pointer delegates, Epic apparently didn’t feel like creating the same shortcut for other delegate types:



TAttribute<float> NewAttribute( this, &FSomeNonUObjectType::OnGetHPPercent );


For other delegate types, again, the longhand form will let you create any delegate type. Most likely you can just create your own shortcut in your setter. You can see the syntax used for it in Attribute.h and DeclarativeSyntaxSupport.h (look for INTERNAL_SLATE_DECL_ATTRIBUTE).

-Camille

Thanks for the reply I am slowly I am not used to this type of programming.

Most of the tutorials I followed were passing Weakpointers to the HUD class so I just copied it. I will change them.

So basically a Attribute can be directly blinded with a delegate without any Delegate Declaration.

Suppose my slate widget has 2 variables. And I got a function which takes these 2 values and returns result from an array…

Something similar to this



int32 SItemWidget::GetNumOfStacks(int32 RowNum, int32 ColNum)
{
	return GameHUD->StoragePtr->Storage.Rows[RowNum].Columns[ColNum].ItemIndex;
}

What should I do to call this?

The value of


StoragePtr->Storage.Rows[RowNum].Columns[ColNum].ItemIndex 

changes over time. How do I bind this so my slate updates dynamically?

Something like this wont work I think



			
				SNew(STextBlock)
				.Text(FText::FromString(FString::FromInt(GetNumOfStacks(SlotLoc.Get().RowNum, SlotLoc.Get().ColNum))))
			]

It’s not really a problem, mostly a pet peeve of mine. I had to revisit some code from an intern who wasn’t clear on weak pointers vs. regular pointers, and constantly had to spell out TWeakObjectPtr<UMyClass>( SomePointer ) when passing pointers around, which got old fast. Fixing it was fairly tedious because I had to rename lots of references around the code, remove explicit weak pointer construction, remove .Gets, etc. It’s just the minor sort of thing that saves you from typing out redundant code in the long run.

The SLATE_ATTRIBUTE syntax gives you shortcuts to avoid explicitly declaring the delegate, but you do need a function matching the delegate, yes. It’s very straightforward when your function is a simple parameter-less getter, but that’s obviously not always the case.

One of the great features of UE4’s delegates is the ability to bind payload variables. A payload variable, in short, is a variable that is not part of the delegate’s signature but is stored at bind time in order to tack on to the delegate being called. Here’s what the documentation in Delegate.h has to say about it:



 *	You can assign "payload data" to your delegates!  These are arbitrary variables that will be passed
 *  directly to any bound function when it is invoked.  This is really useful as it allows you to store
 *  parameters within the delegate it self at bind-time.  All delegate types (except for "dynamic") supports
 *	payload variables automatically!
 *
 *	When binding to a delegate, you can pass payload data along.  This example passes two custom variables,
 *	a bool and an int32 to a delegate.  Then when the delegate is invoked, these parameters will be passed
 *	to your bound function.  The extra variable arguments must always be accepted after the delegate
 *	type parameter arguments.
 *
 *			MyDelegate.BindStatic( &MyFunction, true, 20 );


Concretely, your situation is a perfect application of payload delegates. It looks like you have an inventory grid – when constructing each grid slot, you know the coordinates of that grid slot, and those coordinates won’t change later, even if the contents of the grid slot do change. Rather than create a separate function for each grid slot (yuck!), payload variables allow you to save those coordinates and pass them to your getter. The downside is figuring out the syntax for payload variables can be quite tricky and the compilation errors that come up from bad syntax are nigh incomprehensible. SLATE_ATTRIBUTE’s syntax thankfully also supports payloads out of the box.

Now, to get our hands dirty: you’re right in that the way you are setting your text attribute won’t update dynamically. It’ll basically call the function, do the conversion, and store the resulting value as a constant. In order to get the polling behaviour, you need to bind a delegate function, not call it directly.

STextBlock::Text is a SLATE_TEXT_ATTRIBUTE, which is basically a specialized attribute that returns a FText but also adds handy shortcuts to internally convert FStrings to the FText required for display screen. In other words, you need to bind a function that returns either a FString or a FText, but your function currently returns an int32. You’ll need to add an extra function* that does this conversion in order to bind it:


FString SItemWidget::GetNumOfStacksString(int32 RowNum, int32 ColNum)
{
	return FString::FromInt( GetNumOfStacks(RowNum, ColNum) );
}

This new function is the one you’ll want to bind to your text attribute. The Slate syntax for this becomes:


SNew( STextBlock )
.Text( this, &SItemWidget::GetNumOfStacksString, SlotLoc.Get().RowNum, SlotLoc.Get().ColNum )

The RowNum and ColNum tacked in the declaration here are the aforementioned payload variables. When constructing the text block and binding the attribute, Slate will save the value of RowNum and ColNum alongside the delegate internally. From then on, whenever the textblock is asked to display, it will call the getter with the saved coordinates, yielding the updated value you want.

I believe that should get you started, do let me know if anything is still unclear or not working.

  • Starting with UE4.6, you could actually bind a lambda function that does the conversion without having to declare an extra function. But lambdas add more syntax on top of the delegate syntax so for the sake of simplicity, I won’t go that route here. I’m just mentioning it because once you get familiar with binding slate attributes, using lambdas for this purpose is extremely handy.

I had tried something similar to this but it didn’t work.


				SNew(STextBlock)
				.Text(this, &SItemWidget::GetNumOfStacks, SlotLoc.Get().RowNum, SlotLoc.Get().ColNum)

And the function


FString SItemWidget::GetNumOfStacks(int32 RowNum, int32 ColNum)
{

I get a compiling error at the .Text line


C:\Users\Srikant\Documents\Unreal Projects\SurvivalGame\Source\SurvivalGame\Inventory\Slate\Widgets\SItemWidget.cpp(32): error C2664: 'STextBlock::FArguments::WidgetArgsType &STextBlock::FArguments::Text(const TAttribute<FText> &)' : cannot convert argument 2 from 'FString (__cdecl SItemWidget::* )(int32,int32)' to 'FString (__cdecl SItemWidget::* )(Var1Type,Var2Type) const'
1>          with
1>          
1>              Var1Type=int32
1>  ,            Var2Type=int32
1>          ]
1>          Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast

And there’s those lovely template syntax errors. This one’s fairly straightforward:


cannot convert argument 2 from 
'FString (__cdecl SItemWidget::* )(int32,int32)' 
to 
'FString (__cdecl SItemWidget::* )(Var1Type,Var2Type) **const**'
1>          with
1>          
1>              Var1Type=int32
1>  ,            Var2Type=int32
1>          ]

The function types match, but the function qualifiers don’t. The attribute wants a const function, but your getter isn’t. I just reproduced the example locally to make sure, and adding const to the getter fixes it:


FString TestGetString( int32 A, int32 B ) const;

I correct it just a second before… These errors are kind of annoying I couldn’t understand first what it was.

Thanks… :slight_smile:

Also one last question. Is storing as a weak pointer actually necessary for something like an inventory widget?

I am passing pointer to the HUD like this


		SAssignNew(MainInventoryUI, SInventoryWidget)
			.GameHUD(this);


void SInventoryWidget::Construct(const FArguments& InArgs)
{
	GameHUD = TWeakObjectPtr<ATestHUD>(InArgs._GameHUD);
}

Is this correct?

That’s a fairly open-ended design question with no one right answer, but in this case I would say yes.

I’m guessing your HUD actor creates and manages the inventory widget. You can safely say the HUD “owns” the widget and keeps a pointer to it. If the HUD gets destroyed, it should in turn destroy the widget. But the widget needs a reference to the HUD in order to poll some state or otherwise communicate, so you pass a pointer to it. If you store that as a hard pointer, then you have an ownership cycle where both items “own” each other, always have a reference count of at least 1 and therefore never get destroyed. By storing the HUD as a weak pointer, it doesn’t count in the reference count and you allow natural destruction of the widget in the HUD’s cleanup.

I personally don’t much like cycles like this and therefore try to make my widgets completely passive. In our inventory, every single inventory “item” is self-contained and has no awareness of who owns it. Then the inventory list itself is also self-contained with no idea what screen it’s displayed in. The owner of these widgets just tells them “here is where you will get your item icon”, “here is how to get the stack count”, etc. Using this approach let me easily add a mini inventory preview in a completely unrelated screen when we needed it.

And this is done through… Yep, Slate attributes. By giving each widget a comprehensive set of attributes, you can effectively eliminate the need for each widget to be aware of its parent. It requires a fair amount of discipline to avoid creating dependencies but it’s pretty satisfying when everything “clicks” together.

Yeah I feel that type of design would be slightly better.

I really got an awesome error message again…


1>C:\Users\Srikant\Documents\Unreal Projects\SurvivalGame\Source\SurvivalGame\Inventory\Slate\Widgets\SItemWidget.cpp(25): error C2664: 'SBorder::FArguments::WidgetArgsType &SBorder::FArguments::BorderImage(const TAttribute<const FSlateBrush *> &)' : cannot convert argument 2 from 'const FSlateBrush (__cdecl SItemWidget::* )(void) const' to 'const FSlateBrush (__cdecl SItemWidget::* )(void) const'
1>          Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast

What does this mean?

.h


	const FSlateBrush GetItemIcon() const;

.cpp



SNew(SBorder)
.HAlign(HAlign_Right)
.VAlign(VAlign_Bottom)
.BorderImage(this, &SItemWidget::GetItemIcon) // <- Error at this line
.Padding(FMargin(0,0,4.2,2))


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


const FSlateBrush SItemWidget::GetItemIcon() const
{
}

----------------Edit---------------

Solved with const FSlateBrush* SItemWidget::GetItemIcon() const
{
}

Yeah. Slate brushes are a bit peculiar to work with in that they are always passed around as pointers. They were at one point intended to be used as assets, but that’s been deprecated.

In any event, it means you have to store the brush somewhere in order to return a pointer to it. In our inventory’s case, since the icon is static, I just keep a Slate brush in the SInventoryItem widget, construct it using a UTexture2D argument and let the widget’s UImage use that constructed brush.

Ok another thing I want to ask for the SButton ButtonStyle, it cant be changed by polling right?

As it takes a FButtonStyle instead of a TAttribute<FButtonStyle>.

Correct.

The styles themselves are not meant to be changed by polling, but the contents of those styles can. Styles, like brushes, are usually stored by pointer, for instance SButton’s:


	/** Style resource for the button */
	const FButtonStyle* Style;

This means that you can update the values of the style and those changes will be reflected on the button.

This is a really great thread on slate and the parameters passed in at construction. Thanks for all the info dudes.

I was wondering what the design is for a passive system? How much it differs form the this example provided by envenger? How do widgets gain access to data if no one owns them and does not provide updated polling data for them? They poll themselves?