转载:开发日记地址 http://blog.sina.com.cn/s/blog_4078ccd60100049a.html

这几天研究了一下Windows系统的多显示器模式的编程,实现了Windows下支持10显示器模式的通用com组件,这里做一个整理和回顾,希望能对再这方面开发的兄弟们有些启发和帮助:

(一) Windows系统下的多显示器模式的原理

Microsoft新的操作系统(Windows 98\Windows 2000\Windows XP)内置了对多监视器的支持,即用户可以在一台计算机上安装多个显示卡并接上多个显示器,然后把这些显示器的显示区域组织成一个大的虚拟的Windows桌面。每一个显示区域的底部都有系统任务栏,我们可以在任何一个显示区域内增加桌面快捷方式,这样就可以在第一个显示区域上用Visual C++编程,同时在第二个显示区域上打开Internet Explorer上网——再也不用进行麻烦的切换了。

多显示器模式的原理实际上很简单,主要还是要靠操作系统的支持,比如WinXP就支持10个显示器,本文所使用的调试和开发环境都是以WinXP为主,其余的原理都相同慢慢调试就行了.

Windows提供的多显示器模式主要有以下三个功能:

1.更大的Windows桌面:在多显示器模式下,可以把多个显示器的显示区域结合在一起来显示Windows桌面,不管这些显示器的尺寸、物理位置、分辨率和刷新频率是否相同。当我们运行一个应用程序时,程序的主窗口可以位于任何一个显示器的显示区域内,也可以跨多个显示区域。我们也可以把一个程序的窗口从一个显示区域移到另一个显示区域中。

2. 屏幕复制或远程显示:我们可以让两个显示器显示相同的内容。在进行培训或者向众人进行演示时,这个特点是很有用的。利用这个特性,技术支持人员还可以对应用程序进行远程监视和调试。

3.多重独立显示:在以上的两种模式下,所有的显示区域都是Windows虚拟桌面的一部分,但是在多重独立显示模式下,应用程序访问的显示器并不属于Windows虚拟桌面。假设系统的第二个显示器是一个高分辨率的大尺寸显示器,我们可以把它用做CAD应用程序的专用显示。通过在CAD应用程序中调用新的Windows API,我们可以借助GDI在上面画图。独立显示器的显示区域没有桌面上的任何对象(任务栏和快捷方式),它与Windows桌面是独立的。这可以避免Windows桌面对应用程序输出的任何干扰,我们也不用担心会在无意中把其它的窗口拽到独立显示的显示区域中,这种方式就好像为应用程序提供了一个专用的显示器。

(二)理解虚拟桌面(Virtual Desktop)及其坐标

既然是要对多显示器模式进行编程和开发,那么我们就要首先理解Windows的虚拟桌面(Virtual Desktop)及其坐标了.这是我们编程开发的基础,理解了一切就很顺利了,几乎没有什么难度.

在单显示器系统中,实际Windows桌面的形状和大小与显示器是相同的。在多显示器模式下,每一个显示器实际上是一个大虚拟桌面的一个“子视窗”。

我们可以通过控制面板中的显示器属性对每一个显示器的显示区域的大小(分辨率)和相对位置进行调整,所有这些显示区域互相连接但并不重叠。图一中的显示器1是主显示器,主显示器的作用是确定虚拟桌面的坐标。不管主显示器的位置如何,它的显示区域的左上角的坐标定为虚拟坐标的零点(0,0),右下角的坐标是(X-1,Y-1)(假设主显示器的分辨率为X×Y),其余显示区域的坐标由它和主显示器的相对位置决定。通常虚拟桌面中显示区域的相对位置和实际显示器的物理相对位置是相同的。因为所有显示区域必须相连,因此可以用一个包含所有显示区域的最小矩形来表示虚拟桌面的大小。图一中的矩形边界代表了虚拟桌面的范围。

因为虚拟桌面中的坐标系统必须是连续的,因此第二个显示区域的坐标是主显示器的显示区域的继续。假设两个显示器都使用1024×768的分辨率,并且第二个显示器位于第一个显示器(主显示器)的正右方,则第二个显示区域的坐标是从(1024,0)到(2047,767)。

