简体   繁体   English

使用 window 大小缩放 winforms 控件

[英]Scale winforms controls with window size

I am trying to scale all controls in my program (the ones in the tabcontrol too) when user resizes the window. I know how to resize the controls, but i wanted my program to have everything scaled, so the ui i easier to read当用户调整 window 的大小时,我正在尝试缩放程序中的所有控件(也包括 tabcontrol 中的控件)。我知道如何调整控件的大小,但我希望我的程序能够缩放所有内容,因此 ui 更易于阅读

i am not a good explainer, so here's a reference picture:我不是一个很好的解释者,所以这里有一个参考图片: 缩放控件(未调整大小)

I have seen a lot of discussions about resizing the controls, but they are not very helpful.我已经看到很多关于调整控件大小的讨论,但它们并不是很有帮助。 can i achieve this effect in winforms?我可以在 winforms 中实现这种效果吗?

One way to scale winforms controls with window size is to use nested TableLayoutPanel controls and set the rows and columns to use percent rather than absolute sizing.缩放 window 大小的 winforms 控件的一种方法是使用嵌套的TableLayoutPanel控件并将行和列设置为使用百分比而不是绝对大小。

嵌套表格布局面板


Then, place your controls in the cells and anchor them on all four sides.然后,将控件放在单元格中并将它们固定在所有四个边上。 For buttons that have a background image set to BackgroundImageLayout = Stretch this is all you need to do.对于背景图像设置为BackgroundImageLayout = Stretch的按钮,这就是您需要做的全部。 However, controls that use text may require a custom means to scale the font.但是,使用文本的控件可能需要自定义方法来缩放字体。 For example, you're using ComboBox controls where size is a function of the font not the other way around.例如,您正在使用 ComboBox 控件,其中大小是字体的 function,而不是相反。 To compensate for this, these actual screenshots utilize an extension to do a binary search that changes the font size until a target control height is reached.为了弥补这一点,这些实际的屏幕截图使用一个扩展来进行二进制搜索,该扩展会更改字体大小,直到达到目标控件高度。

调整性能


The basic idea is to respond to TableLayoutPanel size changes by setting a watchdog timer, and when the timer expires iterate the control tree to apply the BinarySearchFontSize extension.基本思想是通过设置看门狗定时器来响应TableLayoutPanel大小的变化,并且当定时器到期时迭代控制树以应用BinarySearchFontSize扩展。 You may want to clone the code I used to test this answer and experiment for yourself to see how the pieces fit together.您可能想要克隆我用来测试此答案的代码,并自己进行实验以了解各个部分如何组合在一起。

Something like this would work but you'll probably want to do more testing than I did.这样的东西会起作用,但你可能想做比我做的更多的测试。

static class Extensions
{
    public static float BinarySearchFontSize(this Control control, float containerHeight)
    {
        float
            vertical = BinarySearchVerticalFontSize(control, containerHeight),
            horizontal = BinarySearchHorizontalFontSize(control);
        return Math.Min(vertical, horizontal);
    }
    /// <summary>
    /// Get a font size that produces control size between 90-100% of available height.
    /// </summary>
    private static float BinarySearchVerticalFontSize(Control control, float containerHeight)
    {
        var name = control.Name;
        switch (name)
        {
            case "comboBox1":
                Debug.WriteLine($"{control.Name}: CONTAINER HEIGHT {containerHeight}");
                break;
        }
        var proto = new TextBox
        {
            Text = "|", // Vertical height independent of text length.
            Font = control.Font
        };
        using (var graphics = proto.CreateGraphics())
        {
            float
                targetMin = 0.9F * containerHeight,
                targetMax = containerHeight,
                min, max;
            min = 0; max = proto.Font.Size * 2;
            for (int i = 0; i < 10; i++)
            {
                if(proto.Height < targetMin)
                {
                    // Needs to be bigger
                    min = proto.Font.Size;
                }
                else if (proto.Height > targetMax)
                {
                    // Needs to be smaller
                    max = proto.Font.Size;
                }
                else
                {
                    break;
                }
                var newSizeF = (min + max) / 2;
                proto.Font = new Font(control.Font.FontFamily, newSizeF);
                proto.Invalidate();
            }
            return proto.Font.Size;
        }
    }
    /// <summary>
    /// Get a font size that fits text into available width.
    /// </summary>
    private static float BinarySearchHorizontalFontSize(Control control)
    {
        var name = control.Name;

        // Fine-tuning
        string text;
        if (control is ButtonBase)
        {
            text = "SETTINGS"; // representative max staing
        }
        else
        {
            text = string.IsNullOrWhiteSpace(control.Text) ? "LOCAL FOLDERS" : control.Text;
        }
        var protoFont = control.Font;
        using(var g = control.CreateGraphics())
        {
            using (var graphics = control.CreateGraphics())
            {
                int width =
                    (control is ComboBox) ?
                        control.Width - SystemInformation.VerticalScrollBarWidth :
                        control.Width;
                float
                    targetMin = 0.9F * width,
                    targetMax = width,
                    min, max;
                min = 0; max = protoFont.Size * 2;
                for (int i = 0; i < 10; i++)
                {
                    var sizeF = g.MeasureString(text, protoFont);
                    if (sizeF.Width < targetMin)
                    {
                        // Needs to be bigger
                        min = protoFont.Size;
                    }
                    else if (sizeF.Width > targetMax)
                    {
                        // Needs to be smaller
                        max = protoFont.Size;
                    }
                    else
                    {
                        break;
                    }
                    var newSizeF = (min + max) / 2;
                    protoFont = new Font(control.Font.FontFamily, newSizeF);
                }
            }
        }
        return protoFont.Size;
    }
}

