User-Sized Slate Window doesn't Resize properly

I have a Modal SWindow that our users need to be able to resize. Also, the window will need to resize itself because users can add rows to the SListView inside the window.

Here is the SWindow definition:

void BuildMainEditableWidget()
{
	RowWindow = SNew(SWindow)
		.AutoCenter(EAutoCenter::None)
		.Title(FText::FromString(TEXT("Add New Lines")))
		.IsInitiallyMaximized(false)
		.ScreenPosition(FVector2D(100, 100))
		.CreateTitleBar(true)		
		.SizingRule(ESizingRule::Autosized)
		[
			BuildEditableBox()			
		];
 
 
	// Using the size that UE figures out, by first setting the Sizing rule to "Autosized"	
	RowWindow->SlatePrepass();
	RowWindow->SlatePrepass();

	NewDesiredSize = RowWindow->GetDesiredSize();

	// Widget that is defined below
	SelectedRowsBox->SlatePrepass();
	SelectedRowsBox->SlatePrepass();
 
	float YOffset = AddedRowsArray.Num() > 1 ? ((SelectedRowsBox->GetDesiredSize().Y * 0.55f) * AddedRowsArray.Num()) : 0.f;
	NewDesiredSize.Y += YOffset;

	// Now we set the window to be "UserSized"
	RowWindow = SNew(SWindow)
		.AutoCenter(EAutoCenter::None)
		.Title(FText::FromString(TEXT("Add New Lines")))
		.ScreenPosition(FVector2D(100, 100))
		.ClientSize(NewDesiredSize)
		.CreateTitleBar(true)
		.SizingRule(ESizingRule::UserSized)
		[
			BuildEditableBox()
		];

	TSharedPtr<SWindow> RootWindow = FGlobalTabmanager::Get()->GetRootWindow();
	FSlateApplication::Get().AddModalWindow(RowWindow.ToSharedRef(), RootWindow, IsSlowTask);
}

Here’s BuildEditableBox()

TSharedRef<SWidget> BuildEditableBox()
{
	MainEditableBox = SNew(SBox)
	[
		SNew(SVerticalBox)
			+ SVerticalBox::Slot()
			.VAlign(VAlign_Top)
			.AutoHeight()
			[
				SNew(SBorder)
					.Padding(10)
					.Content()
					[
						SNew(SButton).ButtonStyle(&ButtonStyle)
							.Text(FText::FromString("Add New Line"))
							.OnClicked(FOnClicked::CreateRaw(this, &SLAMSDataEntryWidget::OnAddNewRowButtonPressed))
					]
			]
			+ SVerticalBox::Slot()
			.VAlign(VAlign_Top)
			.AutoHeight()
			[
				BuildSelectedRowsWidget()
			]
	];
	
	return MainEditableBox.ToSharedRef();	
}

Finally, the SListView:

