Winforms: 为什么Graphics.DpiX/DpiY总是返回96

本文介绍了Windows中DPI设置的工作原理及其对应用程序的影响。详细解释了为何在更改DPI设置后,通过System.Drawing.Graphics获取的DPI值仍为默认值96,并提供了正确获取实际DPI值的代码示例。

 

一、问题描述

Windows中缺省的DPI值为96。在Vista中,我们把DPI设为150%,也就是144。可如果此时我们去获取属性System.Drawing.Graphics.DpiX或者System.Drawing.Graphics.DpiY的值,我们发现得到的仍然是96,而不是144

二、原因分析

         修改WindowsDPI值,所有窗口的字体会变大,因此窗口的布局(Layout)也有可能不同。Windows程序员可以根据WindowsDPI设置去定义窗口的布局,但我们发现很多程序员在根据DPI定义窗口布局的时候遇到了很多问题。所以Windows的解决办法就是不建议程序员根据DPI设置调整窗口布局,Windows会根据系统设置自动按比例放大窗口的所有元素(包括文字和控件)。这样虽然降低了程序员的灵活性和自主性,但至少保证了窗口布局在所有DPI设置时都是正确的。

于是从Vista开始,Windows添加了一个叫SetProcessDPIAwareAPI。缺省的情况下,该函数不被调用,因此我们的进程不知道DPI已经修改,那我们得到的DPI也总是其缺省值96了。

三、建议

         如前所述,我们不建议程序员试图去获取DPI设置值,并根据该值去调整窗口布局。如果确实需要得到系统的DPI的设置值,我们必须调用API SetProcessDPIAware。下面是一个例子:

    public class Utility

    {

        private const int LOGPIXELSX = 88;

        private const int LOGPIXELSY = 90;

 

        public static int DpiX

        {

            get

            {

                if (Environment.OSVersion.Version.Major >= 6)

                    SetProcessDPIAware();

 

                IntPtr hDC = GetDC(new HandleRef(null, IntPtr.Zero));

                return GetDeviceCaps(hDC, LOGPIXELSX);

            }

        }

 

        public static int DpiY

        {

            get

            {

                if (Environment.OSVersion.Version.Major >= 6)

                    SetProcessDPIAware();

 

                IntPtr hDC = GetDC(new HandleRef(null, IntPtr.Zero));

                return GetDeviceCaps(hDC, LOGPIXELSY);

            }

        }

 

        [DllImport("user32.dll")]

        private extern static bool SetProcessDPIAware();

 

        [DllImport("user32.dll")]

        private extern static IntPtr GetDC(HandleRef hWnd);

 

        [DllImport("gdi32.dll")]

        private extern static int GetDeviceCaps(IntPtr hdc, int nIndex);

    }

         上述代码先得到显示器的DC,然后得到该DCX方向和Y方向的DPI值。另外值得注意的是,由于API SetProcessDPIAwareVista才被引入,所以在调用该API之前,我们需要判断Windows的版本号。Vista的主版本号是6

 

