4

When I run my WinForm app in Windows in a VM on a retina MacBook Pro, the size of the form shrinks at runtime, while the buttons simultaneously move outward. This can cause buttons at the bottom edge to slip below the window's edge, out of sight. Since they're bottom-anchored, they're rendered completely inaccessible. When run from a Windows-native desktop, the app usually behaves fine.

This only happens with the Font or DPI AutoScaleMode settings on the form. With Inherit or None, the form and its contents are huge, but directly proportional to how I designed them.

I've reproduced this with a fresh-from-template WinForm app, doing nothing other than resizing the form, and dropping in a button. How can I get the app to scale without the dimensions changing relative to each other?

This is the InitializeComponent() method in the designer.cs:

  private void InitializeComponent()
  {
        this.sendButton = new System.Windows.Forms.Button();
        this.SuspendLayout();
        // 
        // sendButton
        // 
        this.sendButton.Location = new System.Drawing.Point(60, 856);
        this.sendButton.Margin = new System.Windows.Forms.Padding(4);
        this.sendButton.MinimumSize = new System.Drawing.Size(200, 60);
        this.sendButton.Name = "sendButton";
        this.sendButton.Size = new System.Drawing.Size(200, 62);
        this.sendButton.TabIndex = 1004;
        this.sendButton.Text = "Send";
        this.sendButton.UseVisualStyleBackColor = true;
        // 
        // Form1
        // 
        this.AutoScaleDimensions = new System.Drawing.SizeF(12F, 25F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.ClientSize = new System.Drawing.Size(282, 981);
        this.Controls.Add(this.sendButton);
        this.Name = "Form1";
        this.Text = "Form1";
        this.ResumeLayout(false);

  }

Here's a screenshot of the form in the designer:

Form at design time

And at runtime:

Form at runtime

Dov
  • 15,530
  • 13
  • 76
  • 177
  • 1
    Use Anchors. Or a Dock-Filled TableLayoutPanel. – LarsTech Dec 20 '17 at 16:52
  • @LarsTech I do anchor to the bottom, but when I do, the control is stuck in that position out-of-bounds – Dov Dec 20 '17 at 16:54
  • 1
    I don't see the Anchor property being used in your designer code. If you are going to design on different DPI environments, the TableLayoutPanel can make things easier. – LarsTech Dec 20 '17 at 16:55
  • @LarsTech I didn't include it in my sample because it doesn't affect behavior. The layout isn't really the problem - the form isn't behaving as expected. I'll try to reword the question to make that clearer. – Dov Dec 20 '17 at 16:59
  • 1
    Well, sure it is. If you put a button at a certain point on your Retina Mac, it will probably be a different location on your Dell. A Dock-Filled TableLayoutPanel helps because it can lay things out on a percentage basis instead. You can try futzing with the form's AutoScaleMode. – LarsTech Dec 20 '17 at 17:04
  • @LarsTech but even _on the MacBook_ it looks fine in the designer, and behaves differently at runtime. The form and controls on it move around, but only when the form is sufficiently tall or wide. – Dov Dec 20 '17 at 17:13
  • @Dov Try setting AutoScaleMode=None, so CurrentAutoScaleDimensions (Run-Time) equals AutoScaleDimensions (Design-Time). The scaling factor is yours to handle then. – Jimi Dec 20 '17 at 17:19
  • @Jimi Then I need to manually scale all my controls, right? – Dov Dec 20 '17 at 17:31
  • @Dov Yes, but of course this can be a pain. Creating DPI-Aware (or High DPI) is a necessary task now. See [Creating a DPI-Aware Application](https://stackoverflow.com/questions/4075802/creating-a-dpi-aware-application), [Detect if non DPI-aware application has been scaled/virtualized](https://stackoverflow.com/questions/33507031/detect-if-non-dpi-aware-application-has-been-scaled-virtualized) – Jimi Dec 20 '17 at 17:39
  • Are you editing using standard DPI and font? I am not sure if the designer properly support high DPI. As far as I know simple WinForms works fine in HI-DPI if designed in standard DPI. Many factors affects the result you get. Among other thing, on what OS are you displaying the form as Microsoft as made many improvement in Windows 8 and 10 and their updates. So which OS is used is also very important... – Phil1970 Dec 20 '17 at 17:56
  • @Phil1970 I'm using all the default settings for a WinForm in Visual Studio 2017, and I'm running on Windows 10, fully up to date (within the last couple weeks) – Dov Dec 20 '17 at 21:23
  • What happens if you switch to more standard DPI and resolution while designing and switch back to HI-DPI while running the application? – Phil1970 Dec 21 '17 at 17:07
  • @Phil1970 I've run a binary built on another machine on the High DPI machine, with the same results. It's definitely looking like a bug in the scaling that .Net or Windows is doing. – Dov Dec 24 '17 at 15:54

2 Answers2

7

This issue is not caused by a rescaling problem, it is far more mundane. It is caused by the Form.SetBoundsCore() method, I linked to the problem statement.

You can see it using the SystemInformation class, applying constraints on the bounds so the window cannot be too small and cannot be larger than the monitor. This method runs twice in your program. The very first time is when your form's InitializeComponent() method runs. Note that at this point the size of the form is still far too large, it does not fit the monitor until after it is rescaled.

Perhaps you smell the bug, it applies the size constraint too early, before the new size is calculated by the ScaleControl() method. So your design Size.Height gets clipped by the size of the monitor. Then the window is scaled to adjust for the DPI difference, the resulting height is too small.

Normally you can override a virtual method like SetBoundsCore(), it is however not very practical to bypass the base.SetBoundsCore() call. Too much stuff happens in this method and bypassing it can cause other bugs. The practical workaround is almost too silly to mention. Note from the linked code that it does not apply the size constraint unless the form's WindowState is set to normal. So you can bypass it by setting the WindowState to Minimized in the designer. All you then have to do is set it to normal in the Load event, it fires after the window is rescaled:

    protected override void OnLoad(EventArgs e) {
        this.WindowState = FormWindowState.Normal;
        base.OnLoad(e);
    }

Heh. Scaling up is always a lot less troublesome than scaling down.

I would be remiss to not post the more typical dpiAware code. In this case similar to:

    protected override void OnLoad(EventArgs e) {
        this.ClientSize = new Size(button1.Width + 2 * button1.Left, button1.Bottom + 10);
        base.OnLoad(e);
    }

So simply force the client area to show the controls. You'd pick a control at the bottom and one on the far right.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • I'm guessing there's no way to prevent the Minimized -> Normal GUI fireworks that happens. – LarsTech Dec 27 '17 at 21:46
  • Not sure why that would be a problem, it happens while the window is not visible yet. No visible artifacts or fireworks. – Hans Passant Dec 27 '17 at 21:48
  • For me, the form appears coming up from the task bar. – LarsTech Dec 27 '17 at 22:01
  • 2
    Ah, I have that animation option turned off in the system settings. Rats. Let's call it a feature. – Hans Passant Dec 27 '17 at 22:04
  • I wonder if there's a point other than `OnLoad` that would make sense to change the window state back... – Dov Dec 29 '17 at 13:13
  • This seems to be exactly my case, but for some reason the problem started some hours ago. I had been using the window for over a month. And there was no scaling problem. Suddenly out of nowhere it started to get all cropped up. this.WindowState = FormWindowState.Normal does the trick, but you can see the screen flickering trying to show up. – Ben Quan Jan 10 '18 at 06:44
1

I think the issue is AutoScaleMode. Make these changes to your code in Form1:

this.AutoScaleDimensions = new System.Drawing.Size(96, 96);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;

This may resolve your problem. SizeF is used for Font, and for 100% font size, its value is (13F, 8F).

Derek T. Jones
  • 1,800
  • 10
  • 18
Yash Doshi
  • 11
  • 5