Edit编辑

The essence of my answer is use nested TableLayoutPanel controls and I didn't want to take away from that so I had given a reference link to browse the full code.我的答案的本质是使用嵌套的 TableLayoutPanel 控件,我不想放弃它,所以我提供了一个参考链接来浏览完整的代码。 Since TableLayoutPanel is not a comprehensive solution without being able to scale the height of ComboBox and other Fonts (which isn't all that trivial for the example shown in the original post) my answer also showed one way to achieve that.由于TableLayoutPanel不是一个全面的解决方案,不能缩放 ComboBox 和其他 Fonts 的高度(这对于原始帖子中显示的示例来说并不是那么微不足道),我的回答也展示了一种实现这一目标的方法。 In response to the comment below (I do want to provide "enough" info.) here is an appendix showing the MainForm code that calls the extension.为了回应下面的评论(我确实想提供“足够”的信息。)这里是一个附录,显示了调用扩展的 MainForm 代码。

Example:例子:

In the method that loads the main form:在加载主窗体的方法中:

  • Iterate the control tree to find all the TableLayoutPanels.迭代控件树以找到所有 TableLayoutPanel。
  • Attach event handler for the SizeChanged event.附加SizeChanged事件的事件处理程序。
  • Handle the event by restarting a watchdog timer.通过重新启动看门狗定时器来处理事件。
  • When the timer expires, _iterate the control tree of each TableLayoutPanel to apply the onAnyCellPaint method to obtain the cell metrics and call the extension.当计时器到期时,迭代每个TableLayoutPanel的控件树以应用onAnyCellPaint方法获取单元格指标并调用扩展。

By taking this approach, cells and controls can freely be added and/or removed without having to change the scaling engine.通过采用这种方法,可以自由添加和/或删除单元格和控件,而无需更改缩放引擎。

public partial class MainForm : Form
{
    public MainForm() => InitializeComponent();
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        if(!DesignMode)
        {
            comboBox1.SelectedIndex = 0;
            IterateControlTree(this, (control) =>
            {
                if (control is TableLayoutPanel tableLayoutPanel)
                {
                    tableLayoutPanel.SizeChanged += (sender, e) => _wdtSizeChanged.StartOrRestart();
                }
            });

            _wdtSizeChanged.PropertyChanged += (sender, e) =>
            {
                if (e.PropertyName!.Equals(nameof(WDT.Busy)) && !_wdtSizeChanged.Busy)
                {
                    IterateControlTree(this, (control) =>
                    {
                        if (control is TableLayoutPanel tableLayoutPanel)
                        {
                            IterateControlTree(tableLayoutPanel, (child) => onAnyCellPaint(tableLayoutPanel, child));
                        }
                    });
                }
            };
        }
        // Induce a size change to initialize the font resizer.
        BeginInvoke(()=> Size = new Size(Width + 1, Height));
        BeginInvoke(()=> Size = new Size(Width - 1, Height));
    }

    // Browse full code sample to see WDT class
    WDT _wdtSizeChanged = new WDT { Interval = TimeSpan.FromMilliseconds(100) };

    SemaphoreSlim _sslimResizing= new SemaphoreSlim(1);
    private void onAnyCellPaint(TableLayoutPanel tableLayoutPanel, Control control)
    {
        if (!DesignMode)
        {
            if (_sslimResizing.Wait(0))
            {
                try
                {
                    var totalVerticalSpace =
                        control.Margin.Top + control.Margin.Bottom +
                        // I'm surprised that the Margin property
                        // makes a difference here but it does!
                        tableLayoutPanel.Margin.Top + tableLayoutPanel.Margin.Bottom +
                        tableLayoutPanel.Padding.Top + tableLayoutPanel.Padding.Bottom;
                    var pos = tableLayoutPanel.GetPositionFromControl(control);
                    int height;
                    float optimal;
                    if (control is ComboBox comboBox)
                    {
                        height = tableLayoutPanel.GetRowHeights()[pos.Row] - totalVerticalSpace;
                        comboBox.DrawMode = DrawMode.OwnerDrawFixed;
                        optimal = comboBox.BinarySearchFontSize(height);
                        comboBox.Font = new Font(comboBox.Font.FontFamily, optimal);
                        comboBox.ItemHeight = height;
                    }
                    else if((control is TextBoxBase) || (control is ButtonBase))
                    {
                        height = tableLayoutPanel.GetRowHeights()[pos.Row] - totalVerticalSpace;
                        optimal = control.BinarySearchFontSize(height);
                        control.Font = new Font(control.Font.FontFamily, optimal);
                    }
                }
                finally
                {
                    _sslimResizing.Release();
                }
            }
        }
    }
    internal void IterateControlTree(Control control, Action<Control> fx)
    {
        if (control == null)
        {
            control = this;
        }
        fx(control);
        foreach (Control child in control.Controls)
        {
            IterateControlTree(child, fx);
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM