一、窗體控件大小
第一種方法:使用網格避免整除誤差
在選項中將Windows窗體設計器的LayoutMode(布局模式)改成SnapToGrid(對齊到網格),並將Default Grid Cell Size(默認網格大小)設為最小可縮放單元(或它的倍數),以避免移植時產生整除誤差。同時由於這些單元是可見的,也使得將控件拖到合適的尺寸非常簡單。
同時,應該將窗體的AutoScaleMode改為Dpi。默認的Font縮放使用系統默認字體的大小進行縮放,但是系統默認字體並不和DPI完全等比例,這樣也會造成整除誤差。
如果文字不能對齊的話,可以考慮調整TextAlign屬性,比如單行Label推薦使用MiddleLeft、MiddleCenter或MiddleRight對齊方式,而不是默認的TopLeft對齊方式,以和其它控件的對齊方式統一。
最小可縮放單元如下表所示(最小可縮放單元=縮放/25%=DPI/24):
縮放 DPI值 最小可縮放單元
100% 96 4
125% 120 5
150% 144 6
175% 168 7
200% 192 8
225% 216 9
250% 240 10
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虛擬化,由程序自己來管理界面:
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()); } } }
如果不需要兼容Windows XP的話,可以更簡單地直接調用SetProcessDPIAware:
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()); } } }
.NET Framework 4.5.1提供了優化的高DPI支持,但需要手動開啟,在工程中添加app.config(發布時需要將.exe文件和.exe.config文件一起發布),內容如下:
<?xml version="1.0" encoding="utf-8"?> <configuration> <appSettings> <add key="EnableWindowsFormsHighDpiAutoResizing" value="true" /> </appSettings> </configuration>
二 、FormSize和設計不符
Windows的高DPI支持是通過DWM(Desktop Window Manager)縮放實現的,但是有時候我們不希望這種效果(例如縮放會使一些內容變得模糊),因此需要禁用Windows高DPI對程序的縮放。有兩種方式可以實現這種效果:一個是使用應用程序清單文件,一個是使用系統API實現。
1、使用清單文件
這里以Winform為例,右鍵項目->添加->新建項->應用程序清單文件,將含有dpiAware標簽的屬性取消注釋,代碼如下:
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
2、使用系統API
下面的代碼對Win7及以上的系統禁用高DPI。
if (Environment.OSVersion.Version.Major >= 6)
{
SetProcessDPIAware();
}
[DllImport("user32.dll")]