但是并不是所有的显示区域都具有相同的分辨率,而且这些显示区域也不一定是底边对齐的。就像图一中显示的那样,你真正能看到的有效显示区域是红色+兰色+紫色的不规则区域,而黄色区域虽然也属于虚拟桌面的一部分,但它不属于任何一个显示区域,这部分也叫做无效区域。如图一中所示,假设显示器1的分辨率是1024×768,显示器2的分辨率为800×600,显示器3的分辨率为640×480。零点的位置如图中所示,显示器1的坐标为(0,0)到(1023,767),显示器2的坐标为(-800,168)到(-1,767),显示器3的坐标是(1024,0)到(1663,479)。而(-800,0)到(-1,167)以及(1024,480)到(1663,767)这两块无效区域是不能显示任何信息的,系统不会允许用户把鼠标移动到这两个区域。需要注意的是无效区域是包括在虚拟桌面中的,因此图一中的虚拟桌面的大小是从(-800,0)到(1663,767)。

我在编程开发的过程中就使用了2个显示器,一个是自己的笔记本,分辨率为1024×768作为主显示器,另外一个由于比较懒,直接找了一个小巧的NEC12寸屏幕的小黑白显示器,不是为了别的搬着方便啊,这个NEC黑白支持分辨率800×600,强吧.

如下图我是直接设置了扩展桌面,两个显示器就都可以使用了

在这里要注意主显示器和副显示器的区别,其实主显示器和副显示器你是可以进行任意调整的.

(三)系统支持编程开发的API

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决定,规则与MonitorFromPoint相同。

3. HMONITOR MonitorFromWindow(HWND hwnd,DWORD dwFlags)

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

4. BOOL GetMonitorInfo(HMONITOR hMonitor,LPMONITORINFO lpmi)

GetMonitorInfo返回由hMonitor代表的显示器的有关信息,这些信息存储在指向MONITORINFO结构的指针——lpmi中。这些信息包括用RECT结构表示的显示器的显示区域的大小(如果这个显示器不是主显示器,RECT的坐标可能为负数),以及用RECT结构表示的显示器的工作区域的大小,工作区域是显示区域中除去系统任务栏和应用程序快捷方式栏所剩下的区域,还能够判断此显示器是否为主显示器,并返回一个标志。

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调用如SystemParametersInfo和GetSystemMetrics也加入了对多显示器模式的支持。比如调用GetSystemMetrics时,如果用SM_XVIRTUALSCREEN、SM_YVIRTUALSCREEN、SM_CXVIRTUALSCREEN和SM_CYVIRTUALSCREEN,得到的是虚拟桌面左上角的坐标和整个的长度和宽度。

我们在编程时特别要注意坐标的变化:首先单显示器下负坐标或大于SM_CXSCREEN和SM_CYSCREEN部分的窗口将被隐藏,而在多显示器模式下这些都是合法的。其次在确定应用程序窗口和对话框的位置时,要选择正确的显示器和正确的全局坐标(虚拟桌面坐标)。最后,在恢复原来存储的窗口之前,要检查一下这些窗口坐标的有效性。

这些都可以在微软的MSDN上去查出来,需要仔细的看一看,每个API都亲自试一试.

大家可以参考MSND的一篇文章"How to Exploit Multiple Monitor Support in Memphis and Windows NT 5.0",说的很详细.

(四)实现多屏幕编程的组件设计

这个组件参考了网上的许多资料,这里先向那些无私的同行表示感谢,我做的工作只是将他们的成果进行了系统化的整理......

组件的设计流程如下:

(1).初始化程序

Syntax:: MScreenInfo();

Description : 部件构造函数,初始化部件,获取系统屏幕信息,设置部件属性。

(2). 获取指定屏幕的宽度

Syntax: Short GetScreenWidth( Short ScreenNo) ;

Input : ScreenNo -- 指定屏幕的序号,0 -- m_monitorNum-1;

Return: Screen Width in Pixel;

Decription: 获取ScreenNo指定屏幕的宽度。

(3). 获取指定屏幕的高度

Syntax: Short GetScreenHeight( Short ScreenNo) ;

Input : ScreenNo -- 指定屏幕的序号,0 -- m_monitorNum-1;

Return: Screen Height in Pixel;

Decription: 获取ScreenNo指定屏幕的高度。

程序流程图:与图2相同,只是最后一步返回dm.dmPelsHeight.

(4). 获取指定屏幕的坐标原点-left

Syntax: Short GetScreenLeft( Short ScreenNo) ;

Input : ScreenNo -- 指定屏幕的序号,0 -- m_monitorNum-1;

Return: Screen Left in Pixel;

Decription: 获取ScreenNo指定屏幕的坐标原点-left。

程序流程图:与图2相同,只是最后一步返回dm.dmPosition.x.