TSharedRef<SWidget> BuildSelectedRowsWidget()
{
	float DPI_Scale = -1;
	FVector2D viewportSize(0, 0);
	if (GEditor && MainPluginTab)
	{
		FIntPoint SizeXY;
		FViewport* Viewport = GEditor->GetActiveViewport();
		SizeXY = Viewport ? Viewport->GetSizeXY() : FIntPoint(2000,1000);
		viewportSize = FVector2D(SizeXY.X, SizeXY.Y);
		int32 X = FGenericPlatformMath::FloorToInt((float)SizeXY.X);
		int32 Y = FGenericPlatformMath::FloorToInt((float)SizeXY.Y);
		DPI_Scale = GetDefault<UUserInterfaceSettings>(UUserInterfaceSettings::StaticClass())->GetDPIScaleBasedOnSize(FIntPoint(X, Y));
	}


	AdjustedSize = (1 / DPI_Scale) * viewportSize;
	AdjustedSize.X *= 0.90f;
	float ActualWidth = AdjustedSize.X;
	SelectedRowsBox = 
	SNew(SBox)
	.WidthOverride(ActualWidth)
	[
		SNew(SBorder)
			.Padding(10)
			.Content()
			[
				SNew(SVerticalBox)
					+ SVerticalBox::Slot()
					.VAlign(VAlign_Top)
					.AutoHeight()
					[
						SAssignNew(LamsAddedRowsWidget, SListView<TSharedPtr<FLAMSWidgetRowData>>)
							.ItemHeight(20)
							.ScrollBarStyle(&ScrollStyle)
							.ListViewStyle(&TableViewStyle)
							.ListItemsSource(&AddedRowsArray)
							.HeaderRow(
								SNew(SHeaderRow)
								+ SHeaderRow::Column("Select").DefaultLabel(NSLOCTEXT("Data", "SelectColumn", "Select"))					.FillWidth((ActualWidth / 10.f)/ActualWidth)					
								+ SHeaderRow::Column("Direction").DefaultLabel(NSLOCTEXT("Data", "DirectionColumn", "Direction"))				.FillWidth((ActualWidth / 5.f)/ActualWidth)		
								+ SHeaderRow::Column("Location").DefaultLabel(NSLOCTEXT("Data", "LocationColumn", "Location"))					.FillWidth((ActualWidth / 5.f)/ActualWidth)		
								+ SHeaderRow::Column("Text").DefaultLabel(NSLOCTEXT("Data", "TextColumn", "Text"))						.FillWidth((ActualWidth / 2.f )/ActualWidth)		
								+ SHeaderRow::Column("Remove").DefaultLabel(NSLOCTEXT("Data", "RemoveColumn", "Remove"))					.FillWidth((ActualWidth / 10.f)/ActualWidth))		
					]
			]
	];
	return SelectedRowsBox.ToSharedRef();
}

Steps to Reproduce
I have been trying for months to make an SWindow that resizes when the internal content is changed, while still allowing users to resize the window themselves.

At this point, I need some actual code examples to help me through this problem. I cannot spend another week on this problem, as our customers really need this functionality to work properly and I cannot find any documentation or code examples (within the engine) to guide me.

Given the widget setup, here is where things are failing for me - calling Resize() on the SWindow does not properly scale the size of the window.

// Now, each time a user adds a row, we want to resize the SWindow height, keeping the width and position that a user may modified
FReply OnAddNewRowButtonPressed()
{
	if (RowWindow)
	{
		CurrPos = RowWindow->GetPositionInScreen();
 
		IsResizing = true;
		CurrPos = RowWindow->GetPositionInScreen();
		RowWindow = SNew(SWindow)
			.AutoCenter(EAutoCenter::None)
			.Title(FText::FromString(TEXT("Add New Lines")))
			.IsInitiallyMaximized(false)
			.ScreenPosition(FVector2D(100, 100))
			.CreateTitleBar(true)
			.SizingRule(ESizingRule::Autosized)
			.SupportsMaximize(false)
			.SupportsMinimize(true)
			.HasCloseButton(true)
			.Style(&WindowStyle)
			[
				BuildEditableBox()
			];
		RowWindow->SlatePrepass();
		RowWindow->SlatePrepass();
		FVector2D NewDesiredSize = RowWindow->GetDesiredSize();
		float YOffset = AddedRowsArray.Num() > 1 ? ((NewDesiredSize.Y * 0.20f) * AddedRowsArray.Num()) : 0.f;
		NewDesiredSize.Y += YOffset;
		RowWindow =
			SNew(SWindow)
			.AutoCenter(EAutoCenter::None)
			.Title(FText::FromString(TEXT("Add New Lines")))
			.IsInitiallyMaximized(false)
			.ScreenPosition(FVector2D(100, 100))
			.ClientSize(NewDesiredSize)
			.CreateTitleBar(true)
			.SizingRule(ESizingRule::UserSized)
			.SupportsMaximize(false)
			.SupportsMinimize(true)
			.HasCloseButton(true)
			.Style(&WindowStyle)
			[
				BuildEditableBox()
			];
 
		// This does not work. The window size is just not correct. 
		// No matter how many different ways I try to resize the window, it just won't work :(	
		RowWindow->ReshapeWindow(CurrPos, NewDesiredSize);
		// RowWindow->Resize(NewDesiredSize); 							// also does not work
		// FSlateApplication::Get().ForceRedrawWindow(RowWindow.ToSharedRef());			// makes no difference
	}

	return FReply::Handled();
}

