Windows 多显示器下的编程

概要
本文分步骤介绍如何在 Windows XP 中配置和使用多台监视器。

Windows XP 可通过使用多台监视器扩大您的桌面,进而提高您的工作效率。一台计算机上可连接多达十台单独的监视器,借助于这些监视器,您的桌面可以有充足的空间容纳大量的程序或窗口。通过将项目从一台监视器移动到另一台监视器或将其扩展到多台监视器,您可以轻松地同时执行多项任务。您可以在一台监视器上编辑图像或文本,同时在另外一台监视器上查看 Web 活动。或者,您还可以打开一篇长文档的多个页,将这些页拖动到多台监视器中,以便轻松查看文字布局和图像。您亦可以将 Microsoft Excel 电子表格在两台监视器间拖动,这样无需使用滚动条便可以查看多个列的信息。

连接多台监视器

<script type="text/javascript">loadTOCNode(2, 'summary');</script> 将一台监视器用作主监视器,它将在您启动计算机后显示“登录”对话框。此外,多数程序会在您首次打开时在主监视器上显示程序窗口。您可以为每台监视器选择不同的屏幕分辨率和颜色质量设置。可以将多台监视器连接到各自的视频适配器,也可以连接到支持多输出的一个单独的视频适配器。若要连接多台监视器并进行配置,请按照下列步骤操作:
1.单击“开始”,然后单击“控制面板”。
2.单击“外观和主题”,然后单击“显示”。
3.在“设置”选项卡上,单击“识别”,以在每台监视器上显示一个大型数字。此数字表明各监视器对应的图标。
4.单击监视器图标并将该图标拖动到不同的位置上,这些位置分布代表了您希望在两台监视器间移动项目的方式。然后单击“确定”或“应用”来查看更改。
注意:图标位置决定了您在两台监视器间移动项目的方式。例如,如果您正在使用两台监视器,并希望通过左右拖动的方式将项目从一台监视器移到另一台监视器中,请将图标并列排放。若要以上下拖动的方式在两台监视器间移动项目,请将图标上下排放。图标的位置不必与监视器的物理位置对应。即使您的监视器并排放置,也可以将图标上下排列。

 

更改主监视器

<script type="text/javascript">loadTOCNode(2, 'summary');</script>
1.在“显示属性”对话框的“设置”选项卡上,单击代表要指定为主监视器的监视器的图标。
2.通过单击选中“使用该设备作为主监视器”复选框。注意,如果您选中的监视器图标当前已设置为主监视器,则此复选框不可用。

在多台监视器中查看同一桌面

<script type="text/javascript">loadTOCNode(2, 'summary');</script>
1.在“显示属性”对话框的“设置”选项卡上,单击代表除主监视器外还需要使用的其他监视器的图标。
2.通过单击选中“将 Windows 桌面扩展到该监视器上”复选框。启用此功能后,您可以在屏幕上将项目拖动到其他监视器上,也可以调整窗口的大小,将窗口扩展到多台监视器上。

在多台监视器间移动项目

<script type="text/javascript">loadTOCNode(2, 'summary');</script>
1.在“显示属性”对话框的“设置”选项卡上,单击“识别”,以在每台监视器上显示一个大型数字。此数字表明各监视器对应的图标。
2.单击监视器图标并将图标拖动到不同的位置上,这些位置分布代表了您希望在两台监视器间移动项目的方式。然后单击“确定”或“应用”。
3.在屏幕上拖动桌面上的项目,直到它出现在另一台监视器上。也可以调整窗口的大小,将窗口扩展到多台监视器上。

使用双视屏

<script type="text/javascript">loadTOCNode(2, 'summary');</script>在多数便携式计算机和某些台式计算机(一个视频卡上带有两个视频端口)上,您可以使用双视屏将显示区域扩展到另一台监视器上。双视屏与多台监视器的功能十分相近,不同之处是您无法选择主显示器。在便携式计算机上,主监视器始终是 LCD 显示屏幕。在台式计算机上,第一个视频输出端口连接的是主监视器。当您连接了第二台监视器并打开计算机后,使用“控制面板”中的“显示”工具对您的设置进行配置,步骤与配置多台监视器相同。您可以在有接埠或无接埠的便携式计算机上使用双视屏。

 

