[英]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:在加载主窗体的方法中:
SizeChanged
event.附加SizeChanged
事件的事件处理程序。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.