原文:【WPF】DPI对控件定位产生的影响

需求

程序界面上是一个Window,当用户点击桌面上除此Window之外的任何地方,都要把这个window隐藏掉。程序有个托盘图标,点击托盘图标不能隐藏window,托盘上有个右键菜单,点击右键菜单也不能隐藏。

分析

1.系统像素与显示器像素

我们知道wpf中控件宽高的单位是1/96英寸,如果你系统的dpi为96(再这里我们不考虑显示器的dpi,那是windows系统的事情),那么1/96英寸就是1个系统像素。这里的像素要和显示器的像素分开。显示器的像素是出场的时候就已经设计好的,是不可改变的,如:1600(宽)x1200(高)。在这里我们可以把显示器的像素称为“像元”,系统的像素称为“系统像素”加以区分。像元是单个的发光物理单位,如一个发光二极管。如果我将此项改为800x600,则显示器显示的时候会将两个像元作为一个像素显示。

系统像素则是可以改变的,在win10中可以通过如下进行改变。

2.系统的dpi和wpf中的宽高单位

系统的dpi在win10中可以上图的更改文本、应用等项目的大小那里设置,默认的100%就是96dpi,当调成125%时,dpi就是96*125%=120dpi。此时,系统的分辨率还是不变的哦,仍然是1920x1200。

对于你的屏幕(1920x1200分辨率)在96dpi下,高度将是1200(一种与设备无关的单位)。在120dpi下,高度将是1200/(120/96)=960(与设备无关单位)。具体为什么,参考下面的Code1。总结:96dpi时,1设备无关单位=1像素,120dpi时,1设备无关单位=1像素x125%=1.25像素。所以如果你一个控件的width=100,在96dpi时显示是100个像素,在120dpi时将显示125个像素,这样看起来是不是就大了!!!

知道了这两点,就方便控件定位了。


(这里再插一句,系统分辨率和DPI间的关系:通过调小系统分辨率和调大DPI都可以实现放大系统文字和图标的功能,但是你会发现通过调整分辨率实现的放大会比较模糊,而调整DPI实现的放大几乎很少出现模糊的情况。这里的原因暂时还没有摸索出来,后续会继续更新)

3.需要获取鼠标的点击位置

鼠标点击位置的获取,可以通过下面代码Code2获得,单位是系统像素。

4.获取到的鼠标位置与设备无关单位间的的转换

鼠标位置的单位是系统像素,假如鼠标距屏幕左边是100像素吧。则100/(系统的dpi/96)=?得到的值就是一个与设备无关的左边距的值,也就是与wpf中控件的width,height同样的单位。


Code1:系统像素,系统dpi的获取方法:

using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))
{
Console.WriteLine("xDpi:" + g.DpiX + " YDPI:" + g.DpiY);
}
Console.WriteLine("【PrimaryScreen.Bounds(系统设置中像素的值)】");
Console.WriteLine(string.Format("width{0},height{1}", System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width, System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height));
Console.WriteLine("【设备宽高(dpi)】");
Console.WriteLine(string.Format("Width:{0},Height:{1}", SystemParameters.PrimaryScreenWidth, SystemParameters.PrimaryScreenHeight));
Console.WriteLine("【WorkAera宽高(dpi)】");
Console.WriteLine(string.Format("Width:{0},Height:{1}", SystemParameters.WorkArea.Width, SystemParameters.WorkArea.Height));

每次更改dpi后,必须注销系统才能获取到新的dpi。

(从上面代码中,可以看到无论你dpi怎么改,系统像素是一直不变的。设备的宽高和workarea的宽高会改变)