(5). 获取指定屏幕的坐标原点-top

Syntax: Short GetScreenLeft( Short ScreenNo) ;

Input : ScreenNo -- 指定屏幕的序号,0 -- m_monitorNum-1;

Return: Screen Top in Pixel;

Decription: 获取ScreenNo指定屏幕的坐标原点-top。

程序流程图:与图2相同,只是最后一步返回dm.dmPosition.y.

(6). 获取主屏幕--Primary Screen

Syntax: Short GetPrimaryScreen();

Input: Null;

Return: Primary Screen No, 0 -- m_monitorNum - 1

Description: 获取主屏幕的序号。

程序流程:依次判断那一个屏幕的原点是(0, 0).

(五)组件开发的实现和主要代码

1 开发环境

操作系统: WindowsXP 编程环境: VC 6.0

2 组件接口如下

3 主要代码

// 获得显示器的数量

CMScreenInfoCtrl::CMScreenInfoCtrl()
{
InitializeIIDs(&IID_DMScreenInfo, &IID_DMScreenInfoEvents);

// 找出显示器的总数量
int i;
BOOL flag;
DISPLAY_DEVICE dd;

i = 0;
flag = true;
ZeroMemory(&dd, sizeof(dd));
dd.cb = sizeof(dd);
do
{
flag = EnumDisplayDevices(NULL, i, &dd, 0);
if (flag) i += 1;
} while (flag);

m_monitorNum = i; // 总数量
}

// 获得显示区宽度

short CMScreenInfoCtrl::GetScreenWidth(short ScreenNo)
{
if (ScreenNo < 0 || ScreenNo >= m_monitorNum) return 0;

BOOL flag;
DISPLAY_DEVICE dd;

ZeroMemory(&dd, sizeof(dd));
dd.cb = sizeof(dd);
flag = EnumDisplayDevices(NULL, ScreenNo, &dd, 0);

if (!flag) return 0;

DEVMODE dm;
ZeroMemory(&dm, sizeof(dm));
dm.dmSize = sizeof(dm);
flag = EnumDisplaySettings((char*)dd.DeviceName,ENUM_CURRENT_SETTINGS, &dm);

if (!flag) return 0;

return (short) dm.dmPelsWidth;
}

// 设置显示区宽度

void CMScreenInfoCtrl::SetScreenWidth(short ScreenNo, short nNewValue)
{
SetModifiedFlag();
}

// 获得显示区宽度

short CMScreenInfoCtrl::GetScreenHeight(short ScreenNo)
{
if (ScreenNo < 0 || ScreenNo >= m_monitorNum) return 0;

BOOL flag;
DISPLAY_DEVICE dd;

ZeroMemory(&dd, sizeof(dd));
dd.cb = sizeof(dd);
flag = EnumDisplayDevices(NULL, ScreenNo, &dd, 0);

if (!flag) return 0;

DEVMODE dm;
ZeroMemory(&dm, sizeof(dm));
dm.dmSize = sizeof(dm);
flag = EnumDisplaySettings((char*)dd.DeviceName,ENUM_CURRENT_SETTINGS, &dm);

if (!flag) return 0;

return (short) dm.dmPelsHeight;
}

// 设置显示区高度

void CMScreenInfoCtrl::SetScreenHeight(short ScreenNo, short nNewValue)
{
SetModifiedFlag();
}

// 获得显示区Y坐标

short CMScreenInfoCtrl::GetScreenTop(short ScreenNo)
{
if (ScreenNo < 0 || ScreenNo >= m_monitorNum) return -1;

BOOL flag;
DISPLAY_DEVICE dd;

ZeroMemory(&dd, sizeof(dd));
dd.cb = sizeof(dd);
flag = EnumDisplayDevices(NULL, ScreenNo, &dd, 0);

if (!flag) return -1;

DEVMODE dm;
ZeroMemory(&dm, sizeof(dm));
dm.dmSize = sizeof(dm);
flag = EnumDisplaySettings((char*)dd.DeviceName,ENUM_CURRENT_SETTINGS, &dm);

if (!flag) return -1;

return (short) dm.dmPosition.y ;
}

// 设置显示区Y坐标

void CMScreenInfoCtrl::SetScreenTop(short ScreenNo, short nNewValue)
{
SetModifiedFlag();
}

// 获得显示区X坐标

