Issues with window sizes on multiple monitors

Hello,

We are utilizing a unique hardware setup that involves multiple monitors of varying resolutions. Our Unreal application is configured to spawn additional Slate windows on these monitors based on hard-coded positions and sizes, but we are encountering some issues with the windows spawning with the wrong size.

When attempting to create extra Slate windows on an additional monitor we pass in a vector position and window size (in pixels) into our C++ function (the primary code snippet can be found in the reproduction steps). Logging shows these numbers are correct, but when we use the SAssignNew or SWindow->ReshapeWindow functions, the size of the spawned window is wrong in certain cases. Specifically, when the “previous” window was on a monitor with a different resolution.

Example:

- Two monitors with different resolutions

- First window (could be the editor or the original window from running the exe) on monitor 1

- Second window is spawned on monitor 2, it will be sized incorrectly

- Third window is spawned on monitor 2, this one will be sized correctly

- Fourth window is spawned on monitor 1, this one will be sized incorrectly

I’ve produced this behavior in a new project with just the C++ spawning function, and the behavior consistently mimicked that example. It seems like an off by one error, with the engine utilizing the previous window’s monitor DPI, etc to determine/adjust window size.

In our particular use case we don’t care about monitor resolution or DPI since our window sizes are hard-coded, so if there is a way to simply spawn the window at the provided size that would be preferred. I’ve fiddled around with different parameters on the SAssignNew function, but I am open to suggestions for adjusting that as well if it can solve the issue.

Thanks

Steps to Reproduce

I was able to recreate the issue in a new 5.4 project with the following steps:

  • Connect an additional monitor with a different resolution than the main one
  • Create a C++ function with the below code to spawn an additional slate window
    • The class and name of the function can be whatever, but make sure the function accepts two vectors, one for window size and one for window position
  • Hard-code a vector size and pixel position for the window to be spawned
    • You can use any method to determine what pixel coordinates would place the window’s upper left corner on the second monitor, I personally used a screen capture and a photo editor. Any position and size will do so long as it’s fully on the second monitor.
  • Create a simple BP call to that C++ function, passing in the previously determined size and screen position. Hook that call into a key press, etc so it can be used in Play mode
  • Run the editor on the primary (first) monitor
  • Spawn the new window on the second monitor. The second window should spawn in at the correct position, but with the incorrect size.
  • The log prints in the C++ function can further detail how the size changed between steps

*Note: I’ve omitted the code related to the viewport since I didn’t think it was relevant, but I can provide the full C++ function if requested.

`//ExtraWindow is just a TSharedPtr variable initialized to nullptr
//InitialSizeInScreen and InitialPositionInScreen are function inputs

UE_LOG(LogTemp, Warning, TEXT(“Size before SAssignNew: %s”), *InitialSizeInScreen.ToString()); //this size is correct
SAssignNew(ExtraWindow, SWindow)
.SizingRule(ESizingRule::UserSized)
.UserResizeBorder(FMargin(3.0f, 3.0f))
.Cursor(EMouseCursor::Default)
.bDragAnywhere(true)
.SaneWindowPlacement(true)
.LayoutBorder(FMargin(3.0f, 3.0f))
.Type(EWindowType::GameWindow)
.Title(WindowTitle)
.CreateTitleBar(false)
.UseOSWindowBorder(true)
.FocusWhenFirstShown(false)
.ClientSize(InitialSizeInScreen)
.ScreenPosition(InitialPositionInScreen);
UE_LOG(LogTemp, Warning, TEXT(“Size after SAssignNew: %s”), *ExtraWindow->GetSizeInScreen().ToString()); //this size is not`

Hi Garrett,

The quickest way to verify what is going wrong here may be to add some logging in SWindow::Construct, there are a few moments where we adjust the incoming window size:

`const FVector2f DPIScaledClientSize = InArgs._AdjustInitialSizeAndPositionForDPIScale ? InArgs._ClientSize * DPIScale: FVector2f(InArgs._ClientSize);
FVector2f WindowSize = GetWindowSizeFromClientSize(DPIScaledClientSize, DPIScale);

if (InArgs._SaneWindowPlacement)
{
// Clamp window size to be no greater than the work area size
WindowSize.X = FMath::Min(WindowSize.X, AutoCenterRect.GetSize().X);
WindowSize.Y = FMath::Min(WindowSize.Y, AutoCenterRect.GetSize().Y);
}`It may be interesting to check if the two monitors have different DPI scaling settings in Windows, though I suspect the underlying issue here is that we’re using the wrong AutoCenterRect (which is used both for DPI scale calculation and for the SaneWindowPlacement adjustment). The default for AutoCenter is EAutoCenter::PreferredWorkArea, which is going to attempt to find the monitor that is currently focused in some way. Since you’re already explicitly setting the window size/position to hardcoded values and not relying on any of the auto-adjustments, I suspect that adding .AutoCenter(EAutoCenter::None) to your arguments might fix the issue you’re seeing. Let me know if that doesn’t work, and I can see about getting hardware to reproduce this.

Best,

Cody