Code2:鼠标位置获取

 class MouseHook
{
private const int WM_MOUSEMOVE = 0x200;
private const int WM_LBUTTONDOWN = 0x201;
private const int WM_RBUTTONDOWN = 0x204;
private const int WM_MBUTTONDOWN = 0x207;
private const int WM_LBUTTONUP = 0x202;
private const int WM_RBUTTONUP = 0x205;
private const int WM_MBUTTONUP = 0x208;
private const int WM_LBUTTONDBLCLK = 0x203;
private const int WM_RBUTTONDBLCLK = 0x206;
private const int WM_MBUTTONDBLCLK = 0x209; public event MouseEventHandler OnMouseActivity; static int hMouseHook = 0; public const int WH_MOUSE_LL = 14; HookProc MouseHookProcedure;
Log _log = new Log("MouseHook", true, Log4netWrapper.Default);
[StructLayout(LayoutKind.Sequential)]
public class POINT
{
public int x;
public int y;
} [StructLayout(LayoutKind.Sequential)]
public class MouseHookStruct
{
public POINT pt;
public int hWnd;
public int wHitTestCode;
public int dwExtraInfo;
} [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam); public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam); public MouseHook()
{
} ~MouseHook()
{
Stop();
} public void Start()
{
if (hMouseHook == 0)
{
MouseHookProcedure = new HookProc(MouseHookProc); hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProcedure, Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0]), 0); if (hMouseHook == 0)
{
Stop();
_log.E("SetWindowsHookEx failed.");
}
}
} public void Stop()
{
bool retMouse = true;
if (hMouseHook != 0)
{
retMouse = UnhookWindowsHookEx(hMouseHook);
hMouseHook = 0;
} if (!(retMouse))
{
_log.E("UnhookWindowsHookEx failed.");
}
} private int MouseHookProc(int nCode, Int32 wParam, IntPtr lParam)
{
if ((nCode >= 0) && (OnMouseActivity != null))
{
MouseButtons button = MouseButtons.None;
int clickCount = 0; switch (wParam)
{
case WM_LBUTTONDOWN:
button = MouseButtons.Left;
clickCount = 1;
break;
case WM_LBUTTONUP:
button = MouseButtons.Left;
clickCount = 1;
break;
case WM_LBUTTONDBLCLK:
button = MouseButtons.Left;
clickCount = 2;
break;
case WM_RBUTTONDOWN:
button = MouseButtons.Right;
clickCount = 1;
break;
case WM_RBUTTONUP:
button = MouseButtons.Right;
clickCount = 1;
break;
case WM_RBUTTONDBLCLK:
button = MouseButtons.Right;
clickCount = 2;
break;
} MouseHookStruct MyMouseHookStruct = (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));
MouseEventArgs e = new MouseEventArgs(button, clickCount, MyMouseHookStruct.pt.x, MyMouseHookStruct.pt.y, 0); OnMouseActivity(this, e);
}
return CallNextHookEx(hMouseHook, nCode, wParam, lParam);
}
}

使用方法:

MouseHook hook = new MouseHook();
hook.OnMouseActivity += Hook_OnMouseActivity;
hook.Start();

在Hook_OnMouseActivity中可以获取到鼠标的位置。

实现

这里就不用再赘述代码了,只有关键的一点,在程序中,判断与鼠标点击位置与窗体显示位置之间的距离,要做差运算,所以单位要统一。我建议将鼠标的像素位置值转为与设备无关的值,然后进行计算,否则会出现偏移。

【WPF】DPI对控件定位产生的影响的更多相关文章

  1. WPF中Ribbon控件的使用

    这篇博客将分享如何在WPF程序中使用Ribbon控件.Ribbon可以很大的提高软件的便捷性. 上面截图使Outlook 2010的界面,在Home标签页中,将所属的Menu都平铺的布局,非常容易的可 ...

  2. WPF 调用WinForm控件

    WPF可以使用WindowsFormsHost控件做为容器去显示WinForm控件,类似的用法网上到处都是,就是拖一个WindowsFormsHost控件winHost1到WPF页面上,让后设置win ...

  3. InteropBitmap指定内存,绑定WPF的Imag控件时刷新问题。

    1.InteropBitmap指定内存,绑定WPF的Imag控件的Source属性 创建InteropBitmap的时候,像素的格式必须为PixelFormats.Bgr32, 如果不是的话在绑定到I ...

  4. 转载:Robotium之Android控件定位实践和建议(Appium/UIAutomator姊妹篇)

    来源于:http://blog.csdn.net/zhubaitian/article/details/39803857 1. 背景 为保持这个系列的一致性,我们继续用SDK自带的NotePad实例应 ...

  5. 在WPF程序中将控件所呈现的内容保存成图像(转载)

    在WPF程序中将控件所呈现的内容保存成图像 转自:http://www.cnblogs.com/TianFang/archive/2012/10/07/2714140.html 有的时候,我们需要将控 ...

  6. Robotium之Android控件定位实践和建议(Appium/UIAutomator姊妹篇)

    本人之前以前撰文描写叙述Appium和UIAutomator框架是怎样定位Android界面上的控件的. UIAutomator定位Android控件的方法实践和建议 Appium基于安卓的各种Fin ...

  7. 【WPF】监听WPF的WebBrowser控件弹出新窗口的事件

    原文:[WPF]监听WPF的WebBrowser控件弹出新窗口的事件 WPF中自带一个WebBrowser控件,当我们使用它打开一个网页,例如百度,然后点击它其中的链接时,如果这个链接是会弹出一个新窗 ...

  8. 在WPF的WebBrowser控件中抑制脚本错误

    原文:在WPF的WebBrowser控件中抑制脚本错误 今天用WPF的WebBrowser控件的时候,发现其竟然没有ScriptErrorsSuppressed属性,导致其到处乱弹脚本错误的对话框,在 ...

  9. 【转】Appium基于安卓的各种FindElement的控件定位方法实践

    原文地址:http://blog.csdn.net/zhubaitian/article/details/39754041#t11 AppiumDriver的各种findElement方法的尝试,尝试 ...