以上摘自【如何在 Windows XP 中配置和使用多台监视器】
http://support.microsoft.com/kb/307873/zh-cn

Win 98之后开始支持多显示器(Multiple Display)。Windows多显示器情况分为两种,一是单显卡多显示器,一是多显卡多显示器。前一种的多显示器依靠显卡的多个接口,一般显卡有D-Sub接口+DVI-I接口(相关资料请看显卡接口一文)。后一种通过不同的显示器接到不同的显卡来实现。 多显示器的情况就给编些GUI带来了困难,但是多显示器也带来了好处,可以将多个显示器组合起来做成大面积屏幕,可以看电影了,只是这样成本高了点。

 
一般PC配有一块显卡和一台显示器,但是可以在主板上再插一块显卡,然后使用两个显示器。后加入的显示器被Windows设置为主显示器(主屏),另外的显示器叫做副显示器(副屏)。其中主屏的坐标被设为(0,0),打开控制面板->显示,打开显示对话框,进入设置面板,可以看到多个显示器,同时可以知道哪个是主屏,哪个是副屏,并且可以通过鼠标拖动副屏来调整副屏相对于主屏的位置。如果只有一块显卡,该显卡有多个输出接口,可以将不同的显示器接到不同的输出接口来实现多屏,此时主屏不能选择,笔记本上主显示器是LCD(液晶屏),台式机为连接显卡第一个输出接口的显示器。
 
下面就我实际开发中遇到的问题,结合《Programming for Multiple Monitors in Windows 98》这样一篇文章,讲述一下多显示器情况下的编程。
为方便描述,现假设PC原有一台显示器,坐标分辨率为(1024*768),然后再添加一显卡和显示器,分辨率为(800*600),这样(800*600)的显示器成为主屏,(1024*768)的显示器成为副屏。下面以此例进行描述,为了便于理解,有些以屏幕取代显示器:
 
一、虚拟桌面(Virtual desktop)
一台显示器时,虚拟桌面就是所看到的桌面,坐标(0,0),大小(1024,768)。2台显示器时,副屏坐标为(800,0),大小为(1024*768),虚拟桌面坐标(0,0),大小 (1824,768),此时的虚拟桌面并非你看到的2个显示器中的任何一个。示意图如下:
 
由上图可见,Virtual desktop是两者拼接起来的结果。
二、多显示器相关API
1.       数据类型
HMONITOR 显示器的句柄
MONITORINFO 显示器信息
MONITORINFOEX 显示器信息(上面结构的扩展)
typedef struct tagMONITORINFO
{
DWORD cbSize;
RECT rcMonitor;
RECT rcWork;
DWORD dwFlags;
} MONITORINFO, *LPMONITORINFO;
typedef struct tagMONITORINFOEXA
{
MONITORINFO;
TCHAR szDevice[CCHDEVICENAME];
} MONITORINFOEX, *LPMONITORINFOEX;
 
2.       获得某点所在的屏幕
HMONITOR MonitorFromPoint(POINT pt,DWORD dwFlags);
Pt为点坐标
dwFlags可取下面的值,表示没有任何显示器包含该点时,返回什么值MONITOR_DEFAULTTONULL(返回NULL)
MONITOR_DEFAULTTOPRIMARY(返回主屏)
MONITOR_DEFAULTTONEAREST(返回最靠近该目标的屏幕)
3.       获取某矩形区域,窗口所在屏幕
HMONITOR MonitorFromRect(LPRECT lprc,DWORD dwFlags);
lprc为指定矩形区域
dwFlags含义与2相同
 
