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.