short CMScreenInfoCtrl::GetScreenLeft(short ScreenNo)
{
if (ScreenNo < 0 || ScreenNo >= m_monitorNum) return -1;

BOOL flag;
DISPLAY_DEVICE dd;

ZeroMemory(&dd, sizeof(dd));
dd.cb = sizeof(dd);
flag = EnumDisplayDevices(NULL, ScreenNo, &dd, 0);

if (!flag) return -1;

DEVMODE dm;
ZeroMemory(&dm, sizeof(dm));
dm.dmSize = sizeof(dm);
flag = EnumDisplaySettings((char*)dd.DeviceName,ENUM_CURRENT_SETTINGS, &dm);

if (!flag) return -1;

return (short) dm.dmPosition.x ;
}

// 设置显示区X坐标

void CMScreenInfoCtrl::SetScreenLeft(short ScreenNo, short nNewValue)
{
SetModifiedFlag();
}

// 获得主显示区

short CMScreenInfoCtrl::GetPrimaryScreen()
{
// TODO: Add your property handler here
if (m_monitorNum <= 1) return 0;

// if the Screen Top = 0 and Left = 0, then, it's the Primary Screen
short i;
for (i=0; i<m_monitorNum; i++)
{
if (GetScreenTop(i)==0 && GetScreenLeft(i)==0) return i;
}
return 0;
}

// 设置主显示区

void CMScreenInfoCtrl::SetPrimaryScreen(short nNewValue)
{
SetModifiedFlag();
}

关键的代码基本就是这些了.

(3)组件发布

直接编译成为ocx组件,取名为MutlScreen.ocx

使用regsvr32.exe注册一下就可以使用了.

(六)分屏输出组件的应用

我们使用最简单的VB来编写一个小程序实现

1 建立一个VB的工程,引用组件,建立两个form,分别为frmCtl,和frmOutScreen,我们将frmOutScreen输出到第二个屏幕上;

2 组件使用的代码如下:

Function initMotion()

numScreen = frmCtl.MScreenInfo1.MonitorNum
primaryScreen = frmCtl.MScreenInfo1.primaryScreen
wScreen1 = frmCtl.MScreenInfo1.screenWidth(0)
hScreen1 = frmCtl.MScreenInfo1.screenHeight(0)
topScreen1 = frmCtl.MScreenInfo1.ScreenTop(0)
leftScreen1 = frmCtl.MScreenInfo1.ScreenLeft(0)
wScreen2 = frmCtl.MScreenInfo1.screenWidth(1)
hScreen2 = frmCtl.MScreenInfo1.screenHeight(1)
topScreen2 = frmCtl.MScreenInfo1.ScreenTop(1)
leftScreen2 = frmCtl.MScreenInfo1.ScreenLeft(1)
End Function

3 frmOutScreen的代码

Private Sub Form_Load()

frmOutScreen.Left = leftScreen2 * 15 + 1
frmOutScreen.Top = 8

frmOutScreen.WindowState = 2
frmOutScreen.WindowsMediaPlayer1.Left = 0
frmOutScreen.WindowsMediaPlayer1.Top = 0
frmOutScreen.WindowsMediaPlayer1.Width = frmMediaplay.Width
frmOutScreen.WindowsMediaPlayer1.Height = frmMediaplay.Height
frmOutScreen.Refresh

End Sub

4 编译运行就可以实现frmOutScreen在第二个显示器的输出,你可以加入你的web组件实现浏览器在第二个屏幕输出,看如下我的执行结果

好了,就这些了,要实现更复杂的功能就需要你自己一点点的去调试了.

开发日记后记

断断续续大约用了一周的零散时间完成了多显示器支持的功能,达到可以使用的目的,并可以分享给大家,再此作几点总结作为结束。

1 不熟悉的技术先不要急着去进行代码的实现,要先搞懂原理;

2 多使用现在已经有得资料,主要是系统帮助,就如MSDN,已经提供了很多有用的东西;

3 要多使用互联网查找资料,尤其是国外相关网站的资料;

4 要熟悉自己使用的设备;

5 胆大心细,多进行实验;

6 不懂就问啊,实际上有许多人都已经作了N多的工作了啊

7 最后一点,工作好,休息好,心情好,很重要,哈哈哈

8 别忘记分享给大家。。。。。

结束,开始休息

(一只老虎写于日记之后)

转载于:https://www.cnblogs.com/xuehuzaifei/p/3222807.html