HMONITOR MonitorFromWindow(HWND hWnd,DWORD dwFlags);
hWnd为指定窗口
dwFlags含义同2
这两个接口返回包含目标最多的屏幕,如果没有任何屏幕包含目标,那么根据第二个参数返回具体的值。
4.       获取显示器信息
BOOL GetMonitorInfo( HMONITOR hmonitor, LPMONITORINFO lpmi);
hmonitor 显示器句柄
lpmi MONITORINFO或MONITORINFOEX结构指针
 
注意MONITORINFOEX是MONITORINFO的超集,作为参数时需先设置它的cbSize属性,使它等于自身的大小。
 
可以这样使用
MONITORINFOEX mix;
mix.cbSize = sizeof(mix);
GetMonitorInfo(hMonitor, (LPMONITORINFO)&mix);
5.       获取显示器的宽和高
GetSystemMetrics(int nIndex);
使用SM_CXSCREEN,SM_CYSCREEN可以获取主屏大小(800, 600)
如果要获得虚拟桌面的大小,需要使用SM_CXVIRTUALSCREEN,SM_CYVIRTUALSCREEN,此时获取的大小为(1824, 768)
6.       获取工作区大小
BOOL SystemParametersInfo(
 UINT uiAction, // system parameter to retrieve or set
 UINT uiParam,   // depends on action to be taken
 PVOID pvParam, // depends on action to be taken
 UINT fWinIni    // user profile update option
);
 
上面函数对使用uiAction为SPI_GETWORKAREA,SPI_SETWORKAREA时,做了改进,以前总是对主屏进行操作。 现在要获取其他屏幕的属性,可以使用GetMonitorInfo,要设置其他屏幕可以在uiAction为SPI_SETWORKAREA时,将RECT作为pvParam,这样就可以设置包含该RECT的显示器。
7.       便利相关显示器
BOOL WINAPI EnumDisplayMonitors(
HDC hdc,
LPCRECT lprcClip,
MONITORENUMPROC lpfnEnum,
LPARAM dwData);
 
hdc 设备环境
lprcClip 矩形区域
lpfnEnum 回调函数
dwData 传给回调函数的参数
 
回调函数需要有下面的形式
BOOL CALLBACK MonitorEnumProc(
HMONITOR hmonitor,
HDC hdcMonitor,
LPRC lprcMonitor,
DWORD dwData);
 
下面引用《Programming for Multiple Monitors in Windows 98》的原文说明
The MONITORENUMPROC callback is called for each monitor that intersects the visible region of hdc and the lprcClip parameter. If the lprcClip parameter is NULL, then no additional clipping is performed. The dwData parameter is for user-defined data and is passed through to the dwData parameter in the callback function. MONITORENUMPROC is a user-defined callback function that must have the following signature:
BOOL CALLBACK MonitorEnumProc(
HMONITOR hmonitor,
HDC hdcMonitor,
LPRC lprcMonitor,
DWORD dwData);
If the hdc passed to EnumDisplayMonitors was NULL, then hdcMonitor is NULL. Otherwise, hdcMonitor contains a valid device context whose color attributes match the display monitor defined by hmonitor. If hdcMonitor is NULL, then lprcMonitor is in virtual-desktop coordinates. It is important to note that no application needs to use EnumDisplayMonitors to handle monitors of differing bit depths, since Windows automatically dithers high-color images on lower-color devices. You should use this function only if you want to do custom dithering to ensure the best possible display. In order to help you understand exactly how EnumDisplayMonitors is used, I will present a couple of code snippets illustrating common usage scenarios.
One use for EnumDisplayMonitors is to make sure that images are drawn optimally on lower-color devices. The best way to do this is to change how your window's WndProc handles the WM_PAINT message.
case WM_PAINT:
HDC hdc;
hdc = BeginPaint(hWnd,
&paintStruct);
EnumDisplayMonitors(hdc, NULL, monitorEnumPaintProc, 0);
EndPaint(&paintStruct);
Inside your callback, you query the passed device context to determine its capabilities and display area and do your drawing accordingly.
8.       枚举显示器设备
BOOL WINAPI EnumDisplayDevices(
PVOID Unused,         //Reversed, Set to NULL
DWORD iDevNum, //index of monitor to query information
PDISPLAY_DEVICE lpDisplayDevice, //output information
DWORD dwFlags); //set to 0
其中iDevNum是指查询设备的索引,以0为基数,如果有该设备存在则填充lpDisplayDevice,返回TRUE;如果没有该设备,则返回FALSE。如果要枚举所有的设备,那么需要让iDevNum从0开始一直调用该函数,知道返回FALSE为止。
第三个参数的结构定义如下:
typedef struct _DISPLAY_DEVICE {
DWORD cb;
BYTE DeviceName[32];    //name of the device. Same as value get by GetMonitorInfo
BYTE DeviceString[128]; //Firendly name of the device
DWORD StateFlags; //不详
} DISPLAY_DEVICE, *PDISPLAY_DEVICE,
*LPDISPLAY_DEVICE;
StateFlags参数含义不详,但是可以通过它的取值看出些汉以来:
#define DISPLAY_DEVICE_ATTACHED_TO_DESKTOP
0x00000001
#define DISPLAY_DEVICE_MULTI_DRIVER
0x00000002
#define DISPLAY_DEVICE_PRIMARY_DEVICE
0x00000004
#define DISPLAY_DEVICE_MIRRORING_DRIVER
0x00000008
#define DISPLAY_DEVICE_VGA_COMPATIBLE
0x00000010
 
