在Delphi中开发使用多显示器的应用程序
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在Delphi中开发使用多显示器的应用程序相关的知识,希望对你有一定的参考价值。
参考技术A Windows可以将多个显示器映射为虚拟桌面 使我们可以利用这一点设计出方便工作的应用程序 例如PowerPoint就充分发挥了双显示器的优势(大多数的笔记本电脑都支持) 它可以在一个显示器上播放幻灯片 而在另一个显示器上显示备注 可以控制播放的进程 使使用者做商务演说的时候非常等心应手 那么我们怎么开发这种应用程序呢?这篇文章将向你展示如果用Delphi实现使用多显示器的应用程序
Windows还支持克隆显示方式 每个显示器输出同样的内容 这对某些应用也是有意义的 还有些显卡虽然也支持两个显示器 不过他们并不是真正意义上的多显示器 而是虚拟高分辨率显示模式(如 × 或者 × ) 通过显卡将画面分别显示到两个显示器上 这两种显示模式都不是本文介绍的zhongdian 而且也非常简单 所以我们也就不再赘述了
Windows最多支持 个显示器 Windows将所有显示器映射为一个大的虚拟桌面 可以将显示器理解为桌面某个局部的视图 在显示属性中可以根据显示器的物理位置任意排布这些显示器 如果显示器的排列不规则 虚拟桌面上的某些部分可能无法显示在任何一个显示器上 为了不使一个窗体显示在两个显示器之间等原因的考虑 Windows将一个显示器作为主显示器 启动计算机时 登录对话框就显示在主显示器中 绝大多数程序启动示 都会显示在主监视器中
根据上述介绍 不难发现几个重要的概念 桌面 显示器 主显示器等 首先必须先弄清楚这些概念以及他们之前的关系 这是掌握多显示器应用程序开发方法的重点 理解了这些概念 其他的部分就非常好理解了
桌面实际上是指Windows可显示的逻辑区域 实际上是可以将一个窗体显示到桌面之外的 然而这并不是说桌面的所有部分都会显示在某台显示器上(原因如前所述) 但反过来说 任何一个显示器显示的内容都必然是桌面的一部分
桌面是一个矩形区域 可以通过顶点坐标(Top Left)和宽高来描述桌面的尺寸 为什么还需要顶点坐标呢?因为顶点坐标不是想当然的( ) 那么( )在哪里呢?说来话长 还是让我们先来回顾一下刚才提到地一个概念——主显示器吧 Windows希望一般的程序初始的时候显示到主显示器 因为人们习惯于关注一个离自己最近的显示器 而Windows也不可能强制用户把最左边一个显示器作为主显示器 这样一来应用程序为了把自己显示到主显示器 就需要费脑筋的计算 然而 多数用户都只有一个显示器(两个显示器实在太占地方了) 而一般的应用程序也不希望大费周章的去计算主显示器在哪里 自己应该显示在什么位置 所以Windows提出了一个合理的解决方案 以主显示器的顶点坐标作为坐标系的原点 这样一来 普通的程序之需要想在单显示器环境中一样考虑问题就可以了
显示器是桌面的局部视图 就好像透过窗户看窗外的风景 站在不同的窗前就可以看到不同的画面 同样的 显示器也是一个矩形区域 同样可以通过顶点坐标(Top Left)和宽高来描述它的尺寸 顶点坐标是相对于桌面坐标系原点的 也就是相对于主显示器的顶点
工作区的概念比较简单 它是指显示器中除了任务条和其他停靠在桌面上的窗体之外的矩形区域
Windows为多显示器应用程序的开发提供了一组API VCL将这些API封装起来 非常自然的融入整个Framework之中 使得开发多显示器应用程序变得非常简单 下面就介绍与之相关的内容
在VCL之中大家最熟悉的恐怕非TCustomForm莫属了 它是所有窗体的基类 TCustomForm的Position属性用来设置窗体的现实位置 其可选值中有两个是值得关心的 一个是poScreenCenter 当Position属性被设置成poScreenCenter时 窗体会显示到主显示器的中央 另一个是poDesktopCenter 当Position属性被设置成poDesktopCenter时 窗体显示在整个桌面的中央 如果把这个属性设成poDesktopCenter 程序又运行在一个有多台显示器的系统上 那么这个窗口就会显示在两个显示器之间 会给用户带来不必要的麻烦 因此即使我们的程序不是针对多显示器而设计的 也应该细心处理这个值 另外一个属性是DefaultMonitor 它的作用与Position有些类似 决定窗口最初显示在哪个显示器内 它有四个备选值 dmDesktop dmPrimary dmMainForm和dmActiveForm 他们的含义如下
Value Meaning dmDesktop 不特别处理 dmPrimary 将窗体显示到第一个显示器上 这又是一个陷阱 字面上理解是主显示器 而事实上它是指Screen Monitor[ ]这个显示器 dmMainForm 将窗体显示到主窗体所在的显示器 dmActiveForm 将窗体显示到桌面上活动窗体所在的显示器
TCustomForm还有一个只读的共有属性(没有Published)Monitor 它提供了访问窗体所在显示器实例的引用 这个值与DefaultMonitor是有紧密的关联的
那么怎么在使窗体在不同的显示器之间移动呢?这并不困难 估计你也想到了 这里介绍两种方法
第一 可以设置TCustomForm的Top和Left使窗体显示在桌面的任意位置 正如前面所述 桌面是由所有显示器组成的 它们有共同的坐标系 所以可以根据显示器的逻辑位置决定窗体的位置 现在的问题是如何获得每个显示器的逻辑位置和尺寸 后面就会介绍
第二 可以调用TCustomForm的MakeFullyVisible方法将窗体完全显示到指定的显示器之中 可以通过这个方法避免窗口在两个显示器上各显示一部分
刚才我们提出了一个问题 如何获得每个显示器的逻辑位置和尺寸 为了解答这个问题 需要再介绍连个类 TScreen和TMonitor
TScreen 描述与显示设备有关的一些信息 我们主要关心与显示器逻辑位置和尺寸有关的信息 其他方面的内容可以在Delphi的文档中获知 在程序运行的时候VCL自动创建一个TScreen的实例——全局变量 所以通常情况下程序是不需要实例化TScreen的
TScreen有一组形如Desktop*的属性 这些属性描述了整个桌面的尺寸和各顶点坐标 还有对开发多显示器应用程序有重要意义的连个属性 MonitorCount和Monitors 通过这两个属性我们可以枚举出系统中所有的显示器(TMonitor)的实例 每个实例都反映了相应显示器的相对位置和分辨率等信息(后文会详细说明)
在TScreen的众多属性之中 我们会找到Height和Width这两个属性 要特别警惕它们不是指整个桌面的尺寸 而是指主显示器的高度和宽度 这非常容易让人产生错觉 无以为是整个桌面的尺寸 与之类似的还有形如WorkArea*的一组属性 它们描述了主显示器的工作区域的尺寸和各顶点坐标 是不是觉得少了什么?为什么没有获取主显示器相对位置的属性?原因就像前面所说的 Windows是以主显示器的左上角为坐标系原点的 所以主显示器的相对位置必然是( )
除了这些属性之外 还要介绍TScreen的三个成员函数 MonitorFromPoint MonitorFromRect和MonitorFromWindow 顾名思义 他们分别是获取个坐标 某个区域和某个窗口所在的显示器的实例 在实际的开发中可能也会用到
最好 再来看看TMonitor类 它封装了物理显示器的有关属性——这些属性都是只读的 下表简单介绍了这些属性的含义 它们对编写多显示器应用程序非常有用
属性 说明 Handle 获取该显示器的Windows句柄 MonitorNum 获取显示器的编号 Primary 获取该显示器是否是主显示器 又且仅有一个显示器的Primary是True Top 获取显示器的上边界 Left 获取显示器的左边界 Height 获取显示器的高度 Width 获取显示器的宽度 BoundsRect 获取显示器的对应桌面的区域 它与上面四个属性是等价的 WorkareaRect 获取显示器的工作区对应桌面的区域清楚地了解了TScreen和TMonitor之后 前面的问题也就自然解决了 到这里 本文已经介绍了开发多显示器应用程序所需的全部知识 相信你可以利用这些知识开发出非常实用的软件产品
附 你可以下载一个DEMO 帮助理解本文
lishixinzhi/Article/program/Delphi/201311/24817
Synchronize执行过程
Synchronize执行过程及原理
在windows原生应用程序开发中,经常伴随多线程的使用,多线程开发很简单,难点就是在于线程的同步,在Delphi中提供了VC中不具备的一个过程Synchronize,使用起来非常方便,解决了很多VC开发中碰到的常见问题,但是在看了很多Delphi代码后,发现很多人对于Synchronize的理解还是有问题的,不能很好地正确使用Synchronize过程,本文对Synchronize过程的使用提出一些个人的见解,供大家参考。
在VC中使用多线程,由于MFC或VC本身的特点,VC人员在更新窗口界面和工作线程的关系上分的是比较清楚的,但是Delphi里由于VCL提供的方便特性,造成大量人员在工作线程中更新窗口界面,给系统稳定带来潜在的危险,应该说比较严格的方法应该是永远让更新窗口界面的工作仅由主线程完成,工作线程仅仅做后台的一些工作,当工作线程的结果需要反馈到界面上来的时候,应该使用各种同步对象(临界区、互斥量)等来进行同步,然后让主线程更新窗口界面。
这里由于主要讲解Synchronize的使用,使用同步对象更新的方法就不说了,首先说一下在VC中比较接近Synchronize过程的SendMessage函数,其实在Delphi中也可以使用SendMessage来实现线程同步,实现通知主线程更新窗口界面,只是没有Synchronize过程用起来那么方便。
SendMessage函数的工作过程是比较复杂的,有兴趣的朋友可以多查查SendMessage函数及其同多线程的关系,如果在工作线程中调用SendMessage函数,工作线程会挂起,然后等待主线程空闲,主线程空闲开始处理接收到的消息,处理完成把结果返回,工作线程接收到返回结果后继续运行,也就是消息处理函数是在主线程中运行的,而且工作线程会挂起等待SendMessage函数返回即消息处理函数处理完成才会继续往下执行,在VC开发中这种方式也是经常被使用的。
理解了SendMessage函数工作过程,现在来说一下Synchronize过程的工作过程,Synchronize过程是Delphi底层实现的,其工作原理非常类似SendMessage函数,但是使用起来要比SendMessage函数方便易用,Synchronize过程开始时,也是会挂起当前的工作线程(使用的同步对象),然后让主线程执行Synchronize参数中具体的过程proc,工作线程等待proc执行,proc执行结束返回到工作线程,工作线程再继续执行。
总之,如果在TThread的Execute过程中仅仅调用一个Synchronize过程,Synchronize参数中的过程都是在主线程中执行的,等于没有使用多线程。也就是要细化Synchronize参数中的过程proc,仅让proc执行一些更新界面的工作,不能把真正耗时的动作放在proc中,不然使用多线程没有任何意义。
附:Delphi底层中Synchronize过程的实现过程简介
Synchronize过程的核心函数是System.Classes.pas中的过程class procedure TThread.Synchronize(ASyncRec: PSynchronizeRecord; QueueEvent: Boolean = False);这个函数中是通过调用WakeMainThread事件方法调用具体过程proc的,WakeMainThread事件的注册在Vcl.Forms.pas里,应用程序启动时由Application对象进行注册,WakeMainThread工作过程在Vcl.Forms.pas中为procedureTApplication.WakeMainThread(Sender: TObject);
begin
PostMessage(Handle, WM_NULL, 0, 0);
end;
也就是仅仅把WM_NULL消息放入应用程序消息队列中,工作线程调用完WakeMainThread事件方法即PostMessage以后就挂起(通过同步对象)等待proc执行完成信号触发,Vcl.Forms.pas中主线程消息处理函数中会看到如下代码
WM_NULL:
CheckSynchronize;
即在检索消息循环后碰到WM_NULL消息,会执行CheckSynchronize过程,CheckSynchronize过程在System.Classes.pas中function CheckSynchronize(Timeout: Integer= 0): Boolean; 其工作核心就是执行proc过程,并在执行完成后设置proc执行完成信号,工作线程检测到这个信号后会唤醒,并继续执行后续工作。以上过程中从Synchronize调用到PostMessage都是在工作线程中进行的,然后挂起,检索WM_NULL消息执行CheckSynchronize过程和proc过程都是在主线程中完成的,这些工作完成后,工作线程再唤醒继续执行。
以上来自网友http://blog.csdn.net/gogogo/article/details/10139501
个人调试总结:
1.工作线程调用Synchronize唤醒主线程(其实就是通过回调到主线程WakeMainThread接口,接口WakeMainThread再发消息WM_NULL);工作线程进入等待执行完成的信号;
2.主线程接收到WM_NULL消息后,再将进入CheckSynchronize,检查队列中需要执行的函数,当执行完成后,设置执行完成信号;工作线程等待结束,继续往下运行;
3.主线程接收到WM_NULL并不影响其他消息的进入,因为TApplication.Run接口中不停地轮寻消息,消息也会不停地进入各自的消息处理过程; 除非程序睡眠了
4.工作线程调用Synchronize参数列表中执行过程,不应该将耗时的业务交给主线程执行,耗时的业务应该交给工作线程执行
5.工作线程无法代替的工作,可以用Synchronize交给主线程执行;
以上是关于在Delphi中开发使用多显示器的应用程序的主要内容,如果未能解决你的问题,请参考以下文章