I’m working on a Slate menu at the moment, but I’ve got a weird problem where the Slate widget seems to clear all of its variables for a reason I don’t understand - almost like the widget is getting garbage collected.
I’ve pretty much got a CompoundWidget, and I use SAssignNew as such:
SAssignNew(ClassSelectionWidget, STDSClassSelectionWidget)
.Cursor(EMouseCursor::Default);
and then add it to the viewport:
GEngine->GameViewport->AddViewportWidgetContent(
SNew(SWeakWidget)
.PossiblyNullContent(ClassSelectionWidget.ToSharedRef())
);
This part works fine. I’ve also got a couple of my own Widgets for a class selection button, and a class information panel. The basic idea is when the class selection button is clicked, it calls back to the parent widget, and then that tells the class info panel to do some stuff.
I have the widgets built as such (with some surrounding code):
SNew(SOverlay)
+SOverlay::Slot()
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)
[
SAssignNew(ClassButtons, SVerticalBox)
]
+SOverlay::Slot()
.VAlign(VAlign_Center)
.HAlign(HAlign_Right)
[
SAssignNew(ClassProperties, SPlayerClassProperties)
.OwnerWidget(this)
]
However, whenever I click on one of my buttons and call a function on my Class Selection widget, the ClasssProperties and ClassButtons sharedpointers are null, and everything else I have in that class is null or empty.
Is there something going on in the background here that I should be accounting for?
Cheers!
Hey Matt,
We’ve asked a programmer to take a look at this issue.
Cheers!
Is your class set up something like this?
class SMyWidget : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS( SMyWidget ) {}
SLATE_ARGUMENT( TSharedPtr, OwnerWidget )
SLATE_END_ARGS()
void Construct( const FArguments& InArgs )
{
OwnerWidget = InArgs._OwnerWidget;
}
void SetSelectedClass( InClass )
{
OwnerWidget->SetSelectedClass( InClass );
}
private:
TSharedPtr OwnerWidget;
};
It seems a bit strange how you are passing in owner widget:
SAssignNew(ClassProperties, SPlayerClassProperties)
.OwnerWidget(this)
We usually use shared pointers to pass around widgets and it looks like you are passing it as a raw pointer
Yeah, thats how I’m passing it in. Whats the correct way to do it?
I’d test another fix (passing in the OwnerHUD and calling through the widget in there) but the startup crash is causing me headaches at the moment.
Cheers
Unless you can provide a bit more info, I don’t know what else could be causing your crash if you are setting up your widget the way I indicated. At some point you end up with null widgets so I would put some breakpoints in the destructors of all your widgets that end up being null to see if they are ever destroyed. I would also step through the code to make sure they are getting initialized and are not reset anywhere else.
The only thing I can think of after looking at your code is that your ClassSelectionWidget object is being destroyed.
Where do you store that widget after it is created?
SNew(SWeakWidget).PossiblyNullContent(ClassSelectionWidget.ToSharedRef())
This stores a weak pointer to your widget so it doesn’t prevent destruction. You need to store it somewhere else or else it is going to be destroyed when it leaves the function it was created from.
A good place to find out where it is destroyed is just to put a breakpoint in the destructor of what is being destroyed. Widgets aren’t garbage collected so it can only be destroyed when its reference count goes to zero.
Ok, so the destructor isn’t getting called until I close my menu or the game, which sounds right.
That makes me think its got something to do with how I’m passing the widget reference around, but I’m not sure of how I could be doing it wrong.
I’m setting the OwningWidget as such in the constructor of the ClassSelectionButton and ClassProperties:
OwnerWidget = InArgs._OwnerWidget;
To pass my class selection back to the owning widget, I’m doing
OwnerWidget.Get()->SetSelectedClass(PlayerClass.Get());
OwnerWidget is not null here, but if I examine the class members after the .Get() they’re all null. So I’m pretty sure I’m missing something here but not sure what.
A bit of a wild guess, but instead of
.OwnerWidget(this)
try
.OwnerWidget(TSharedPtr<SYourUIWidgetType, ESPMode::ThreadSafe>(this));
Also, garbage collection and destruction are two very different things. Garrbage collectors generally don’t use destructors to free memory, although the UE4 one might.
The issue seems to be that your OBJECT is getting garbage collected while your pointer itself remains in tact. In what scope is your ClassSelectionWidget pointer created? This needs to be done in your class, creating the pointer in the function will let the object it points to be garbage collected when the function goes out of scope (as no references to it are left on the stack).
That would potentially create two distinct shared pointers to the same object, and you’re in for a bad time if you do that as one will end up trying to access/delete a deleted object.
The correct way to convert ‘this’ to a shared pointer is to derive from TSharedFromThis (or another class that already does; all Slate widgets already derive from TSharedFromThis - see SWidget) and then use AsShared() rather than ‘this’.