随机推荐

  1. SQLite编码

    •SQLite编码 •讲师:李明杰 •技术博客:http://www.cnblogs.com/mjios •SQLite3 •在iOS中使用SQLite3,首先要添加库文件libsqlite3.dyl ...

  2. 简洁常用权限系统的设计与实现(六):不维护节点的深度level,手动计算level,构造树 (把一颗无序的树,变成有序的)

     本篇介绍的方法,参考了网上的代码.在递归过程中,计算level,是受到了这种方法的启发. CSDN上有篇关于树的算法,目标是把一个无序的树,变成有序的. 我看了下代码,并运行了下,感觉是可行的. 我 ...

  3. layer弹框在实际项目中的一些应用

    官方介绍:layer至今仍作为layui的代表作,受众广泛并非偶然,而是这五年多的坚持,不断完善和维护.不断建设和提升社区服务,使得猿们纷纷自发传播,乃至于成为今天的Layui最强劲的源动力.目前,l ...

  4. 大型项目linux自动化版本发布脚本(shell)之tomcat、nginx服务脚本

    开发十年,就只剩下这套Java开发体系了 >>>   最近,又临近博主所负责的一个大型项目的发版了.之前有提到过,该项目涉及到30-40台服务器的发版.且项目客户规定发版需在晚上10 ...

  5. Cordova之如何用命令行创建一个项目(完整示例)

    原文:Cordova之如何用命令行创建一个项目(完整示例) 1. 创建cordova项目 (注意:当第一次创建或编译项目的时候,可能系统会自动下载一些东西,需要一些时间.) 在某个目录下创建cordo ...

  6. 【27.40%】【codeforces 599D】Spongebob and Squares

    time limit per test2 seconds memory limit per test256 megabytes inputstandard input outputstandard o ...

  7. Android 对.properties文件的读取

    /** * * @param filepath .properties文件的位置 */ public void checkFileExists(String filepath){ File file ...

  8. 浏览器加载js文件顺序

    在默认情况下,下载和执行js都会阻塞页面的渲染,当然现在浏览器支持并行下载,但仍然会阻塞图片等的下载和渲染,所以通常建议把js文件放body底.对于执行顺序,不管是外部js还是内部,只要 遇到< ...

  9. 支付(异步通知notify_url 与 同步通知return_url的区别)

    同步通知和异步通知发送的数据没有本质的区别:同步通知有2个作用:第一是从支付宝的页面上返回自己的网站继续后续操作:第二是携带支付状态的get参数:让自己的网站用于验证: 同步通知后:还需要异步通知主要 ...

  10. Linux核心设计依据(六)该块I/O一层

    块设备是能随机存取装置固定大小的数据表设备.如硬盘:字符设备(如串口和键盘)它是按照字符流进入有序进行.不同之处在于是否足够的随机存取数据--这时候,你可以随心所欲地从一个位置跳到访问设备和位置.复杂 ...