Windows系统下多显示器模式开发相关推荐

  1. Windows系统下安装配置 MinGW-w64 开发环境

    MinGW.MinGW-w64 简介 MinGW(全称为,Minimalist GNU for Windows),它实际上是将经典的开源 C语言编译器 GCC 移植到了 Windows 平台下,并且包 ...

  2. php larval框架运行环境,4种Windows系统下Laravel框架的开发环境安装及部署方法详解...

    1.准备工作 1.1PHP集成环境 这里我们使用的是XAMPP,XAMPP是一个功能强大的建站集成软件包,采用一键安装的方式,包含PHP7.0.Mysql.Tomcat等.最新版下载地址:PHP 5. ...

  3. Windows系统下QT+OpenCasCAD仿真开发

    背景 最近开发了一个六自由度机械臂调姿平台的控制软件,集成了API激光跟踪仪和KUKA机器人,实现了根据产品的测量位姿驱动仿真环境中模型并且实现模型间的碰撞检测. 其中KUKA机器人的控制可以参考笔者 ...

  4. 如何正确入门Windows系统下驱动开发领域?

    [作者] 猪头三 作者网站: http://www.x86asm.com 原文链接: http://blog.csdn.net/Code_GodFather/...0/5975901.aspx [贡献 ...

  5. Java windows系统下JDK开发环境配置和环境变量配置

    一.JDK下载地址 http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 二.Ecli ...

  6. 游戏开发jenkins杂谈系列:windows系统下修改系统时间触发Build periodically设置的定时任务,往前调时间但不再执行定时任务的解决办法

    网上搜了一堆帖子,没有讲到我遇到的这个问题的: windows系统下修改系统时间执行定时任务,例如任务在2020.11.18. 20:36分已经触发了,我将时间调回至2020.11.17 20:35, ...

  7. windows 10下的kiosk模式

    windows 10下的kiosk模式可以保证windows 10 开机自动运行某个程序,且全屏,除了按alt + del + ctrl组合键退出外,按鼠标.键盘不能见到任何window系统下的任何界 ...

  8. python修改文件格式为unix_软件测试技术之如何用python在Windows系统下,生成UNIX格式文件...

    本文将带你了解软件测试技术之如何用python在Windows系统下,生成UNIX格式文件,希望对大家学测试技术有所帮助 如何用python在Windows系统下,生成UNIX格式文件 平时测试工作中 ...

  9. 在Windows系统中搭建PHP环境,PHP环境搭建-Windows系统下PHP环境搭建

    1.PHP环境搭建的前提是 Apache HTTP Server (Apache 服务器)已经安装部署成功,并可以正常访问到服务器的主页面.Apache HTTP Server 的安装部署已经在上一篇 ...

最新文章

  1. Python __dict__属性详解
  2. runLoop和runtime的分析
  3. apache ant 安装_SAP Hybris使用recipe进行安装时,是如何执行ant命令的?
  4. react学习笔记(二)编写第一个react组件
  5. java comparator 降序排序_【转】java comparator 升序、降序、倒序从源码角度理解
  6. Android APP 引导页实现-第一次应用进入时加载
  7. WEB文件上传之apache common upload使用(一)
  8. Linux 学习_在Linux下面安装tomcat
  9. 国货在崛起,八成都做对了这件事
  10. python正则表达式怎么表示零个或多个字符_VBA中正则表达式之分组
  11. 验证邮箱是否合法php,验证邮箱字段是否合法
  12. 如何化身BAT面试收割机?都是精髓!
  13. android mm 修改路径,Android 编译系统模块
  14. TDD、BDD、ATDD、DDD 软件开发模式
  15. 深度学习模型参数初始化的方法
  16. java 1.8 64_JDK 1.8 64位 下载 安装 配置
  17. 【解决】Git:hint:Pulling without specifying how to reconclie divergent branches is...
  18. 基于单片机的电子秤(数码管)系统设计(#0416)
  19. 第13章Stata Logistic回归分析
  20. rknn模型转换问题记录

热门文章

  1. 协议森林13 9527 (DNS协议)
  2. MySQL安装错误:/usr/local/mysql/libexec/mysqld: unknown option '--skip-federated'
  3. 互联网业界7月三件大事
  4. if java_Java 条件语句
  5. Shell脚本中$0、$?、$!、$$、$*、$#、$@
  6. java程序编写九九乘法表_用面向对象的方法编写的九九乘法表java代码的编写
  7. oracle语句优化pl sql语句,求oracle插入初始数据pl/sql语句优化,该怎么处理(2)
  8. 组合模式java怎么获取钥匙_java中组合模式详解和使用方法
  9. java finereport_java报表FineReport_JS整理
  10. springboot创建多个对象