天天看点

开发支持多显示器的Windows程序

https://blog.csdn.net/jean7155/article/details/45395065?utm_source=blogxgwz3

平:Windows api 多显示器信息相关函数和相关说明,挺全的

转载地址:http://blog.csdn.net/eddiejam/article/details/3959612

Microsoft为支持多显示器模式提供了一些新的API调用,下面具体介绍它们的功能:

1.HMONITOR MonitorFromPoint(POINT pt,DWORD dwFlags)

  MonitorFromPoint返回包含特定点(pt)的一个显示器句柄。如果pt不属于任何一个显示器,返回的显示器句柄由dwFlags标志决定:

MONITOR_DEFAULTTONULL时返回NULL

MONITOR_DEFAULTTOPRIMARY时返回代表主显示器的HMONITOR句柄

MONITOR_DEFAULTTONEAREST时返回最靠近pt点的显示器的HMONITOR句柄

2.HMONITOR MonitorFromRect(LPCRECT lprc,DWORD dwFlags)

  MonitorFromRect返回包含lprc代表的矩形的显示器句柄;如果包含此矩形的显示区域不止一个,则返回包含矩形最大部分的显示器句柄;如果矩形不属于任何一个显示区域,返回的句柄由dwFlags决定:

MONITOR_DEFAULTTONULL时返回NULL

MONITOR_DEFAULTTOPRIMARY时返回代表主显示器的HMONITOR句柄

MONITOR_DEFAULTTONEAREST时返回最靠近矩形的显示器的HMONITOR句柄

3. HMONITOR MonitorFromWindow(HWND hwnd,DWORD dwFlags)

  与MonitorFromRect类似,但输入是一个代表窗口的句柄hwnd而不是指向矩形的指针。

4. BOOL GetMonitorInfo(HMONITOR hMonitor,LPMONITORINFO lpmi)

  GetMonitorInfo返回由hMonitor代表的显示器的有关信息,这些信息存储在指向MONITORINFO结构的指针——lpmi中。

typedef struct tagMONITORINFO

{  

    DWORD  cbSize; 

    RECT   rcMonitor; 

    RECT   rcWork; 

    DWORD  dwFlags; 

} MONITORINFO, *LPMONITORINFO;

这些信息包括用RECT结构表示的显示器的显示区域的大小(如果这个显示器不是主显示器,RECT的坐标可能为负数),以及用RECT结构表示的显示器的工作区域的大小,工作区域是显示区域中除去系统任务栏和应用程序快捷方式栏所剩下的区域,还能够判断此显示器是否为主显示器,并返回一个标志(判断dwFlags是否包含MONITORINFOF_PRIMARY来判断是否为主显示器)。

5.BOOL EnumDisplayMonitors(HDC hdc,LPCRECT lprcClip,MONITORENUMPROC lpfnEnum,LPARAM dwData)

  hdc是一个代表显示设备环境的句柄,lprcClip是指向一个矩形区域的指针。把这个矩形区域和设备环境中的可见区域取交集,得到的区域可能分布在多个显示器的显示区域中,EnumDisplayMonitors对每一个包含交集的显示区域调用一次MonitorEnumProc类型的函数。DwData为传递给MonitorEnumProc函数的数据。

6.BOOL CALLBACK MonitorEnumProc(HMONITOR hmonitor,HDC hdcMonitor,LPRC lprcMonitor, DWORD dwData)

  MonitorEnumProc是一个被EnumDisplayMonitors函数调用的回调函数,它的内容可以由用户自定义。利用这两个函数,用户在进行跨多个显示器的显示时就可以利用每一个显示器的不同的显示特性。

  当然,并不是所有画图程序都必须调用这两个函数,这时你假设所有的显示器都使用同样颜色的分辨率。

