Updating Slate UI Layout On Click (Plugin code)

Hi everyone,

I’ve been trying to implement the UI for a plugin I’m making using Slate. However, I haven’t been able to figure out how I can change the layout itself when a button is clicked. Since the button click should change the layout itself, I imagine there is a way to redraw the UI on demand, but I don’t know how to do it.

In OnSpawnPluginTab, I bind a lambda expression to my button click. The expression simply turns a boolean flag to true (initially false). SpawnFLVBox() is the layout segment that I want to change. The code for it follows.

OnSpawnPluginTab:

TSharedRef<SDockTab> FPickActsModule::OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs)
{
    return SNew(SDockTab)
        .TabRole(ETabRole::NomadTab)
        [
            SNew(SVerticalBox)
            + SVerticalBox::Slot()
            .HAlign(HAlign_Center)
            .VAlign(VAlign_Center)
            .AutoHeight()
            [
                SNew(STextBlock)
                .Text(WidgetText)
            ]
            + SVerticalBox::Slot()
            .HAlign(HAlign_Fill)
            .VAlign(VAlign_Top)
            [
                SNew(SButton)
                .OnClicked_Lambda([this]()->FReply { _testFlag = true; return FReply::Handled(); })
            ]
            + SVerticalBox::Slot()
            .HAlign(HAlign_Fill)
            .VAlign(VAlign_Top)
            [
                SpawnFLVBox()
            ]
        ];
}

SpawnFLVBox:

TSharedRef<SHorizontalBox> FPickActsModule::SpawnFLVBox()
{
    TSharedRef<SHorizontalBox> horizontalBox = _testFlag ?
        // if true
        SNew(SHorizontalBox)
        + SHorizontalBox::Slot()
        [
            SNew(SEditableTextBox)
            .HintText(FText::FromString(TEXT("0")))
        ]
        + SHorizontalBox::Slot()
        [
            SNew(SEditableTextBox)
            .HintText(FText::FromString(TEXT("0")))
        ]:
        // if false
        SNew(SHorizontalBox)
        + SHorizontalBox::Slot()
        [
            SNew(SEditableTextBox)
            .HintText(FText::FromString(TEXT("0")))
        ]
        + SHorizontalBox::Slot()
        [
            SNew(SEditableTextBox)
            .HintText(FText::FromString(TEXT("0")))
        ]
        + SHorizontalBox::Slot()
        [
            SNew(SEditableTextBox)
            .HintText(FText::FromString(TEXT("0")))
        ];

    return horizontalBox;
}

We can see in SpawnFLVBox that I simply check the flag, and draw 2 editable text boxes instead of 3 if the flag is true. However, when I click the button, the layout is not re-drawn, which makes sense because OnSpawnPluginTab is only called at the beginning. So my question is: how do I request a UI re-draw? How do I refresh the view? I seem to understand that Slate doesn’t work by updating the UI every frame. But then I don’t know what to do to solve my problem. Any help would be greatly appreciated.

Two options:

  1. Wrap the contents of your slots in an SBox and bind its visibility to something that returns EVisibility::Collapsed or EVisibility::Visible depending on the state of your bool.
  2. Keep your horizontalBox variable around, and ClearChildren and AddSlot for the children you need when the button is pressed.

I don’t know. I just GEngine->AddOnScreenDebugMessage to print hint texts. Have you figured it out?

Jamie_Dale got the answer. Here are the example codes.

Method 1

// PickActsModule.h
class FPickActsModule
{
...
EVisibility TestVisible() const { return _testFlag; };
EVisibility _testFlag = EVisibility::Visible; // false
...
}

// PickActsModule.cpp
    ...
    + SVerticalBox::Slot()
    .HAlign(HAlign_Fill)
    .VAlign(VAlign_Top)
    [
        SNew(SButton)
        .OnClicked_Lambda([this]()->FReply {
            _testFlag = EVisibility::Collapsed; // true
            return FReply::Handled(); 
        })
    ]
    + SVerticalBox::Slot()
    .HAlign(HAlign_Fill)
    .VAlign(VAlign_Top)
    [
        SNew(SHorizontalBox)
        + SHorizontalBox::Slot()
        [
            SNew(SEditableTextBox)
            .HintText(FText::FromString(TEXT("0")))
        ]
        + SHorizontalBox::Slot()
        [
            SNew(SEditableTextBox)
            .HintText(FText::FromString(TEXT("0")))
        ]
        + SHorizontalBox::Slot()
        [
            // False extra.
            SNew(SEditableTextBox)
            .HintText(FText::FromString(TEXT("0")))
            .Visible_Raw(this, &FPickActsModule::TestVisible)
        ]
    ]

Method 2

// PickActsModule.h
class FPickActsModule
{
...
TSharedPtr<SHorizontalBox> TrueBox;
TSharedPtr<SHorizontalBox> FalseBox;
TSharedPtr<SHorizontalBox> FLVBox; // Container of TrueBox & FalseBox
...
}

// PickActsModule.cpp
// Somewhere to initialize.
TrueBox = SNew(SHorizontalBox)
        + SHorizontalBox::Slot()
        [
            SNew(SEditableTextBox)
            .HintText(FText::FromString(TEXT("0")))
        ]
        + SHorizontalBox::Slot()
        [
            SNew(SEditableTextBox)
            .HintText(FText::FromString(TEXT("0")))
        ];

FalseBox = SNew(SHorizontalBox)
        + SHorizontalBox::Slot()
        [
            SNew(SEditableTextBox)
            .HintText(FText::FromString(TEXT("0")))
        ]
        + SHorizontalBox::Slot()
        [
            SNew(SEditableTextBox)
            .HintText(FText::FromString(TEXT("0")))
        ]
        + SHorizontalBox::Slot()
        [
            SNew(SEditableTextBox)
            .HintText(FText::FromString(TEXT("0")))
        ];

FLVBox = SNew(SHorizontalBox);

// Dynamically changes child slots.
    ...
    + SVerticalBox::Slot()
    .HAlign(HAlign_Fill)
    .VAlign(VAlign_Top)
    [
        SNew(SButton)
        .OnClicked_Lambda([this]()->FReply {
            _testFlag = true;
            FLVBox->ClearChildren();
            FLVBox->AddSlot()[TrueBox];
            return FReply::Handled(); 
        })
    ]
    + SVerticalBox::Slot()
    .HAlign(HAlign_Fill)
    .VAlign(VAlign_Top)
    [
         FLVBox.ToSharedRef() // Make sure container is on screen.
    ]

Personally, I prefer using Method 2. Because you can do some stuff like

int MaxTextBoxes = _testFlag ? 2 : 3;
for (int i = 0; i < MaxTextBoxes; ++i)
{
     FLVBox->AddSlot()[EditableTextBox];
}