Please let me know if you need any more information! Thank you.

[mention removed]​ - ahhh thank you so much, this is what I was missing!!

Hi,

If I’m understanding your setup properly, it seems like you’re creating two new SWindows when a new row is added but not doing anything with the result. The call to AddModalWindow is the moment where a given SWindow instance is added as a modal window in the editor, but changing the underlying RowWindow reference won’t cause that to update. With this setup you’d need to close the old SWindow and open the newly created/resized SWindow in its place, though I imagine that’s not quite what you’re aiming for.

If the goal is to re-evaluate the autosizing at the moment new rows are added but keep control in the user’s hands for resizing, you could do some work to calculate the desired size of the contents of the window and add some additional padding for the window border and title bar, but I think an easier approach might be to just update the sizing rule on the existing SWindow. Essentially, you should only ever call SNew(SWindow) one time, then use SetSizingRule to toggle it between Autosized and UserSized.

The caveat is that we need a frame after enabling autosize for the window to actually resize, so we need to delay one frame and then switch the sizing rule back so that the window has time to update. Normally for this you’d use FTSTicker, but normal tickers are blocked by modal windows so we’ll have to do something slightly different:

MySlateWindowPtr->SetSizingRule(ESizingRule::Autosized);
ModalTickHandle = FSlateApplication::Get().GetOnModalLoopTickEvent().AddLambda([this](float DeltaTime)
{
	MySlateWindowPtr->SetSizingRule(ESizingRule::UserSized);
	if (ModalTickHandle.IsValid())
	{
		FSlateApplication::Get().GetOnModalLoopTickEvent().Remove(ModalTickHandle);
	}
});

This seems to be working well for me, I cleaned up my test code and I’ll share it below in case a full example would be helpful. I based this off of the Editor Standalone Window plugin template, swapping out the tab creation for a full SWindow creation. Let me know if you have any issues!

Best,

Cody

void  FMySlateWindowModule::CreateWindow()
{
	MySlateWindowPtr = SNew(SWindow)
		.AutoCenter(EAutoCenter::None)
		.Title(LOCTEXT("WindowTitle", "My Window"))
		.ScreenPosition(FVector2D(100, 100))
		.ClientSize(FVector2D(500, 500))
		.CreateTitleBar(true)
		.SizingRule(ESizingRule::UserSized)
		[
			SAssignNew(MyContainer, SVerticalBox)
				+ SVerticalBox::Slot()
				.HAlign(HAlign_Center)
				.VAlign(VAlign_Center)
				[
					SNew(SButton)
					.OnClicked_Lambda([this]()
					{
						for (int32 i = 0; i < 25; i++)
						{
							MyContainer->AddSlot()
							.Padding(5, 5)
							.HAlign(HAlign_Center)
							.VAlign(VAlign_Center)
							[
								SNew(STextBlock)
								.Text(LOCTEXT("ContentText", "This is some content!"))
							];
						}
 
						MySlateWindowPtr->SetSizingRule(ESizingRule::Autosized);
						ModalTickHandle = FSlateApplication::Get().GetOnModalLoopTickEvent().AddLambda([this](float DeltaTime)
						{
							MySlateWindowPtr->SetSizingRule(ESizingRule::UserSized);
							if (ModalTickHandle.IsValid())
							{
								FSlateApplication::Get().GetOnModalLoopTickEvent().Remove(ModalTickHandle);
							}
						});
 
						return FReply::Handled();
 
					})
 
					[
						SNew(STextBlock)
						.Text(LOCTEXT("AddContentButton", "Add Content"))
					]
 
				]
		];
}
 
void FMySlateWindowModule::PluginButtonClicked()
{
	CreateWindow();
	TSharedPtr<SWindow> RootWindow = FGlobalTabmanager::Get()->GetRootWindow();
	FSlateApplication::Get().AddModalWindow(MySlateWindowPtr.ToSharedRef(), RootWindow, false);
}