Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WinForm changes size at runtime... high DPI related?

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

like image 296
Dov Avatar asked Jan 30 '23 00:01

Dov


2 Answers

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).

like image 30
Yash Doshi Avatar answered Feb 06 '23 11:02

Yash Doshi


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.

like image 93
Hans Passant Avatar answered Feb 06 '23 11:02

Hans Passant