根据窗口大小缩放winforms控件

tv6aics1  于 2023-01-21  发布在  其他
关注(0)|答案(1)|浏览(182)

当用户调整窗口大小时,我尝试缩放我程序中的所有控件(也包括tabcontrol中的控件)。我知道如何调整控件大小,但我希望我的程序能缩放所有控件,这样用户界面更容易阅读
我不是一个很好的解释者,所以这里有一个参考图片:

我看过很多关于调整控件大小的讨论,但都不是很有帮助。我能在winforms中实现这个效果吗?

hpxqektj

hpxqektj1#

    • 用窗口大小缩放winforms控件的一种方法**是使用嵌套的TableLayoutPanel控件,并将行和列设置为使用百分比而不是绝对大小。

然后,将控件放置在单元格中,并将它们锚定在所有四个边上。对于背景图像设置为BackgroundImageLayout = Stretch的按钮,这就是您需要做的全部工作。但是,使用文本的控件可能需要自定义方式来缩放字体。例如,您使用的ComboBox控件的大小是字体的函数,而不是相反。为了弥补这一点,这些实际屏幕截图利用扩展来进行改变字体大小直到达到目标控件高度的二分搜索。

基本思想是通过设置一个看门狗定时器来响应TableLayoutPanel大小的变化,当定时器超时时,迭代控制树以应用BinarySearchFontSize扩展。您可能需要将我用来测试这个答案的代码clone,并自己进行实验,看看这些部分是如何组合在一起的。
像这样的东西会起作用,但你可能想做更多的测试比我没有。

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;
    }
}
    • 编辑**
  • 我的答案的本质是使用嵌套的TableLayoutPanel控件,我不想从这一点上拿走,所以我给了browse完整代码的参考链接。由于TableLayoutPanel不是一个全面的解决方案,不能缩放组合框和其他字体的高度。(对于原始帖子中的例子来说,这并不是那么微不足道)我的回答也展示了一种实现这一目标的方法。(我确实想提供"足够"的信息!)下面是一个附录,显示了调用扩展的MainForm代码。*
    • 示例:**

在加载主窗体的方法中:

  • 迭代控件树以查找所有TableLayoutPanels。
  • 附加SizeChanged事件的事件处理程序。
  • 通过重启看门狗定时器处理事件。
  • 当计时器到期时,_iterate每个TableLayoutPanel的控制树以应用onAnyCellPaint方法来获得小区度量并调用扩展。

通过采用这种方法,可以自由地添加和/或移除单元格和控件,而不必改变缩放引擎。

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);
        }
    }
}

相关问题