0

Windows (depending font/zoom/scaling) is resizing my form. I'd like to programmatically find the original position and size of the form and controls (which were set in the designer. At runtime, these sizes and positions change as the form is created.

Dhanil Dinesan
  • 575
  • 9
  • 27
Mark Malburg
  • 125
  • 1
  • 9
  • Pretty sure the only way to do this is copying the x/y position of the form and control into a variable – MindSwipe Oct 21 '19 at 13:52
  • Why would you even want those values? What possible use are they? – DavidG Oct 21 '19 at 13:54
  • Is this an Anchor / Docking issue, or a computer's display setting issue? – LarsTech Oct 21 '19 at 13:55
  • I would like to lay things out in the designer view and then (at runtime) refer back to the original, designer-based layout. Anchor/dock isn't giving me the control that I need. When my form loads, it immediately gets resized by Windows as it is first displayed (which is really annoying). – Mark Malburg Oct 21 '19 at 14:00
  • MindSwipe - that's what I'd like to do, however at runtime, the variables get changed via ResumeLayout before I can access them. :( – Mark Malburg Oct 21 '19 at 14:02
  • 1
    But if you properly anchor or dock your controls, this should not be an issue. – LarsTech Oct 21 '19 at 14:05
  • Lars - anchoring/docking ensures consistent edge borders. I need everything to be "percent based" not fixed borders. With anchors, your controls eventually go to zero size, while your borders remain constant. Basically, I want to capture control positions prior to the ResumeLayout call found in "InitializeComponent". – Mark Malburg Oct 21 '19 at 14:10
  • 1
    Use the @ symbol in front of a user name to notify them you are responding to a comment. We don't know how complicated your form is, but a TableLayoutPanel is capable of a percentage based layout. – LarsTech Oct 21 '19 at 14:13
  • The TableLayoutPanel allows percentage-based column widths and row heights. That might be your best bet if you want a layout that works with the form designer. – RogerN Oct 21 '19 at 14:14
  • @RogerN my controls are "scattered" not gridded. The TableLayoutPanel is nice for gridded things. – Mark Malburg Oct 21 '19 at 14:17
  • What does *Windows is resizing my form* mean? Is your application DPIAware? If not: [How to configure an app to run correctly on a machine with a high DPI setting (e.g. 150%)?](https://stackoverflow.com/a/13228495/7444103). Anyway, you can *scatter* you controls inside containers (Panels, GroupBoxes, FlowLayoutPanels etc.), then add these containers to a TableLayoutPanel, settings rows and columns sizes to a percentage. – Jimi Oct 21 '19 at 16:34

1 Answers1

0

Windows (depending font/zoom/scaling) is resizing my form.

Windows is not resizing your form. Your form is configured to auto-scale. This is controlled by the AutoScaleMode Property. The designer default value is System.Windows.Forms.AutoScaleMode.Font (the true default is Inherit). A high-level overview of the WinForm auto-scaling can be found in the documentation Current support for automatic scaling. A common alternative value to use is System.Windows.Forms.AutoScaleMode.DPI. When auto-scaling is enabled, a scaling factor computed using the value of AutoScaleDimensions and a value evaluated at runtime using the DPI reported to the application. AutoScaleDimensions is set in designer genearated code.

If the application is declared to be DPI-aware, the true DPI value is used. If it is not DPI-aware, Windows tells that application that it is running at 96 dpi. The reported DPI value also affects the metrics used in Font-based scaling.

I'd like to programmatically find the original position and size of the form and controls (which were set in the designer. At runtime, these sizes and positions change as the form is created.

This can be accomplished by intercepting the point before auto-scaling is applied. There is not a direct way to do this as you are dealing with auto-generated code (the Form.designer.cs file's InitializeComponent method). It is in this method that AutoScaleDimensions and AutoScaleMode are set.

When you create a new form, and this method looks like the following.

private void InitializeComponent()
{
    this.components = new System.ComponentModel.Container();
    this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
    this.ClientSize = new System.Drawing.Size(800, 450);
    this.Text = "Form1";
}

Notice that the AutoScaleDimensions property is not yet declared in the method; it will be added once you make some change to form (change a property or add a control) and the new information is written to the file. The AutoScaleDimensions property will only be written if AutoScaleMode is set to either DPI or Font.

The following is the result of changing the form's Size.

private void InitializeComponent()
{
    this.SuspendLayout();
    // 
    // Form1
    // 
    this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
    this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
    this.ClientSize = new System.Drawing.Size(782, 453);
    this.Name = "Form1";
    this.Text = "Form1";
    this.ResumeLayout(false);
}

The purpose for showing the above is show you how to obtain the value for setting the AutoScaleDimensions property in the following proposed solution.

Proposed Solution:

In the designer, set the form's AutoScaleMode property to either Inherit or None. Then modify the form's constructor code to reflect the following:

SizeF scalingFactor;
public Form1()
{
    InitializeComponent();

    SuspendLayout();
    AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
    AutoScaleDimensions = new System.Drawing.SizeF(8.0F, 16.0F);

    // at this point you can obtain the scaling factor that will
    // be applied on ResumeLayout
    scalingFactor = AutoScaleFactor;

    // store the design-time bounds in the control's Tag property
    RecursivelyRecordBounds(this);
    ResumeLayout(true);
}

Also add this method to the form's code.

private static void RecursivelyRecordBounds(Control c)
{
    c.Tag = c.Bounds;
    foreach (Control cc in c.Controls)
    {
        cc.Tag = cc.Bounds;
        if (cc.Controls.Count > 0)
        {
            RecursivelyRecordBounds(cc);
        }
    }
}

For the purposes of this example, each control's design-time Bounds is stored in its Tag property. Note that setting the two auto scaling properties must be wrapped in a SupendLayout/ResumeLayout region. Also it is important to first set the AutoScaleMode property before setting the AutoScaleDimensions property.

An alternative solution (hack) would be leave the AutoScaleMode property set to either Font or DPI and to rely on the observed pattern of the Initialize method. In this method, the form's Text property is always set after the two scaling properties. This allows using the TextChanged event to be used to signal the proper time to retrieve the design-time Bounds.

public Form1()
{
    EventHandler eh = (s,e) => RecursivelyRecordBounds(this);;
    TextChanged += eh;
    InitializeComponent();
    TextChanged -= eh;
}
TnTinMn
  • 11,522
  • 3
  • 18
  • 39
  • Thanks @TnTinMn for the very thorough response and explanation. This is very helpful in understanding the underlying challenge. – Mark Malburg Oct 24 '19 at 11:26
  • Here's a workaround that I stumbled onto... I have a panel which contains the randomly placed controls that I'm trying to manage/scale. If I handle the "ParentChanged" event I can enumerate all of the panel's controls (and get their positions). With that info, I'm now able to programmatically place them when my panel resizes. – Mark Malburg Oct 24 '19 at 11:29