7.EnumDisplayDevices(LPVOID lpReserved,int iDeviceNum,DISPLAY_DEVICE×pDisplayDevice,DWORD dwFlags)

  EnumDisplayDevices列出系统中某个显示设备(以iDeviceNum为序号)的信息。与GetMonitorInfo相比,GetMonitorInfo对应的显示器必须是Windows虚拟桌面的一部分,而EnumDisplayDevices可以列出包括处于独立显示模式下的系统所安装的所有显示器的信息。它返回的信息储存在DISPLAY_DEVICE结构中,包括显示设备名称、对显示设备的描述和显示设备的状态。

以上API使用举例:

1.判断窗口是否在有效坐标内:

HMONITOR hMonitor = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONULL);

如果hMonitor 为NULL,则窗口不在任何显示器中,此时窗口位置是无法看到的。

即使hMonitor 句柄有效,窗口也可能被挡在任务栏后面,因此还要继续拿到窗口所在屏幕的工作区域来判断窗口是否可见。

if (hMonitor)

{

  MONITORINFO info;

  info.cbSize = sizeof(MONITORINFO);

  if (GetMonitorInfo(hMonitor, &info))

  {

    //拿到窗口的坐标跟info.rcWork进来判断看窗口坐标是否有效

  }

2.判断矩形所在屏幕是否是主显示器:

HMONITOR hMonitor = MonitorFromRect(&rcTitle, MONITOR_DEFAULTTONULL);

if (hMonitor)

{

  MONITORINFO info;

  info.cbSize = sizeof(MONITORINFO);

  if (GetMonitorInfo(hMonitor, &info) && info.dwFlags & MONITORINFOF_PRIMARY)

  {

    //矩形大部分区域在主显示器当中

  }

一些要注意的点:

1.通过SystemParametersInfo(SPI_GETWORKAREA, 0, (PVOID)&rcWorkArea, 0)拿到的工作区域范围是主显示器的的工作区域,窗口如果需要支持多显示器就不能简单地这样计算工作区域。替换方案是使用GetSystemMetrics,通过用SM_XVIRTUALSCREEN、SM_YVIRTUALSCREEN、SM_CXVIRTUALSCREEN和SM_CYVIRTUALSCREEN参数分别可以得到虚拟桌面左上角的坐标和整个的长度和宽度(虚拟桌面即把多个显示器作为计算在一个作为一个桌面)。

2.单显示器下负坐标或大于SM_CXSCREEN和SM_CYSCREEN部分的窗口将被隐藏,而在多显示器模式下这些都是合法的,因此支持多显示器时对窗口位置的判断不建议直接使用(0,0)和(CXSCREEN,CYSCREEN)区域,而应该选择上面提到的虚拟桌面坐标。

3.在恢复原来存储的窗口位置之前,要检查一下这些窗口坐标的有效性。例如用上面提到的判断是否显示在某个显示器中,判断是否在显示器可工作区域中,判断是否在虚拟桌面坐标合理范围内。

4.一些Windows的API调用只处理的单显示器的情况,在调用时要考虑多显示器的情况。如通过CenterWindow(CWnd::GetDesktopWindow())来设置窗口居中屏幕时是以主屏幕居中的,如果需要考虑到主窗口在副显示器显示其弹出窗口要居中在副显示器的话,就要加入必要的处理,暂时没有找到CenterWindow传入参数使其在副显示器居中,这种办法是以副显示器中的主窗口居中或人为计算其在副显示器中的居中坐标SetWindowPos一把。

5.(此点为暂时现象总结,仍需进一步验证)切换显示器模式时,显示器的句柄会改变,即使所在显示器没有变,通过MonitorFrom*****得到的句柄也可能不同。如果窗口没有设置ToolWindow属性,在切换显示器模式(无论是双屏的切换或是单屏分辨率的改变),系统会主动改变窗口的位置来适应分辨率或是显示器的改变,这个位置改变不会引起OnWindowPosChanging消息,在OnWindowPosChanged中打日志看到,lpwndpos->flags为0x815,即SWP_NOSIZE & SWP_NOZORDER & SWP_NOACTIVATE & 0x800。

MFC