下面引用《Programming for Multiple Monitors in Windows 98》的原文说明
Since EnumDisplayDevices allows you to query devices that aren't part of the desktop, your application can use monitors in an exclusive manner, without having to share the screen with the desktop. To do this, find a device that doesn't have the DISPLAY_DEVICE_ATTACHED_TO_DESKTOP bit set and call the Win32 API function CreateDC, passing the name returned in the DeviceName member of lpDisplayDevice. If you create a device context in this manner, you should delete it when you're done with it by calling DeleteDC.
9.        
三、GUI编程注意事项
GUI编程使用了显示器,此时如果不考虑多显示器情况,可能出现显示问题。例如用户当前将任务条拖放到副屏上,在副屏上进行操作,运行程序后程序出现在主屏上,这会让用户感觉程序没有运行,但这并不是主要问题。 当用户将主屏上的窗口拖放到副屏后,点击某个按钮或菜单,此时程序进行了动态的位置调整,坐标设想为(0,0),那么窗口又从副屏跑回主屏了,此时我想用户会觉得恼火。 具体的做法可以在窗口创建的时候和窗口移动的时候,获取窗口所处屏幕信息,并记录下当前显示屏的原点。例如:
CPOINT m_ ptOrg; //在.h文件中声明变量
 
//在WM_MOVE或者窗口初始化时调用, 保存原点信息
HMONITOR hWndMonitor = MonitorFromWindow(GetSafeHwnd(), MONITOR_DEFAULTTONULL);
         MONITORINFOEX monitorInfo;
         if (hWndMonitor == NULL) return;
         monitorInfo.cbSize = sizeof(MONITORINFOEX);
                  GetMonitorInfo(hWndMonitor, &monitorInfo);
 
m_ptOrg.x = monitorInfo.left
m_ptOrg.y = monitorInfo.top
 
当调用SetWindowPos时,需要注意此时传递的RECT的原点应该以哪个计算。
四、多窗口的一些有趣的事情
一般第二个窗口的原点横坐标是第一个窗口的宽,任务栏和桌面显示在主屏上。默认情况下,副屏在主屏的右边,鼠标可以向左移动出主屏外。并且可以将任务栏拖到副屏中。
 
多窗口时,Control Panel会记住上次的位置,下次打开的时候还在原来的屏幕上出现。例如打开Control Panel,此时显示在主屏上,如果将它移动到副屏上,然后关闭,再次打开的时候会出现在副屏上。

 



已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页