using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace Recharge { class AutoSizeFormClass { //1.声明一个结构体,记录窗体和控件的基本属性 public struct controlRect { public string Name; public int Left; public int Top; public int Width; public int Height; public float FontSize; public FontFamily FontFamily; } //2.声明一个集合记录所有控件的属性 //使用控件的Name作为key Dictionary<string, controlRect> dic = new Dictionary<string, controlRect>(); int ctrNo = 0; //这里是你开发环境下的分辨率 private int ScW = 1920; private int ScH = 1080; //记录窗体是不是第一次加载的标记 0:第一次加载 1:重复加载 private int IsFirstLoad = 0; //窗体的名称 private string FrmName = string.Empty; //3.创建两个函数 //采用递归的方法将控件包含的所有控件属性记录下来(结构体的每一个属性都需要赋值) private void AddControl(Control ctl) { foreach (Control c in ctl.Controls) { GetCtrParameter(c); //使用递归函数先记录控件本身,后记录控件包含的子控件 if (c.Controls.Count > 0) { AddControl(c); } } } //获取控件的所有属性 private void GetCtrParameter(Control mForm) { controlRect cr; cr.Name = mForm.Name; cr.Left = mForm.Left; cr.Top = mForm.Top; cr.Width = mForm.Width; cr.Height = mForm.Height; cr.FontSize = mForm.Font.Size; cr.FontFamily = mForm.Font.FontFamily; dic.Add(cr.Name, cr); } //4.控件自适应大小 public void controlAutoSize(Control mForm) { FrmName = mForm.Name; float wScale = 0; float hScale = 0; //因为有些控件和DataGridView的子空间加载时间较长,所以在Form1_SizeChanged中, //记录控件的原始大小和位置,第一次加载的时候先根据和开发环境的像素比例绘制窗体 if (IsFirstLoad == 0 && ctrNo == 0) { //获取当前的像素 int SH = Screen.PrimaryScreen.Bounds.Height; int SW = Screen.PrimaryScreen.Bounds.Width; //和开发环境的像素相比获取对应的比值 wScale = (float)SH / (float)ScH; hScale = (float)SW / (float)ScW; controlRect cR; cR.Name = mForm.Name; cR.Left = mForm.Left; cR.Top = mForm.Top; cR.Width = mForm.Width; cR.Height = mForm.Height; cR.FontSize = mForm.Font.Size; cR.FontFamily = mForm.Font.FontFamily; dic.Add(cR.Name, cR);//第一个为窗体本身 AddControl(mForm);//递归获取所有窗体基础信息 AutoScaleControl(mForm, wScale, hScale);//这里其实是第一次构造窗体 IsFirstLoad = 1; } //这里是改变窗体大小时重新设置窗体的属性 else { //新旧窗体之间的高和长的比例,与第一次加载的信息比较 wScale = (float)mForm.Width / dic[FrmName].Width; hScale = (float)mForm.Height / dic[FrmName].Height; //将ctrNo设为1,代表为控件而非窗体 ctrNo = 1; //设置控件以及其嵌套的控件的比例大小,使用递归调用 AutoScaleControl(mForm, wScale, hScale); } } //递归进行自适应调整 private void AutoScaleControl(Control ctl, float wScale, float hScale) { int ctrLeft0, ctrTop0, ctrWidth0, ctrHeight0; float fontSize; FontFamily fontFamily; foreach (Control c in ctl.Controls) { string ctrName = c.Name; ctrLeft0 = dic[ctrName].Left; ctrTop0 = dic[ctrName].Top; ctrWidth0 = dic[ctrName].Width; ctrHeight0 = dic[ctrName].Height; fontSize = dic[ctrName].FontSize; fontFamily = dic[ctrName].FontFamily; //新旧控件之间的线性比例,字体大小依据高度转换 c.Left = (int)(ctrLeft0 * wScale); c.Top = (int)(ctrTop0 * hScale); c.Width = (int)(ctrWidth0 * wScale); c.Height = (int)(ctrHeight0 * hScale); c.Font = new Font(fontFamily, fontSize * hScale); ctrNo++; //先缩放控件本身,后缩放控件的子控件 if (c.Controls.Count > 0) { AutoScaleControl(c, wScale, hScale); } //dataGridview特殊处理 if (c is DataGridView) { DataGridView dgv = c as DataGridView; Cursor.Current = Cursors.WaitCursor; int widths = 0; for (int i = 0; i < dgv.Columns.Count - 1; i++) { //自动调整列宽 dgv.AutoResizeColumn(i, DataGridViewAutoSizeColumnMode.AllCells); widths += dgv.Columns[i].Width; } //如果调整列的宽度大于设定宽度 if (widths > ctl.Size.Width) { dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.DisplayedCells;//调整列的模式 } else { dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;//如果小于则填充 } Cursor.Current = Cursors.Default; } } } } } 这个才是自适应吧
最新发布
10-14
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值