Using delegates with simple parameters in "C++" (U.E. Bloated and Nightmarish Script)

How to pass very simple parameters to Button function binders from “C++”. By simple parameters I really mean ANYTHING : the void* of the bound widget, an int32, or even a single byte.

Currently, between two evil macros overdoses, I create a single function for each binding. For instance, in the following example, one for each Clock speed buttons.

It only works for UI buttons that can be enumerated beforehand, and for UI with a dynamic count I have to create a dedicated class only to cache the parameter I want to be passed, which is syntax heavy to say the least.

void UW_Clock::NativeConstruct() {

	Super::NativeConstruct();

	CheckBoxStop->OnCheckStateChanged.AddUniqueDynamic(this, &UW_Clock::HandleOnClockStopped);
	CheckBoxStart->OnCheckStateChanged.AddUniqueDynamic(this, &UW_Clock::HandleOnClockStarted);
	CheckBoxSpeed1->OnCheckStateChanged.AddUniqueDynamic(this, &UW_Clock::HandleOnSpeedChanged_1);
	CheckBoxSpeed2->OnCheckStateChanged.AddUniqueDynamic(this, &UW_Clock::HandleOnSpeedChanged_2);
	CheckBoxSpeed3->OnCheckStateChanged.AddUniqueDynamic(this, &UW_Clock::HandleOnSpeedChanged_3);
	CheckBoxSpeed4->OnCheckStateChanged.AddUniqueDynamic(this, &UW_Clock::HandleOnSpeedChanged_4);
	CheckBoxSpeed5->OnCheckStateChanged.AddUniqueDynamic(this, &UW_Clock::HandleOnSpeedChanged_5);
}

void UW_Clock::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) {

	Super::NativeTick(MyGeometry, InDeltaTime);

	static FDateTime& CurrentDateTime = USSTClock::CurrentDateTime;

	FString date = FString::Printf(TEXT("%d/%d/%d"), CurrentDateTime.GetYear(), CurrentDateTime.GetMonth(), CurrentDateTime.GetDay());
	float day_progress = float(CurrentDateTime.GetHour()) / 24.0f;

	TextBlockDate->SetText(FText::FromString(date));
	ProgressBarDay->SetPercent(day_progress);

	static int& Speed = USSTClock::Speed;

	CheckBoxStop->SetIsChecked(!USSTClock::IsRunning);
	CheckBoxStart->SetIsChecked(USSTClock::IsRunning);
	CheckBoxSpeed1->SetIsChecked((Speed == 1));
	CheckBoxSpeed2->SetIsChecked((Speed == 2));
	CheckBoxSpeed3->SetIsChecked((Speed == 4));
	CheckBoxSpeed4->SetIsChecked((Speed == 8));
	CheckBoxSpeed5->SetIsChecked((Speed == 16));
}

void UW_Clock::HandleOnClockStopped(bool bIsChecked) { USSTClock::IsRunning = !bIsChecked; }
void UW_Clock::HandleOnClockStarted(bool bIsChecked) { USSTClock::IsRunning = bIsChecked; }
void UW_Clock::HandleOnSpeedChanged_1(bool bIsChecked) { if (bIsChecked) { USSTClock::Speed = 1; } }
void UW_Clock::HandleOnSpeedChanged_2(bool bIsChecked) { if (bIsChecked) USSTClock::Speed = 2; }
void UW_Clock::HandleOnSpeedChanged_3(bool bIsChecked) { if (bIsChecked) USSTClock::Speed = 4; }
void UW_Clock::HandleOnSpeedChanged_4(bool bIsChecked) { if (bIsChecked) USSTClock::Speed = 8; }
void UW_Clock::HandleOnSpeedChanged_5(bool bIsChecked) { if (bIsChecked) USSTClock::Speed = 16; }

So I found the solution to that very, very niche problem of not being able to pass any parameter with delegates in a custom UUserWidget class. It’s not pretty (be warned) but it works and might avoid rewriting everything into Slate when so much is already written in UMG, for now.

So because apparently us dums dums aren’t to access the underlying SButton of a UButton at any cost, the SButton* MyButton property is “protected.” You can unprotect it and enjoy lengthy recompilations or call your old friend C to the rescue. But he doesn’t come. Because pointers to members are C++, and unsafe casting-deferencing to pointers to members isn’t allowed.

A pointer to member is a 32 bit integer. Knowing that the SButton* is in the last row of UButton’s binary layout, you can write in a namespace (be warned again) :

inline SButton* UButton::* UButton_internal = []() { SButton* UButton::* ptr; uint32 INCREM = sizeof(UButton) - alignof(UButton); memcpy(&ptr, &INCREM, 4); return ptr; }();

Then you can use it to access the SButton with the syntax :

(Button->*MyNamespace::UButton_internal)->DoStuff();

Including passing parameters :

	for (uint8 i = 0; i < Buttons.Num(); i++) {
		FOnClicked onclicked; onclicked.BindLambda([this, i]() -> FReply { this->FunctionWithOneByteParameter(i); return FReply::Handled(); });
		(Buttons[i]->*MyNamespace::UButton_internal)->SetOnClicked(onclicked);
	}

It’s ugly but it works and it’s pretty much the only way to do it.