Skip to content

Commit

Permalink
Fixing scaling issue with initializing Form on non-primary monitor wi…
Browse files Browse the repository at this point in the history
…th different DPI settings. (#5557)

* Fixing scaling issue with initializing Form on non-primary monitor with different DPI settings.

In this case, Form Handle is created according to the Secondary monitor DPI hence, doesn't receive `DPI_CHANGED` message.
Fix is making sure we scale the Form according to DPI when Handle is created.

Fixes #4854 and related issues.
  • Loading branch information
dreddy-work committed Sep 7, 2021
1 parent 9b1c202 commit 9b77d91
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -868,7 +868,7 @@ protected override void OnFontChanged(EventArgs e)
// to scale in Dpi mode (during WM_DPICHANGED event).
// This may require scaling/relayout of the form. AutoScaleFactor will take
// AutoScaleMode into account while scaling the controls.
if (AutoScaleMode != AutoScaleMode.None)
if (AutoScaleMode != AutoScaleMode.None && IsHandleCreated)
{
_currentAutoScaleDimensions = SizeF.Empty;

Expand Down
182 changes: 106 additions & 76 deletions src/System.Windows.Forms/src/System/Windows/Forms/Control.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2094,40 +2094,7 @@ public virtual Font Font
[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ActiveXFontMarshaler))]
get
{
// if application is in PermonitorV2 mode and font is scaled when moved between monitors.
// ToDo: need to work on getting Font serialization at design time right.
if (ScaledControlFont is not null)
{
return ScaledControlFont;
}

if (TryGetExplicitlySetFont(out Font font))
{
return font;
}

font = GetParentFont();
if (font is not null)
{
return font;
}

if (IsActiveX)
{
font = ActiveXAmbientFont;
if (font is not null)
{
return font;
}
}

AmbientProperties ambient = AmbientPropertiesService;
if (ambient is not null && ambient.Font is not null)
{
return ambient.Font;
}

return DefaultFont;
return GetCurrentFontAndDpi(out _);
}

[param: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ActiveXFontMarshaler))]
Expand Down Expand Up @@ -2162,8 +2129,6 @@ public virtual Font Font
// Cleanup any font handle wrapper...
DisposeFontHandle();

ScaledControlFont = value;

