设计阶段Windows Forms设计器是为100%缩放(96DPI)的屏幕设计的,它在高分屏下有两个毛病,一是初始控件尺寸过小,二是在不同DPI缩放的设计环境间移植时,会产生整除误差,界面可能会变形。
Windows Forms不能用高分屏设计界面吗?非也!解决这个问题有两种方法(如果觉得以下方案不能满足你的需要,是时候选择WPF了):
第一种方法:使用网格避免整除误差在选项中将Windows窗体设计器的
LayoutMode(布局模式)改成
SnapToGrid(对齐到网格),并将
Default Grid Cell Size(默认网格大小)设为
最小可缩放单元(或它的倍数),以避免移植时产生整除误差。同时由于这些单元是可见的,也使得将控件拖到合适的尺寸非常简单。
同时,应该将窗体的
AutoScaleMode改为Dpi。默认的Font缩放使用系统默认字体的大小进行缩放,但是系统默认字体并不和DPI完全等比例,这样也会造成整除误差。
如果文字不能对齐的话,可以考虑调整TextAlign属性,比如单行Label推荐使用MiddleLeft、MiddleCenter或MiddleRight对齐方式,而不是默认的TopLeft对齐方式,以和其它控件的对齐方式统一。
最小可缩放单元如下表所示(最小可缩放单元=缩放/25%=DPI/24):
<code class="lang-text">缩放 DPI值 最小可缩放单元
100% 96 4
125% 120 5
150% 144 6
175% 168 7
200% 192 8
225% 216 9
250% 240 10</code>
Windows窗体设计器选项(150%):
效果:
第二种方法:使用布局容器进行布局如果认真学习过WPF,就会知道WPF可通过Grid、StackPanel、WrapPanel等布局容器进行布局。实际上,Windows Forms也有两个这样的容器,叫做
TableLayoutPanel和
FlowLayoutPanel,其中前者和Grid一样是表格布局容器,而后者和WrapPanel一样是流式布局容器。
在新建了窗体之后,按照以下流程使用TableLayoutPanel(表格布局面板)布局控件:
1.拖拉父窗口到合适大小,属性设置如下:
Padding:设置合适的内边距【
最小可缩放单元(如150%缩放下是6)或它的倍数】
2.拖一个TableLayoutPanel控件,属性设置如下:
Dock:设置为Fill(停靠方式=填充)
3.根据需要分割出Columns(列)和Rows(行)
要求:一个单元格最多只能放置一个控件(或容器),但是一个控件可跨多行或多列
4.拖其它控件(或容器)到对应单元格,设置
Dock:设置为Fill(停靠方式=填充)
Margin:设置合适的外边距【
最小可缩放单元(如150%缩放下是6)或它的倍数】
ColumnSpan:需要跨的列数
RowSpan:需要跨的行数
注意:Windows Forms中某些控件的Height属性有时不起作用(比如单行TextBox等),设置Dock属性并不能保证单元格被完全填充,但是只要文字仍然能够对齐就可以了。
FlowLayoutPanel(流式布局面板)和TableLayoutPanel有所不同,子控件要设置Width、Height、Margin属性,不能设置Dock属性,并且Width、Height和Margin一样也必须是最小可缩放单元的倍数。也就是说,仍然需要使用“对齐到网格”功能来完成设计。
运行阶段默认情况下,Windows Forms程序由于高DPI支持不完整,在Windows Vista以后的系统中是由系统的DWM来缩放的(称为DPI虚拟化),这样的话会导致界面模糊。可以修改Program.cs并调用SetProcessDPIAware函数关闭DPI虚拟化,由程序自己来管理界面:
<code class="lang-c">using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices; // 导入System.Runtime.InteropServices命名空间
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
static class Program
{
// 外部函数声明
[DllImport("kernel32.dll")]
private static extern IntPtr GetModuleHandle(string name);
// 这个函数只能接受ASCII,所以一定要设置CharSet = CharSet.Ansi,不然会失败
[DllImport("kernel32.dll", CharSet = CharSet.Ansi)]
private static extern IntPtr GetProcAddress(IntPtr hmod, string name);
private delegate void FarProc();
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// SetProcessDPIAware是Vista以上才有的函数,需兼容XP的话不能直接调用,需按如下所示间接调用
IntPtr hUser32 = GetModuleHandle("user32.dll");
IntPtr addrSetProcessDPIAware = GetProcAddress(hUser32, "SetProcessDPIAware");
if (addrSetProcessDPIAware != IntPtr.Zero)
{
FarProc SetProcessDPIAware = (FarProc)Marshal.GetDelegateForFunctionPointer(addrSetProcessDPIAware, typeof(FarProc));
SetProcessDPIAware();
}
// C#的原有代码
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}</code>
如果不需要兼容Windows XP的话,可以更简单地直接调用SetProcessDPIAware:
<code class="lang-c">using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices; // 导入System.Runtime.InteropServices命名空间
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
static class Program
{
// 外部函数声明
[DllImport("user32.dll")]
private static extern void SetProcessDPIAware();
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// SetProcessDPIAware是Vista以上才有的函数,这样直接调用会使得程序不兼容XP
SetProcessDPIAware();
// C#的原有代码
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}</code>
.NET Framework 4.5.1提供了优化的高DPI支持,但需要手动开启,在工程中添加XXXXXXnfig(发布时需要将.exe文件和.XXXXXXnfig文件一起发布),内容如下:<code class="lang-xml"><?xml version="1.0" encoding="utf-8"?>
<configuration>
<appsettings>
<add key="EnableWindowsFormsHighDpiAutoResizing" value="true">
</add></appsettings>
</configuration></code>
200字以内,仅用于支线交流,主线讨论请采用回复功能。