if (Properties.ContainsInteger(s_fontHeightProperty))
{
Properties.SetInteger(s_fontHeightProperty, (value is null) ? -1 : value.Height);
Expand Down Expand Up @@ -2192,7 +2157,7 @@ internal Gdi32.HFONT FontHandle
{
get
{
// if application is in PermonitorV2 mode and font is scaled when application moved between monitor.
// if application is in PerMonitorV2 mode and font is scaled when application moved between monitor.
if (ScaledControlFont is not null)
{
if (_scaledFontWrapper is null)
Expand Down Expand Up @@ -2366,11 +2331,12 @@ public virtual Color ForeColor
remove => Events.RemoveHandler(s_foreColorEvent, value);
}

private Font GetParentFont()
private Font GetParentFont(out int fontDpi)
{
fontDpi = _deviceDpi;
if (ParentInternal is not null && ParentInternal.CanAccessProperties)
{
return ParentInternal.Font;
return ParentInternal.GetCurrentFontAndDpi(out fontDpi);
}
else
{
Expand Down Expand Up @@ -5790,7 +5756,7 @@ protected virtual Rectangle GetScaledBounds(Rectangle bounds, SizeF factor, Boun
RECT adornmentsAfterDpiChange = default;
CreateParams cp = CreateParams;

// We would need to get adornments metrics for both (old and new) Dpi in case application is in permonitor V2 mode and Dpi changed.
// We would need to get adornments metrics for both (old and new) Dpi in case application is in PerMonitorV2 mode and Dpi changed.
AdjustWindowRectExForControlDpi(ref adornmentsAfterDpiChange, cp.Style, bMenu: false, cp.ExStyle);

if (_oldDeviceDpi != _deviceDpi && OsVersion.IsWindows10_1703OrGreater)
Expand Down Expand Up @@ -5948,6 +5914,52 @@ internal virtual Control GetFirstChildControlInTabOrder(bool forward)
return found;
}

/// <summary>
/// Gets the control <see cref="Font"/>. If the font is inherited, traverse through the parent hierarchy and retreives the font.
/// </summary>
/// <param name="fontDpi">Dpi of the control for which <see cref="Font"/> is evaluated.</param>
/// <returns>
/// <para>The control's <see cref="Font"/></para>
/// </returns>
internal Font GetCurrentFontAndDpi(out int fontDpi)
{
fontDpi = _deviceDpi;

// If application is in PerMonitorV2 mode and font is scaled when moved between monitors.
if (ScaledControlFont is not null)
{
return ScaledControlFont;
}

if (TryGetExplicitlySetFont(out Font font))
{
return font;
}

font = GetParentFont(out fontDpi);
if (font is not null)
{
return font;
}

if (IsActiveX)
{
font = ActiveXAmbientFont;
if (font is not null)
{
return font;
}
}

AmbientProperties ambient = AmbientPropertiesService;
if (ambient is not null && ambient.Font is not null)
{
return ambient.Font;
}

return DefaultFont;
}

private protected virtual IList<Rectangle> GetNeighboringToolsRectangles()
=> ((IKeyboardToolTip)ToolStripControlHost)?.GetNeighboringToolsRectangles() ?? GetOwnNeighboringToolsRectangles();

Expand Down Expand Up @@ -7777,12 +7789,29 @@ protected virtual void OnHandleCreated(EventArgs e)
SetWindowFont();
}

if (DpiHelper.IsPerMonitorV2Awareness && !(typeof(Form).IsAssignableFrom(GetType())))
if (DpiHelper.IsPerMonitorV2Awareness)
{
int old = _deviceDpi;
Font localFont = GetCurrentFontAndDpi(out int fontDpi);
_deviceDpi = (int)User32.GetDpiForWindow(this);
if (old != _deviceDpi)
{
if (fontDpi != _deviceDpi)
{
// Controls are by default font scaled.
// Dpi change requires font to be recalculated in order to get controls scaled with right dpi.
var factor = (float)_deviceDpi / fontDpi;
ScaledControlFont = localFont.WithSize(localFont.Size * factor);

// If it is a container control that inherit Font and is scaled by parent, we simply scale Font
// and wait for OnFontChangedEvent caused by its parent. Otherwise, we scale Font and trigger
// 'OnFontChanged' event explicitly. ex: winforms designer natively hosted in VS.
if (TryGetExplicitlySetFont(out Font local) && local is not null)
{
Font = ScaledControlFont;
}
}

RescaleConstantsForDpi(old, _deviceDpi);
}
}
Expand Down Expand Up @@ -12213,48 +12242,49 @@ private void WmDpiChangedBeforeParent(ref Message m)
{
DefWndProc(ref m);

if (IsHandleCreated)
_oldDeviceDpi = _deviceDpi;

// In order to support tests, will be querying Dpi from the message first.
int newDeviceDpi = PARAM.SignedLOWORD(m.WParam);

// On certain OS versions, for non-test scenarios, WParam may be empty.
if (newDeviceDpi == 0)
{
_oldDeviceDpi = _deviceDpi;
newDeviceDpi = (int)User32.GetDpiForWindow(this);
}

// In order to support tests, will be querying Dpi from the message first.
_deviceDpi = PARAM.SignedLOWORD(m.WParam);
if (_oldDeviceDpi == newDeviceDpi)
{
OnDpiChangedBeforeParent(EventArgs.Empty);
return;
}

// On certain OS versions, for non-test scenarios, WParam may be empty.
if (_deviceDpi == 0)
{
_deviceDpi = (int)User32.GetDpiForWindow(this);
}
Font localFont = GetCurrentFontAndDpi(out int fontDpi);
_deviceDpi = newDeviceDpi;

// Controls are by default font scaled.
// Dpi change requires font to be recalculated in order to get controls scaled with right dpi.
if (_oldDeviceDpi != _deviceDpi)
{
var factor = (float)_deviceDpi / _oldDeviceDpi;
Font localFont = Font;
Font scaledFont = localFont.WithSize(localFont.Size * factor);
if (fontDpi == _deviceDpi)
{
OnDpiChangedBeforeParent(EventArgs.Empty);
return;
}

// If it is a container control that inherit Font and is scaled by parent, we simply scale Font
// and wait for OnFontChangedEvent caused by its parent. Otherwise, we scale Font and trigger
// 'OnFontChanged' event explicitly. ex: winforms designer in VS.
if (TryGetExplicitlySetFont(out Font local) || this is not ContainerControl || !IsScaledByParent(this))
{
if (local is not null)
{
Font = scaledFont;
}
else
{
ScaledControlFont = scaledFont;
}
// Controls are by default font scaled.
// Dpi change requires font to be recalculated in order to get controls scaled with right dpi.
var factor = (float)_deviceDpi / fontDpi;
ScaledControlFont?.Dispose();
ScaledControlFont = localFont.WithSize(localFont.Size * factor);

RescaleConstantsForDpi(_oldDeviceDpi, _deviceDpi);
}
else
{
ScaledControlFont = scaledFont;
}
// If it is a container control that inherit Font and is scaled by parent, we simply scale Font
// and wait for OnFontChangedEvent caused by its parent. Otherwise, we scale Font and trigger
// 'OnFontChanged' event explicitly. ex: winforms designer in VS.
if (TryGetExplicitlySetFont(out Font local) || this is not ContainerControl || !IsScaledByParent(this))
{
if (local is not null)
{
Font = ScaledControlFont;
}

RescaleConstantsForDpi(_oldDeviceDpi, _deviceDpi);
}

OnDpiChangedBeforeParent(EventArgs.Empty);
Expand Down

0 comments on commit 9b77d91

Please sign in to comment.