新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 中文XML论坛 - 专业的XML技术讨论区计算机技术与应用『 C/C++编程思想 』 → Win32 API讲座 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 16071 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: Win32 API讲座 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客楼主
    发贴心情 Win32 API讲座

    Win32 API讲座第一课∶认识API

    一、什么是API

    首先,有必要向大家讲一讲,什么是API。所谓API本来是为C和C++程序员写的。API 说来说去,就是一种函数,他们包含在一个附加名为DLL的动态连接库文件中。用标准的定义来讲,API就是Windows的32位应用程序编程接口,是一系列很复杂的函数,消息和结构,它使编程人员可以用不同类型的编程语言编制出的运行在Windows95和Windows NT 操作系统上的应用程序。可以说,如果你曾经学过VC,那么API对你来说不是什么问题。但是如果你没有学过VC,或者你对Windows95的结构体系不熟悉,那么可以说,学习API 将是一件很辛苦的事情。

    如果你打开WINDOWS的SYSTEM文件夹,你可以发现其中有很多附加名为DLL的文件。一个DLL中包含的API函数并不只是一个,数十个,甚至是数百个。我们能都掌握它嘛回答是否定的∶不可能掌握。但实际上,我们真的没必要都掌握,只要重点掌握Windos系统本身自带的API函数就可以了。但,在其中还应当抛开掉同VB本身自有的函数重复的函数。如,VB的etAttr命令可以获得文件属性,SetAttr可以设置文件属性。对API来讲也有对应的函数GetFileAttributes和SetFileAttributes,性能都差不多。如此地一算,剩下来的也就5、600 个。是的,也不少。但,我可以敢跟你说,只要你熟悉地掌握100个,那么你的编程水平比现在高出至少要两倍。尽管人们说VB和WINDOWS具有密切的关系,但我认为,API更接近WINDOWS。如果你学会了API,首要的收获便是对WINDOWS体系结构的认识。这个收获是来自不易的。

    如果你不依靠API会怎么样我可以跟你说,绝大多是高级编程书本(当然这不是书的名程叫高级而高级的,而是在一开始的《本书内容》中指明《本书的阅读对象是具有一定VB 基础的读者》的那些书),首先提的问题一般大都是从API开始。因此可以说,你不学API,你大概将停留在初级水平,无法往上攀登。唯一的途径也许就是向别人求救∶我快死了,快来救救我呀,这个怎么办,那个怎么办烦不烦呢当然,现在网上好人太多(包括我在内,嘻嘻),但,你应当明白,通过此途径,你的手中出不了好的作品。这是因为缺乏这些知识你的脑子里根本行不成一种总体的设计构思。

    二、API文本游览器。

    很多API函数都是很长很长的。想看什么样子吗如下就是作为例子的API DdeClientTransaction函数∶ Declare Function DdeClientTransaction Lib user32 (pData As Byte, ByVal cbData As Long, ByVal hConv As Long, ByVal hszItem As Long, ByVal wFmt As Long, ByVal wType As Long, ByVal dwTimeout As Long, pdwResult As Long) As Long 哇!这么长如果你从来没有接触过API,我想你肯定被吓住了。你也许考虑,该不该继续学下去。不过不要担心,幸运的是Microsoft的设计家们为我们提供了有用的工具,这便是API 文本查看器。

    通过API文本查看器,我们可以方便地查找程序所需要的函数声明、结构类型和常数,然后将它复制到剪贴板,最后再粘贴到VB程序的代码段中。在大多数情况下,只要我们确定了程序所需要的函数、结构和常数这三个方面后,就可以通过对API文本游览器的以上操作将他们加入到程序段中,从而程序中可以使用这些函数了。这些是学习API最基本的常识问题,它远远占不到API的庞大的体系内容。今后我们把精力浪费(这绝不是浪费)在哪里呢那就是∶ 什么时候使用什么函数,什么时候使用什么结构类型,什么时候使用什么常数。

    三、API函数声明。

    让我们回想一下。在VB中,如何声明函数呢我想,如果你正在看此文,那么你绝对能够回答得出这个问题。以下便是你应该很熟悉的函数声明∶ Function SetFocus (ByVal hwnd As Long) As Long 即,这行代码定义了名为SetFocus的函数,此函数具有一个Long型数据类型的参数,并按值传递(ByVal),函数执行后将返回一个Long型数据。 API函数的声明也很类似,如,API中的SetFocus 函数是这样写的∶

    Declare Function SetFocus Lib user32 Alias SetFocus (ByVal hwnd As Long) As Long 有点复杂了一些。是的,是复杂了点。但我可以告诉你,除了这些多出来的部分,其他部分还是和你以前学到的东西是一样的。函数在程序中的调用也是一样。如 Dim dl As Long dl&=SetFoucs(Form1.Hwnd) 但,一点是清楚的。它不象你自己写的程序那样能够看到里面的运行机理,也不像VB 自带的函数那样,能够从VB的联机帮助中查到其用法。唯一的方法就是去学、查VB以外的资料。

    Declare 语句用于在模块级别中声明对动态链接库 (DLL) 中外部过程的引用。对此,你只要记住任何API函数声明都必须写这个语句就可以了。 Iib 指明包含所声明过程或函数的动态链接库或代码资源。也就是说,它说明的是,函数或过程从何而来的问题。 如在上例中,SetFocus Lib user32说明 函数 SetFocus 来自 user32.dll文件。主要的dll动态连接库文件有∶ user32.dll Windows管理。生成和管理应用程序的用户接口。

    GDI32.dll 图形设备接口。产生Windows设备的图形输出 Kernel32.dll 系统服务。访问操作系统的计算机资源。 注意,当DLL文件不在Windows或System文件夹中的时候,必须在函数中说明其出处( 路径)。如,SetFocus Lib cMydlluser32 函数声明中的Alias 是可选的。表示将被调用的过程在动态链接库 (DLL) 中还有另外的名称(别名)。如,Alias SetFocus ,说明SetFocus函数在User32.dll中的另外一个名称是, SetFocus。怎么两个名都一样呢当然,也可以是不同的。在很多情况下,Alias说明的函数名,即别名最后一个字符经常是字符A,如SetWindowsText函数的另一个名称是 SetWindowsTextA,表示为Alias SetWindowsTextA。这个A只不过是设计家们的习惯的命名约定,表示函数属于ANSI版本。

    那么,别名究竟有什么用途呢从理论上讲,别名提供了用另一个名子调用API的函数方法。如果你指明了别名,那么 尽管我们按Declare语句后面的函数来调用该函数,但在函数的实际调用上是以别名作为首要选择的。如,以下两个函数(Function,ABCD)声明都是有效的,他们调用的是同一个 SetFocus函数∶ Declare Function SetFocus Lib user32 SetFocus (ByVal hwnd As Long) As Long Declare ABCD SetFocus Lib user32 Alias SetFocus (ByVal hwnd As Long) As Long

    需要注意的是,选用Alias的时候,应注意别名的大小写;如果不选用Alias 时的时候,函数名必须注意大小写,而且不能改动。当然,在很多情况下,由于函数声明是直接从API 文本游览器中拷贝过来的,所以这种错误的发生机会是很少的,但您有必要知道这一点。 最后提醒你一句,API声明(包括结构、常数)必须放在窗体或模块的通用(General Declarations)段。

    四、数据类型与类型安全

    API函数中使用的数据类型基本上和VB中的一样。但作为WIN32的API函数中,不存在Integer 数据类型。另外一点是在API函数中看不到Boolean数据类型。 Variant数据类型在API函数中是以Any的形式出现,如Data As Any。尽管其含义是允许任意参数类型作为一个该API函数的参数传递,但这样做存在一定的缺点。其原因是,这将会使得对目标参数的所有类型检查都会被关闭。这自然会给各种类型的参数调用带来了产生错误的机会。

    为了强制执行严格的类型检查,并避免上面提到的问题,一个办法是在函数里使用上面提到到Alias技术。如对API函数 GetDIBits 可进行另外一种声明方法。如下∶ GetDIBits函数的原型∶ Public Declare Function GetDIBits Lib gdi32 Alias GetDIBits (ByVal aHDC As Long, ByVal hBitmap As Long, ByVal nStartScan As Long, ByVal nNumScans As Long, lpBits As Any, lpBI As BITMAPINFO, ByVal wUsage As Long) As Long GetDIBits函数的改型∶

    Public Declare Function GetDIBitsLong Lib gdi32 Alias GetDIBits (ByVal aHDC As Long, ByVal hBitmap As Long, ByVal nStartScan As Long, ByVal nNumScans As Long, lpBits As Long, lpBI As BITMAPINFO, ByVal wUsage As Long) As Long 通过本课程前面所学到的知识,我们已经可以得知原型 GetDIBits函数也好,改型 GetDIBitsLong函数也好,实际将调用的都是Alias所指定的 GetDIBits原函数。但你应当看到,两者的区别在于,我们在改型的函数中强制指定lpBits参数为Long形。这样就会使得函数调用中发生的错误机率减少到了最小。这种方法叫做安全类型声明。

    API函数中经常看到的数据类型有∶Long,String,Byte,Any....(也就这些吧。)

    五、常数

    对于API常数来讲,没有什么太特别的学问。请看VB中的以下代码∶ Msg = MsgBox(您好, vbOKCancel) 我们知道, vbOKCancel这个常数的值等于1。对上面的代码我们完全可以这样写,而不会影响代码的功能∶ Msg = MsgBox(您好, 1) 但你大概不太愿意选择后一种,因为这会使得看懂代码费劲起来。这种方法也被API采取了。只是API常数必须在事情之前做好初始化声明VB本身是看不懂的。其内容仍然来自与API 文本游览器。具体形式如下等等∶

    Public Const ABM_ACTIVATE = &H6 Public Const RIGHT_CTRL_PRESSED = &H4 Public Const RPC_E_SERVER_DIED = &H80010007 Private Const RPC_S_CALL_FAILED_DNE = 1727& 在常数的初始化中,有些程序使用Global,如Global Const ABM_ACTIVATE = &H6,但我认为Public完全可以代替它。过去我也用过Global,但现在不大用了。一会儿用这个,一会儿用那个,各程序之间不能保持一致性了,起码看起来别扭。

    六、结构

    结构是C和C++语言中的说法。在VB中一般称为自定义数据类型。想必很多朋友都已经认识它。在API领域里,我更喜欢把它叫做结构,因为API各种结构类型根本不是我定义( 自定义)的。 在VB中,API结构同样由TYPE.......END TYPE语句来定义。如,在API中,点(Point)结构的定义方法如下 Public Type POINTAPI X As Long 点在X坐标(横坐标)上的坐标值

    Y As Long 点在Y坐标(纵坐标)上的坐标值 End Type 又如,API中矩形(Rect)结构的定义如下∶ Public Type RECT Left As Long 矩形左上角的X坐标 Top As Long 矩形左上角的Y坐标 Right As Long 矩形右下角的X坐标 Bottom As Long 矩形右下角的Y坐标

    End Type 这些内容同样可以从API文本游览器中拷贝过来。这些结构中的变量名可随意改动,而不会影响结构本身。也就是说,这些成员变量都是虚拟的。如,POINTAPI结构可改为如下∶ Public Type POINTAPI MyX As Long 点在X坐标(横坐标)上的坐标值 MyY As Long 点在Y坐标(纵坐标)上的坐标值 End Type 不过,一般来讲,是没有这种必要的。结构本身是一种数据类型,因此,使用时必须声明具体变量为该结构型,才能在程序中真正使用到该结构。结构的声明方法和其他数据的声明方法一样,如,以下语句把变MyPoint声明为POINTAPI结构类型∶

    MyPoint As POINTAPI 引用结构中的成员变量也十分简单,在结构名后面加上一个.,然后紧接着写要引用的成员变量即可。这很象VB中的引用一个对象的某个属性。如,假如我们把上面已经声明的MyPoint结构中的X变量的值赋给变量Temp& 则代码如下∶ Temp&=MyPoint.X 但,特别注意的是,你千万不要认为上例中的MyPoint是一个值。它不是值,而是地址( 指针)。值和地址是完全不同的概念。结构要求按引用传递给WINDOWS函数,即所有API 函数中,结构都是按ByRef传递的(在Declare语句 中ByRef是默认型)。对于结构的传递,你不要试图采用ByVal,你将一无所获。由于结构名实际上就是指向这个结构的指针(这个结构的首地址),所以,你也就传送特定的结构名就可以了(参见小结,我用红色字体来突出了这种传递方式)。

    由于结构传送的是指针,所以函数将直接对结构进行读写操作。这种特性很适合于把函数执行的结果装载在结构之中。

    七、小结

    以下的程序是为了总结本课中学到的内容而给出的。启动VB,新建一个项目,添加一个命令按钮,并把下面的代码拷贝到代码段中,运行它。

    Private Declare Function GetCursorPos Lib user32 (lpPoint As POINTAPI) As Long Private Type POINTAPI 定义点(Point)结构 X As Long 点在X坐标(横坐标)上的坐标值 Y As Long 点在Y坐标(纵坐标)上的坐标值 End Type Sub PrintCursorPos( ) Dim dl AS Long Dim MyPoint As POINTAPI dl&= GetCursorPos(MyPoint) 调用函数,获取屏幕鼠标坐标

    Debug.Print X= & Str(MyPoint.X) &  and  & Y= & Str(MyPoint.Y) End Sub Private Sub Command1_Click() PrintCursorPos

    End Sub

    输出结果为(每次运行都可能得到不同的结果,这得由函数调用时鼠标指针在屏幕中所处的位置而决定)∶ X= 240 and Y= 151

    程序中,GetCursorPos函数用来获取鼠标指针在屏幕上的位置。

    以上例子中,你可以发现,以参数传递的MyPpint结构的内容在函数调用后发生了实质性变化。这是由于结构是按ByRef传递的原因。


       收藏   分享  
    顶(0)
      




    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/9/6 9:01:00
     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客2
    发贴心情 
    第二课∶句柄、矩形和画点函数
    一、句柄
    今天开始,我向大家讲有关API的是实质性内容。我们就从句柄开始。 只要你来到了API的世界,经常碰到的问题之一就是句柄。那么究竟什么是句柄呢 如果你从来都没有听说过句柄这个词,可能首先觉得句柄当中有很多内容。其实不然,所谓句柄实际上是一个数据,是一个Long (整长型)的数据。在API中,它经常是以一个参数的形式传递给各种API函数。如 Public Declare Function GetWindow& Lib user32 (ByVal hwnd As Long, ByVal wCmd As Long)

    其中,hwnd就是句柄。在VB里,句柄是一种属性,您打开VB中的对象游览器看一看Form 窗体或者PictureBox控件等究竟有没有hwnd属性。是有的。VB中的解释是这样的∶ Microsoft Windows 运行环境,通过给应用程序中的每个窗体和控件分配一个句柄(或 hWnd)来标识它们。hWnd 属性用于Windows API调用。许多 Windows 运行环境函数需要活动窗口的 hWnd 作为参数。

    如果想更透彻一点地认识句柄,我可以告诉大家,句柄是一种指向指针的指针。我们知道,所谓指针是一种内存地址。应用程序启动后,组成这个程序的各对象是住留在内的。如果简单地理解,似乎我们只要获知这个内存的首地址,那么就可以随时用这个地址访问对象。但是,如果您真的这样认为,那么您就大错特错了。我们知道,Windows是一个以虚拟内存为基础的操作系统。在这种系统环境下,Windows内存管理器经常在内存中来回移动对象,依此来满足各种应用程序的内存需要。对象被移动意味着它的地址变化了。如果地址总是如此变化,我们该到哪里去找该对象呢

    为了解决这个问题,Windows操作系统为各应用程序腾出一些内存储地址,用来专门登记各应用对象在内存中的地址变化,而这个地址(存储单元的位置)本身是不变的。Windows 内存管理器在移动对象在内存中的位置后,把对象新的地址告知这个句柄地址来保存。这样我们只需记住这个句柄地址就可以间接地知道对象具体在内存中的哪个位置。这个地址是在对象装载(Load)时由系统分配给的,当系统卸载时(Unload)又释放给系统。句柄地址(稳定)→记载着对象在内存中的地址────→对象在内存中的地址(不稳定)→ 实际对象

    但是,必须注意的是程序每次从新启动,系统不能保证分配给这个程序的句柄还是原来的那个句柄,而且绝大多数情况的确不一样的。假如我们把进入电影院看电影看成是一个应用程序的启动运行,那么系统给应用程序分配的句柄总是不一样,这和每次电影院售给我们的门票总是不同的一个座位是一样的道理。 在VB中获得一个对象的句柄十分简单,如要获取Form1窗体的句柄,就可以这样写∶ Form1.Hwnd

    对象的句柄还可以通过API函数来获得,如∶

    GetActiveWindow 返回位于最顶部的具有输入焦点的窗口句柄 GetFocus 获得当前线程里补获鼠标输入的窗口句柄 GetForegroundWindow 从位于前台的线程里返回活动窗口的句柄 GetCursor 取得当前指针的句柄 GetDesktopWindow 获取整个桌面的句柄 GetWindow 获得一个窗口的句柄,该窗口与某源窗口有特定的关系《以上函数说明均可在WinAPI.hlp文件中找到。》

    注意以下几点∶ 1,线程内与线程外。(VB不支持多线程)。其他应用程序对此程序来说都是线程外。 2,在windows95操作系统下,各个窗体(包括一些控件,如文本框,图片框等, MICROSORT对它们均统称为窗体)拥有各自的鼠标指针。这和win16下各应用程序使用同样一个指针是截然不同的。

    3,每次从新启动,各窗体的句柄都有所变化。Text5 的装载和卸载过程中,句柄始终是在变化着的。这说明了上面提的影院售门票中存在的现象是真实的。 获得对象句柄的函数还有很多,以后碰到它们时再介绍给大家。

    二、驾驶句柄

    只要弄清了什么是句柄,尤其是窗口句柄,那么操纵一个对象就变得自如一多了。比如,可以通过GetWindowText API函数,我们可以轻松地获得某特定窗口的标题。 GetWindowText 在VB中的声明如下 Private Declare Function GetWindowText& Lib user32 Alias GetWindowTextA (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) 于是,我们可以通过以下一段代码来获得Form1的窗口标题。(新建一个项目,添加一个命令按钮,把以下的代码拷贝过去,还有上面的函数声明。你就可以运行了)

    Private Sub Command1_Click() Dim dl As Long Dim FormCaption As String FormCaption = Space(128) dl& = GetWindowText(Form1.hwnd, FormCaption, 128) Print FormCaption End Sub 注∶其中128是指字符串变量FormCaption的长度(又称缓冲区大小)

    这有必要吗为了获得Form1窗体的标题,何必写这么多代码呢难道这就是API。是的,的确在VB中用 Print Form1.Caption 一行代码就可以抵挡住以上代码了。但是,假如我们启动我们设计的应用程序后,想要在用鼠标点一下别的应用程序的时候,让我们的应用程序显示出那个窗体的标题,那又该怎么办呢比方说,我们另外启动的是Micorsoft Word, 用鼠标点击Word时让程序显示出您选择了Microsoft Word字样。显然只靠VB是办不到的,还得靠API这老手。

    当然,您已经具备了这种能力,可以办到这件事情。让我们一起来。 关键的问题是如何获得Word程序的句柄。首先要认清的是,对VB的应用程序来说,Word 是属于进程外应用程序。正和您已经想到的那样,我们可以使用前一个示范程序用到过的 GetForegroundWindow API函数获取它的句柄。因为,当我们用鼠标点击Word时,Word 将成为前台活动窗体。接着呢当然是 GetWindowText函数显示它的标题就可以了。我们可以采用Timer控件来完成这一切。剩下的事情就不用我多说了。本教程附带的 Program2.vbp程序是,为那些懒得由自己动手写这几行代码的人准备的,但愿您不是。如果你还没有写过API应用程序,可以说这是一个好的机会,还是动一动你的手吧,会有好处的。

    程序正常运行了没有哈,这下感觉到API的魅力了吧 还想不想继续学下去呀!

    三、矩形和点

    关于矩形和点,我们在上一个课堂中简单提了一下。在这里,就做一下详细的介绍。 先从简单点(Point)结构的开始。点的结构如下∶ Type POINTAPI x as Long y as Long End Type 在英语里,点叫Point,那么为什么不叫POINT,而叫POINTAPI呢原来,VB中有Point 方法,这只是为了不必要的冲突或重名。Point用于描述一个位置,当然是一个点的位置了。在屏幕坐标中,x指的是从屏幕左边界到指定点的距离,y指的是从屏幕顶边界到指定点的距离。初次之外,没有太多的学问了,还是那句老话──牢记按引用传递。

    矩形的结构和点结构差不到哪儿去,只不过是用两个点来描述的。它的结构定义如下 Public Type RECT

    Left As Long Top As Long Right As Long Bottom As Long End Type 以下图描述了RECT结构各字段的含义。 可以看出,一个矩形区域是通过矩形的左上角的一个点和右下角的一个点,共两个点来描述的。其中,left和top字段描述了巨型的第一个角的位置,right和bottom字段描述了矩形的第二个角即右下角的位置。 在VB中,描述一个窗体或控件所处的矩形位置时,经常用Left,top以及Width,height来描述的。其中,Width是一个巨型区域的宽度,Height是高度。在此,您应当看到,RECT结构中并不是这样。如,想要获得宽度,必须从right中减掉left。

    API中有若干个函数用来处理矩形数据结构,如,下表所列∶

    函 数 功 能 EquaRect 判断两个矩形是否相同,如果相同则返回True(非0) InflateRect 增加或减少矩形的尺寸 InterSectRect 获得两个矩形相重叠的部分,即一个新的矩形 IsRectEmpty 如矩形为空,则返回TRUE OffsetRect 按规定的偏移量移动一个矩形的位置 ptInRect 判断一个指定点是否位于给出的矩形的内部 SetRect 设置巨型的参数字段值 SetRectEmpty 将所有字段都设为0,从而将矩形置空 SubtractRect 将一个矩形从另一个巨型里减去。即切掉 UnionRect 获得同时包含两个矩形区域的最小矩形。 CopyRect 将一个矩形的内容复制给另一个 这几个函数,都是很好理解和实际应用的。

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/9/6 9:01:00
     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客3
    发贴心情 
    第三课∶窗口函数
    一、 关于窗口函数

    在上一堂课里,我们已经提出了句柄的概念,并为此进行了较深度的讨论。现在来想,我要补充的是,句柄并非是仅仅是窗口才有的。似乎所有的WINDOWS对象都具有句柄。如,GDI对象中的画笔、刷子等,不久即将要学习的设备场景等也有自己的句柄,等等。但,和一些控件不同,这些对象并不属于窗口。 什么是窗口呢有一句非常有趣的话∶如果它位于屏幕,那么它肯定是在一个窗口里;如果它不在于屏幕,它仍然可能在一个窗口里。窗体也是窗口;滚动条、列表框,文本框,甚至是桌面上的快捷图表也是窗口。更有趣的是,就连作为背景的桌面也是窗口。

    很多控件基本上都提供了hWnd属性,但没有提供的也有。对于这些控件可以用SetFocus 方法,将输入焦点设向控件,然后用API函数GetFocus取得当前具有焦点的那个窗口的句柄。当然,这一过程应当写在GoFocus事件中。在我碰到过的问题中有一个有趣的事情是, VB提供的IE控件的hWnd属性不管用。这个问题我一般都采用上述方法来解决的。 很多窗口函数都能对系统的任何窗体进行操作。这意味了VB程序可以直接操纵正在运行中的其他窗体。大家知道,如果对VB设计出的程序未做特殊的处理,那么我们可以启动多个该应用程序实例。我们可以利用API窗口函数来判断一个窗体的先例是否在运行当中,从而可以做到如果有先例则停止启动。很多应用程序就是这个样子的。比如四通利方中文平台,在已经启动的情况下再此启动,程序会告诉用户四通利方已经在运行,并停止启动。

    窗口函数主要可分为四个类型(也许说为这是为了这次讲课分类出来的更适合一些)∶ 1、窗口分级函数; 2、窗口位置与大小函数; 3、窗口信息函数; 4、其他窗口函数。 以下我们就一一讲述。但由于窗口函数比较多,在这里就选择性的进行讨论。关于窗口函数有多少,具体的用法如何,您可以注意小雁侠的VB API站点的技术文档,或者本站程序下载栏目中的WinAPI帮助文。由于帮助文其内容来自小雁侠的网站,因此其内容更新比较起来会较晚一些。

    二、窗口分级函数

    系统中运行的窗口是有级别的高低之分的。谁不知道这样这当然是废话。很多文章都是采用类似的这种废话来做导语,在这里我只不过也是学学而罢。 一、父子关系。 每个窗口都可能有自己的父窗口和子窗口。但,系统中运行的窗口是有限的,说明总得有个窗口是没有其父,我们把它叫做顶级窗口。一般把一个应用程序的主窗口就是顶级窗口,VB独立窗体及MDI窗体都是顶级窗口。窗口间的父子关系一般遵循以下规则∶ 1、父窗口显示时,所有包容在其中的可见的子窗口会随着父窗口的显示而显示出来。

    2、父窗口隐藏时,所有包容在其中的子窗口会随着父窗口的隐藏而隐藏。 3、父窗口被卸载时,哈,您已经知道我想说什么了,当然是∶跟着自动卸载。 4、父窗口移动时,跟着移动。 二、兄弟关系及Z序列 当然,一位父亲有好几个儿女,都是常见的事情。同样,一个父窗口可以拥有多个子窗体。比如,位于一个窗体中的各种控件之间以及MDI窗口的各子窗口之间的关系。父窗口与子窗口的显示、隐藏、卸载及移动,其先后顺序是显而易见的。那么各兄弟窗口之间的情况会是如何呢

    显然,两个互相重叠的两个子窗体不能都同时显示出它的全貌,自然有个显示的顺序规则。这个顺序规则叫做Z序列。有个解释为,如果把屏幕坐标看层X和Y轴组成的平面(事实上正是如此),那么作为三维坐标系统Z轴可看做是垂直于屏幕的坐标轴。这样,可以认为屏幕上的所有窗口是垂直于这个Z轴的。在Z轴上,谁在前,谁在后,就产生了一个Z序列。很生动!可用WINDOWS API函数和Visual Basic Z序列方法对Z序列进行控制。

    有了以上简单的知识以后,我们就不难应用API窗口分级函数,主要有以下及个∶ 窗口分级函数

    函数名 说 明 FindWindow 按类名或窗口名(Caption)查找一个窗口 FindWindowEx 类似于FindWindow提供了更多的功能 GetLastActivePopup 针对指定的窗口,取回上一个活动的弹出式窗口的句柄 GetParent 获得指定窗口父窗口的句柄 GetTopWindow 获得指定窗口的第一个子窗口的句柄 GetWindow 如给定一个窗口句柄,该函数能取回具有特定关系的另一个窗口 的句柄。如,第一个子窗口、父窗口或窗口列表内的上一个或下一个窗口。 SetParent 改变任何窗口的父窗口。 从我个人的经验来看,我最常用的是GetWindow和SetParent函数。

    三、窗口位置与大小函数

    Windows API函数基本上都是(尤其是USER32.DLL动态连接库内的函数)以屏幕像素为度量单位的。这一点很重要,必须牢记。为此,在使用API函数的时候,我们经常把窗体或图片框控件的ScaleMode属性设置为3,即vbPixels(像素)。 理解窗口位置及大小函数的关键在于分清屏幕坐标、窗口坐标及客户坐标这三个概念。以下图展示了这三个坐标系统之间的关系。

    屏目、窗口与客户区坐标系统

    只要对这些坐标有了明确的概念,对使用窗口位置及大小函数就不难了。关于窗口的位置,有些函数返回的是上一堂课学习到的RECT结构。有关窗口位置及大小函数如下表所列∶ 窗口位置及大小函数

    函数名 说 明 BringWindowToTop 使指定的窗口进入可见窗口列表的顶部,如它被部分或全部隐藏,则令其全部可见。同时,该窗口成为当前活动窗口。只有从前台线程调用时,才生效。 ChildWindowFromPoint 在规定的坐标取得某子窗口的句柄(如果有的话),这儿的坐标是指相对于父窗口的客户区坐标。 ChildWindowFromPointEx 与 ChildWindowFromPoint相同,功能更强。 ClientToScreen 判断指定点在窗口客户区内的屏幕坐标。 GetClientRect 获得对窗口客户区进行表述的一个矩形(RECT)。这是以像素为单位判断客户区大小的一个简便的方法。 GetWindowPlacement 获得指定窗口的一个WINDOWPLACEMENT结构。该结构说明了窗口的状态。 GetWindowRect 用于获得一个矩形(RECT)结构,它描述了窗体在屏幕坐标系统中的位置。 MapWindowPoints 对某窗口客户区坐标内的一个或多个点进行转换,用另一窗口的客户区坐标表示。 MoveWindow 移动指定窗口的位置,并能改变它的大小。 OpenIcon 将一个最小化窗口恢复为原始状态。 ScreenToClient 针对屏幕内一个指定的点,用某个特定窗口内的客户区坐标表示它。 SetWindowsPos 更改窗口的位置和大小,并能修改它在内部窗口列表内的位置(这个列表起着控制窗口先是顺序)。 SetWindowPlacement 在一个WINDOWPLACEMENT结构的基础上,设置某窗口的特征。该结构描述了窗口的状态,以及它在最小化、最大化或正常显示时的位置。 WindowFromPoint 根据屏幕上一个指定的点,判断哪个窗口正位于它的下面。 以上函数的具体用法均可在WinAPI帮助文中找到。在这些函数当中,SetWindowsPos 函数的使用率比较高,现在很多人都是用这个函数来实现窗口总在前面的效果,即通过 HWND_TOPMOST常数把窗口置于列表顶部。如果想把From1置于列表顶部,方法如下∶ SetWindowPos Form1.hWnd, HWND_TOPMOST, Form1.Left  Screen.TwipsPerPixelX, Fo rm1.Top  Screen.TwipsPerPixelY, Form1.Width  Screen.TwipsPerPixelX, Form1.Height  Screen.TwipsPerPixelY, 0

    您可以把这行代码放在Paint事件中。 另外,GetWindowRect函数、MoveWindow函数以及下一课堂即将要学到的 GetCursorPos函数的相互配合能够实现一个拖动无标题栏的窗口。这是您必须掌握的技巧之一。感兴趣的朋友,可以到《前线》的《源码解析》栏目,下载第4号演示程序。以下是其主要的代码部分∶ Private MyRect As RECT Private MyPoint As POINTAPI Private Movex As Long, Movey As Long

    Private Sub Image1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)

    Dim dl& dl& = GetWindowRect(Form1.hwnd, MyRect) dl& = GetCursorPos(MyPoint) If Button = 1 Then Movex = MyPoint.X - MyRect.Left Movey = MyPoint.Y - MyRect.Top End If End Sub

    Private Sub Image1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single) Dim dl& dl& = GetCursorPos(MyPoint) If Button = 1 Then dl& = MoveWindow(Form1.hwnd, MyPoint.X - Movex, MyPoint.Y - Movey, _

    MyRect.Right - MyRect.Left, MyRect.Bottom - MyRect.Top, -1) End If End Sub 在MouseDown事件中,程序首先用 GetWindowRect 函数确定窗口在屏幕中的位置。再次是通过GetCursorPos函数确鼠标在屏幕中的位置。从而可通过计算获得鼠标位置与窗口左上角之间的横向与纵向距离(Movex与Movey)。 在紧接着发生的MouseMove事件中程序不断地用GetCursorPos函数获得鼠标当前的位置,并按前面已经求得的Movex与Movey判断窗口所应处的位置,而这在MoveWindow函数调用中直接完成。MoveWindow函数将窗体移动到新的位置。

    四、窗口信息函数

    所谓窗口信息函数就是用来获取有关窗口当前状态信息的函数。这类函数主要有∶

    函 数 说 明 GetClassInfo 取得指定窗口的类信息结构 GetClassInfoEx 效果类似于GetClassInfo,但增加了一些功能 GetClassLong,GetClassWord 用于获取窗口类信息 SetClassLong,SetClassWord 用于设置窗口类信息 GetClassName 获取窗口类名 GetDesktopWindow 获取整个桌面(屏幕)的窗口句柄 GetWindowLong,GetWindowWord 获取与窗口有关的信息 SetWindowLong,SetWindowWord 设置与窗口有关的信息 GetWindowText 获得窗口文本。它的效果大致等价于窗体或控件的Text属性 GetWindowTextLength 获得窗口文本的长度,用字符数表示。 IsChild 判断某窗口是否为另一窗口的子窗口或从属窗口。 IsIconic 判断某窗口是否处于最小化状态 IsWindow 判断指定的句柄是否为窗口句柄。 IsWindowEnabled 判断指定的窗口是否处于活动状态。 IsWindowVisible 判断某窗口是否可见。 IsZoomed 判断窗口是否处于最大化状态。 SetWindowText 设置窗口文本。大致等价于窗体或控件的Text属性。 大部分窗口信息函数是非常好理解的,按照有关手册中进行的函数说明,按指定数据类型进行调用即可。有必要说明的是,关于类和窗口的样式位。Windows是用一个长整形的数据的位设置方式来记录类和窗口的样式的。其中,窗口样式由一个32位样式以及另一个32 位扩展样式来构成。类样式操作由上述列表中的GetClassLong以及GetClassLong来进行,窗口样式操作由GetWindowLong 以及SetWindowLong来进行。 由于样式位的内容较多,我无法在此给出,您可以参考有关手册。这里有必要提醒大家的是,您想改变或获取当前窗口或类的样式,绝大多数情况可以考虑样式位操作。下面,就这个问题举一个简单了例子来说明。

    下面是用BS_LEFTTEXT样式位将VB复选框或选项按钮的文本在左边和右边之间相互移动来、移动去的程序(是附带的Program1.vbp的部分内容)。程序的原理很简单。首先用 GetWindowLong函数获得当前样式位的信息,然后通过位操作来准备新的样式位信息,最后用SetWindowLong实际地去更改。如下∶ Dim f&, dl& f& = GetWindowLong(Option1.hWnd, GWL_STYLE) 获得当前样式位的信息 If Index = 0 Then f& = f& Or BS_LEFTTEXT

    Else f& = f& And Not BS_LEFTTEXT End If dl& = SetWindowLong(Option1.hWnd, GWL_STYLE, f&) 设置新的样式位 Option1.Refresh (对Or和And位操作不熟悉的朋友,请参考有关技术资料) 在这里,对样式位不进行更详细讨论,主要有这样一个原因。用SetWindowLong函数改变一个样式位之后,不会导致窗口发生相应的变化(至少不会立即变化)。有些样式位可能在运行时候才会成功变化,而大多数都只在窗口创建时才生效。因为,用API方式创建一个窗体已经超出了本教程的范围,就算我在这里对样式位谈得再多,您可能也没有多大用处。同时,微软公司没有告诉我们哪些样式位在运行期间安全地改变,因此对具体的情况,只好靠自己进行具体试验。而从我个人的实际编程经验来看,没有特别的要求,我们不大会涉及到这些样式位操作,很多都可在VB中很方便地实现。

    本教程还附带了一个Program2.vbp的演示程序。是我本人随便编写的,没什么特别希罕之处。想看就看看好了。 最后,想简单提一提的是,使用SetWindowLong函数的时候,改变GWL_WNDPROC 数据是非常危险的(系统或VB经常挂死),即更改窗口函数的位置。一般,这种更改在需要进行子类处理的地方应用到。每次试运行程序,都应当习惯性地进行存盘。

    五、其他窗口函数

    API中还有以下本教程未列出的窗口函数,以供大家参考。

    函数名 说 明 AnyPopup 判断是否存在可见的弹出式窗口 CascadeWindows 令窗口在一个父窗口内层叠显示 CloseWindow 对指定的窗口进行最小化处理(如果它是个钉级窗口)对弹出式及子窗口无效 DestroyWindow 清除指定的窗口以及下属所有子窗口与包容窗口 DrawAnimatedRects 获得窗口打开或关闭的动画效果 EnableWindow 激活或屏蔽(禁用)指定窗口 FlashWindow 令指定窗口的标题闪烁显示 GetUpdateRect 判断需要更新的那个窗口的位置 GetWindowContextHelpId 取得与窗口关联在一起的帮助场景 InvalidateRect 指定窗口内需要更新的全部或部分客户区 IsWindowUnicode 判断一个窗口是否期望文本消息采用Unicode格式 LockWindowUpdate 允许或禁止描绘指定的窗口 RedrawWindow 一个功能强大的函数,用于控制全部或部分窗口重画 ScrollWindow,ScrollWindowEx 滚动显示窗口的全部或部分客户区 SetWindowContextHelpId 设置与窗口关联在一起的帮助场景 ShowOwnedPopups 隐藏或显示从属于指定窗口的所有保容弹出窗口 ShowWindow 用于设置窗口的状态,其中包括窗口的隐藏、显示、最小化、最大化以及激活等 ShowWindowAsync 类似于ShowWindow,增加了对其他进程内的窗口进行操作的能力 TileWindows 令窗口在一个父窗口内平铺显示 UpdateWindow 立即更新窗口内需要更新的任何部分 ValidateRect 指出全部或部分矩形已经更新,毋需再更新 其中,FlashWindow函数非常有趣,不妨大家试一试。

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/9/6 9:02:00
     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客4
    发贴心情 
    第四课:鼠标、插入符及系统函数
    一、指针剪切

    什么是指针呢我想大家都知道,没必要我多讲。只是概念上应当清楚,指针是指针,鼠标是鼠标,鼠标控制着指针。在win16中,指针只有一个,运行在系统中的应用程序共享这个指针。但在win32中,各个窗体都具有着自己的指针。这倒不是说屏幕上能同时出现好几个指针,而是说每个窗体都具有它自己的样式和一些特征的指针。指针移动到某窗体的时候,指针就自动变成那个窗体的指针样式。对指针需要认识的另一点是,指针的位置都是以像素屏幕坐标指定的。这一点很重要。接下来,我们进入本小节的主题。

    只要你细心一些就能够发现,鼠标指针一般是不能超出屏幕(显示器屏幕)范围的。但这倒也不是绝对的,一会儿你就会明白。指针一般控制在屏幕以内,这是事实。从这个事实中我们可以知道,指针是限定在某个区域之内活动着的。把指针的活动限定一个区域的过程叫做指针剪切。有关指针剪切的函数有两个。一个是GetClipCursor函数,它可以获得当前鼠标指针的剪切区域。此函数只有一个RECT结构的参数,函数调用结束后,这个RECT结构的数据,中便装载当前鼠标的矩形剪切区域大小。如果在一般情况下调用此函数,你大概获得的正好是屏幕大小。

    另一个函数是,SetClipCursor函数,作用是设置指定大小的指针剪切区域。欲设置的指针大小是装在一个RECT结构的数据中传递的。这两个函数一般搭配使用。在设置新的指针剪切区域前用GetClipCursor函数获取当前指针剪切区域,以便保存其值,而后调用SetClipCursor函数设置新的剪切区域。当这种新的剪切区域不再需要时就向SetClipCursor函数传递先前保存下来的区域值,就可以恢复到原来的指针剪切区域了。本教程为此提供了Program1.vbp演示程序。本人在设计这个程序时,忘了恢复原来的指针区域,结果指针无法脱离新设定的框架(自然也就无法按动敗翑按钮了),不得不用 CTRL+ALT+DELETE强行关闭VB设计器。望也成为您的一个经验。

    二、指针位置函数

    指针位置函数,简单得和指针剪切函数差不多。WINDOWS为此也提供了两个函数,一个是GetCursorPos,另一个是SetCursorPos。GetCursorPos函数只有一个参数,用来装载一个POINTAPI结构的数据,该数据说明的是当前指针的X和Y的坐标。但SetCursorPos 函数有所不同,它不依靠POINTAPI结构的数据,需要直接向它传递指针的X和Y两个参数。 本教程提供的program2.vbp正是对这两个函数的使用演示,演示一种指针的自动移动。

    在这两个函数中GetCursorPos函数的使用率比较高。作为一个比较精彩的例子,本人把自己的第4号演示程序简化为本教程的program6.vbp。此程序演示如何拖动无标题栏的窗体。程序还使用了我们在前几个课堂当中学习到的一些窗口函数,可作为一个好的复习材料,欢迎阅读。在program2.vbp中,对程序中采用的一点数学知识,怕有些朋友难以看出来(注意∶不是斈岩岳斫鈹),在此简单说明。首先程序通过一些方法计算出了鼠标指针的开始位置( OldPoint)和终点位置(NewPoint)。指针需要在连接这两个点的直线上移动。我们知道,如果设(X,Y)是指这个直线上的一个点,那么这个直线的方程可以是这样写的∶Y=aX+b

    这里a和b是常数。 如果我们知道了a和b,那么只要X按一定的单位量增加,那么Y的值也就可以得知了。问题是如何确定这里的a和b呢 解决方法是∶ 设,鼠标起点坐标为∶X1,Y1 鼠标终点坐标为∶X2,Y2 则∶ Y1=aX1+b ① Y2=aX2+b ② 从①可得∶b=Y1-aX1 ③

    把③代入②可得,Y2=aX2+Y1-aX1 ④ 从④可得∶a=(Y2-Y1)÷(X2-X1) 获得a以后剩下的事情就好办了,可以用①或②获得b。 (这实际上是初中一年级的内容!不过一年级的学生可能不一定知道它代表的是直线。解析几何好象在高中二年级开课。你也可以用解析几何的方法来理解。)

    三、其他指针函数

    有关的指针函数还有以下几个

    函 数 说 明 CreateCursor 在两幅位图的基础上创建一个指针 DestroyCursor 清除一个指针,并释放它占用的系统资源 GetCursor 取得当前指针的句柄 GetDoubleClickTime 了解两次连续鼠标单击之间被看作双击事件的时间长度 LoadCursor 从一个文件载入指针资源 mouse_event 生成一个鼠标模拟事件 SetCursor 选择要使用的指针 SetDoubleClickTime 设置两次连续单击之间被看作一次双击的时间长度 SetSystemCursor 改变任何系统标准指针 ShowCursor 控制指针的可视性 SwapMouseButton 交换左右鼠标按钮的作用

    这里写出的很多函数并没有太大的用处。当然,您正好找这些函数,那这对你来说可能是一件非常斨卮髷的发现。你已经看到,对于有些函数功能,VB自身已经拥有的。在这几个函数中本人最喜欢mouse_even函数,有必要向大家介绍。 如果你还没有看Program2.vbp,那么最好先启动并运行它一下。程序的目的是用鼠标指针的移动来自动演示擷ing对你说“Xing对你说”这个按钮的用法。可喜,鼠标指针移动到这个按钮后,按钮并没有被按下。当然,这个程序根本没有设计成按那个按钮,而是直接调用Command2_Click过程。这当然是假的了,糊弄人的了。

    有了mouse_even函数以后,我们可以更换一下设计思路。也就是说,当鼠标指针移动到“Xing对你说”这个按钮以后,不要直接调用Command2_Click过程,而是模拟产生对按钮的点击操作。Program3.vbp正是把这个设计思路搬到了实际程序。程序中还使用了一个还没有学到的API函数Sleep。此函数用来使线程等待一定时间,时间以毫秒表示。如果想等待20毫秒,就可以写为 Sleep 20 。由于经常需要使用,先在这里简单讲一讲为好。 请打开并运行Program3.vbp,现在,示范过程是否像个那么一回事

    四、关于插入符

    作为一个资源,插入符通常用于表示文本编辑器中的一个位置,用来输入文字。外观上一般是闪烁的线段或者光标块。但,事实上插入符可以是任何样式。其样式可以用位图来处理。但由于使用位图是以后的学习内容,本教程不予演示程序。等您学会了位图,这些都是很轻松的事情。您现在需要认识的是什么是插入符,它到底是如何创建,如何固定位置,如何显示等内容,以便在自己的程序中应用。

    函 数 说 明 CreateCaret 创建和选择一个插入符 DestroyCaret 清除插入符 GetCaretBlinkTime 获得插入符的闪烁频率 GetCaretPos 了解插入符在逻辑坐标中的位置 HideCaret 隐藏插入符 SetCaretBlinkTime 设置插入符的闪烁速率 SetCaretPos 设置插入符在逻辑坐标中的位置 ShowCaret 让插入符可见

    按道理来讲,插入符函数应当在控件的GotFocus事件中进行创建和初始化处理。也就是说,在控件得到焦点的时候。不幸的是,VB中只有在控件丢失焦点,并由当前应用程序中另一个控件继承焦点的事后,才会触发GotFocus事件。你可以启动Program4.vbp作试验。具体方法是,应用程序启动以后点一下VB设计器,使VB设计器成为活动窗体。这时,应用程序窗口将退到VB后面看不到。确认VB设计器中看到了闪烁的插入符光标后,从任务栏中点一下应用程序任务条,使应用程序窗体成为活动窗口。这时应用程序窗体将从VB 设计器后面跳到前台并显示。这时你可以观察到,上面的Picture1控件的光标消失了。用鼠标点击控件也无任何反应。除非你先点一下下方的Text控件,然后再点Picture控件,光标是不会出现的。当然完全可以用一个计时器来探测控件得到焦点的情况,但这种作法显然麻烦。对于这个问题我一般是在控件的Click事件中写一行代码来激活GotFocus事件内部的代码(参看程序,并将Private Sub Picture1_Click()事件中的代码Picture1_GotFocus无效)。

    书本上说,插入符应当在LostFocus事件中清楚掉。但本人认为没有必要,这反而会产生一些符作用。比如在本教程提供的Program4中,如果使LostFocus事件内的代码dl& = DestroyCaret有效,结果当我们点击Text控件的时候会看不到插入符的。所以我认为,在想用插入符的时候,您尽管创建就可以了。不想看到它,就采用HideCaret函数隐藏之。 附带的演示程序Program4.vbp总结了一些常用的插入符函数。

    五、键盘控制函数

    要使用键盘控制函数,首先必须认识什么是扫描码,什么是虚拟键码。所谓扫描码是一种计算机键盘的硬件决定的代码,可以说不同类型的键盘有不同标准的扫描码。如果我们直接用扫描码来设计程序,那大概是一件非常枯燥的事情,况且很难保证程序的兼容性。与扫描码不同,虚拟键码对每台计算机来讲都是一样的。这里,所谓键码不是别的,只是用哪个数据来表示哪个按键的问题。 从工作原理来讲,计算机键盘向计算机发送的是扫描码,然后来自键盘的扫描码将被转换为虚拟键码,究竟谁去做这种转换,我们就不用关心了。最后,WINDOWS将虚拟键码再转换为ASCII码或字符。正如所看到过的那样,VISUAL BASIC的KeyPressed等事件中递送过来的键码就是ASCII码了。

    另外,Unicode字符集和ANSI字符集的概念也需要掌握一些。时间关系,以下直接把一本叫《VB核心技术》的书中一段精彩的论述摘录给大家,作者是Bruce Mckinney。现在就要学习的朋友请点击这里。 现在,把有关键盘处理函数列到如下表,大家先大概看一下究竟都有哪些功能的函数∶

    键盘处理API函数

    函 数 说 明 ActivatekeyBoardLayout 在由系统定义的键盘布局中选择一种 EnumSystemCodePages 获得系统支持或安装的所有代码页的一个列表 GetACP 获得目前使用的ANSI代码页的表示符 GetAsyncKeyState 获取函数调用时指定虚拟键的状态 GetCPinfo 获取与一个代码页有关的信息 GetKeyboardLayout 判断一个指定线程使用的键盘布局 GetkeyboardLayoutList 取得目前可用键盘布局的一个列表 GetKeyboardLayoutName 取得键盘布局的名称 GetKeyboardState 取得256个虚拟键目前的状态 GetKeyboardType 判断目前使用的键盘类型 GetKeyNameText 判断一个特定虚拟键的名称 GetKeyState 取得指定虚拟键的状态 GetOEMCP 取得目前正在使用的OEM代码页的标识符 keybd_event 为系统生成一个新的键盘布局 LoadKeyboardLayout 载入一个新的键盘布局 MapVirtualKey 在字符与虚拟键、扫描码以及ASCII码之间进行转换 MapVirtualKeyEx 同上 OemKeyScan 将ASCII代码转换成OEM扫描码 SetKeyboardState 将256个虚拟键的状态设为WINDOWS所能理解的状态 ToAsciiToAsciiEx 将一个扫描码和Shift状态转换成对应的ASCII码字符 ToUnicode 将一个扫描码和Shift状态转换成对应的Unicode字符 UnloadKeyboardLayout 卸载以前有LoadKeyboardLayout函数载入的键盘布局 VkkeyScan,VkKeyScanEx 将ASCII代码转换成虚拟键码和Shift键状态。

    已经看到有很多键盘控制函数。但这些函数的参数基本上都局限在虚拟码和扫描码,也就是说你能够为这些函数提供虚拟码和扫描码,这些函数基本上都能够掌握和使用的。虚拟键码供有256个,很难在这里给出,但我觉得也没必要给出,简单说明一下就可以了。虚拟键码是以“VK_”开头的,比如Ctrl键为VK_CONTROL,Alt键为VK_MENU。只要你打开API文本游览器,选择常数(Consts)后键入“VK_”那么从列表框中可以看到一大堆以“VK_”开头的虚拟键码。另一种技巧是VB本身也有自己的简码表,VK_CONTROL在VB中大概是vbKeyControl了,你把前面的“vbKey”字样去掉,然后换上“VK_”,估计也能查到相关的虚拟键名( 好象其值也一样,比如vbKeyContol的值为17(十进制),而VK_CONTRIL的值为&H11(16进制)。我不敢保证,也没有太多的时间一一对照,有兴趣的朋友可以直接用vbKeyXXXXX来试一试。)。至于扫描码,就依靠MapVirtualKey函数从一个虚拟键码转换一下就可以了。

    为了帮助大家理解好这些函数的实际应用,本教程附带了两个例程,一个是Program4.vbp,另一个是Program8.vbp。 前一个程序是从我的一个演示程序中转化过来的,主要演示按键检测与设定快捷键。这个程序可能有另外的一个用处。举例来说,我一开始不明白键盘上的“Print Screen SysRq”键的虚拟键名是什么。我当然有虚拟键码表,但正如一般人只知道“Ctrl”键名而不知道“CONTROL”这个键名一样(两者实际上是一样),我在列表上无法找到“Print Screen SysRq”这个键的虚拟键名了。后来我启动我这个程序,按了一下“Print Screen SysRq”一看,程序表明它的虚拟键值等于是40(十进制),接着拿Windows提供的计算器一换算,其16进制数据为&H2C。哈,这下就好办了,一看列表,合&H2C对应的虚拟键名为“VK_SNAPSHOT”怎么样,很有意思吧

    第二个程序是我曾经回答一位网由时做的小程序。不幸的是这位网友提的问题在我上一次的硬盘故障中丢失了。大概内容是这样的,很像考试卷里的提问∶“窗体里只有一个文本框,在文本框中输入一些文字后点击一下已经启动的Word,Windows的笔记本等文本编辑器程序窗口,这时,文本框里的内容直接粘贴到这些编辑页中,而不按任何其他的键(如Ctrl+V)。”我的这个程序是以Word为例编写的,要成功地运行它事先您必须启动Word编辑器,并打开一个新的编辑页。

    六、系统函数

    对与Windows的系统函数,我觉得没必要进行特别的说明,因为这些函数根本就没有特别之处。只是,这些函数主要是用来获取和设置系统有关信息的。比如设置桌面壁纸,默认的窗体呀,命令按钮呀之类的颜色呀什么的,还是您自己看更好一些。如下列表∶

    函 数 说 明 Beep 使PC喇叭发出声音 ExitWindowsEx 以各种方式退出和重新启动WINDOWS ExpandEnvironmentStrings 构件环境字串 FreeEnvironmentStrings 释放由GetEnvironmentStrings分配的一个环境字串块 GetCommandLine 取得用于启动应用程序的命令行 GetCOmputerName 取得当前电脑的名字 GetEnvironmentString 取得一个环境块,它提供的对环境变量的访问途径 GetEnvironmentVariable 获得环境块中一个单独的环境变量 GerLastError 取得上一个API函数调用的扩展错误信息。Visual Basic中请用Err对象的LastDLLError属性获得这个值 GetSysColor 取得Windows当前的各种颜色设置 GetSystemInfo 取得与硬件平台有关的信息 GetSystemMetrics 判断一系列系统样式信息;比如菜单栏或窗体标题的高度,垂直滚动条的高度,窗口最小尺寸等等。 GetSystemPowerStatus 取得与电源以及系统状态的有关信息 GetUserName 取得当前的用户名字 GetVersion,GetVersionEx 判断正在运行的Windows和DOS的版本 MessageBeep 生成一个标准系统声音 SetComputerName 设置电脑名 SetEnvironmentVariable 设置一个环境块中的环境变量 SetSysColors 设置Windows对象的当前颜色 SystemParametersInfo 一个功能强大的函数,用来获取与设置众多的系统参数比如保护程序,桌面墙纸,键盘延迟和重复率等等。

    当然,从这些函数中忽乱选择一些函数,也做了一个演示程序(Program7.vbp)。分析代码时请掌握好SYSTEM_INFO和OSSYSTEMINFO结构的用法。

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/9/6 9:02:00
     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客5
    发贴心情 
    第五课∶设备场景
    一、什么是设备场景

    关于设备场景,叫法颇多,有些书上说为设备环境、显示场景,更常见的叫做设备描述表或设备描述体。当然你爱怎么叫随你的便,我还是喜欢说为设备场景。 那么究竟什么是设备场景呢 设备场景是一种Windows对象,而Windows则是一种图形环境,其图形系统令人难以自信地灵活和强大。而实质上,Widnows下的所有绘图都是通过设备场景进行的,而不是直接对窗口和设备本身进行。为了说明设备场景,很多书都拿一些现实生活中的现象来进行对照说明。其中,最常见的是把它比喻为一位画家在作画。我想大家都看过画家是如何画画的,最起码是在电影里或者是在道旁的广告牌上作画的画家。我们可以想象一下∶有个风景秀丽的白云山(是我瞎起的名)上,有位画家一只手拿着调色板,另一只手则拿着画笔,面对一个画板正在写风景画。有些书认为画家的调色板相当于设备场景,有些书则认为画板相当于设备场景,说法不一。鉴于这种情况,我认为还是直接去说明设备场景比较好。

    作为Windows的对象,设备场景实际上是一种Windows内部的数据结构。就象POINTAPI数据结构具有x和y两个属性一样,设备场景同样具有着它自身的属性,只是属性比较多而已,如下表∶

    设备场景属性

    属 性 默 认 值背景色(Background color) 白色(White) 背景模式(Background mode) 不透明(Opaque) 位图(Bitmap) 无(None) 刷子(Brush) 白色刷子(White brush) 刷子起点(Brush origin) 0,0 剪切区(Clipping region) 整个窗口或设备表面(Entire window or device surface) 调色板(Color palette) 默认调色板(Default palette) 画笔位置(Pen position) 0,0 绘图模式(Drawing mode) R2_COPYPEN 字体(Font) 系统字体字间距(Intercharater spacing) 0 影射模式(Mapping mode) MM_TEXT 画笔(Pen) 黑色(Black) 多边形填充模式(Mapping Mode) ALTERNATE 伸缩模式(Stretching mode) BLACKONWHITE 文本色(Text color) 黑色(Black) 视口起点(Viewport origin) 0,0 视口范围(Viewport extents) 1,1 窗口起点(Window origin) 0,0 窗口范围(Window extents) 1,1

    请你多看看这张表,对设备场景都有哪些属性,脑子里应当有个印象。事实上,设备场景的很多属性对应于VB中的Form、PictureBox、Text等窗体或控件的属性。比如,字体、背景色、绘图模式等等。可想而知,很多学VB的朋友尽管并不知道什么叫设备场景,但实质上都不知不觉地使用了设备场景。可以说,设备场景是Windows编程中最重要的概念之一。 对于设备场景,有些朋友可能一时不大好理解,这很自然,不用担心谁都是一样。不知对你能否作为一个帮助,我是把设备场景想象成一种配套的(包括画板、调色板、画笔、刷子等)的绘画工具。其中画板是最重要的,其他的东西都是为这个画板服务的。如过你创建了一个设备场景,就等于是你从百货商店买来了这一套绘画工具,从而具备了绘画的条件。但,你的房间总不是那么宽敞的。为了继续绘出别的画、继续购买新的绘画工具,无用的工具应当及时清理掉。因为设备场景本身是占用内存的。不要担心这会降低运行速度,对计算机来说创建一个设备场景,再删掉一个设备场景,那都是瞬息之间的事情,根本谈不上什么浪费时间,绝对不像跑一趟百货商店那么麻烦、费时。对于绘图,你应当认识的一点是,绘图并不是简单地指绘画,输出文本也是一种绘图过程。尽管如此,API函数中图形函数与文本函数大体都是各自各的。绘画和写文本都是在同样的设备场景中进行,这一点很重要。

    我想,你大概还是没有理解好,不过没有关系,继续往下看好了。本节中请记住一点∶ Widnows下的所有绘图都是通过设备场景进行的。

    二、如何从VB里使用设备场景

    如何从VB里使用设备场景呢VB的设计者们已经为我们想到了这一点,就象为了直接操纵窗体而提供了窗体句柄hWnd一样,为设备场景提供了hDC的句柄属性。很多API函数都是以hDC作为它的一个参数。如果控件没有提供hDC属性,你也可以用GetDC或 GetDCEx函数去获得,不用时就用ReleaseDC函数把它释放掉即可。不过这得需要控件具有hWnd属性。如果连hWnd属性也没有,那就没办法了,大概那根本不是绘图的地方。 按自己的需要也可以创建一个或多个设备场景,需要多少就创建多少。这可能是 CreateCompatibleDC和CreateDC函数的最拿手的好戏。设备场景可以同某一窗口关联,也可以以孤立的方式存在。所谓关联就是说,你在这个设备场景中绘图,内容将立即输出到关联的窗口,所谓孤立就是指尽管你在这个设备场景中绘出了图形,但它只存在于这个设备场景,而不显示在哪里。我们可以通过多个设备场景来对一个最终图形进行光栅运算,从而进行加工,最后把图象传送给已与窗体关联的设备场景,让它显示出来。被创建的设备场景,如果不再使用,应当删去。(这段描述,请参考附带的演示程序WindowDC.vbp。)

    以下表格总结了用于获取和释放设备场景的API函数。

    函 数 说 明 CreateCompatibleDC 创建一个与源DC兼容的内存设备场景。内存设备场景可看作一种对内存中设备的模拟。通过在设备中选进一幅位图,可创建与设备兼容的内存影响。 CreateDC 为指定的设备创建一个设备场景,它通常用于为打印机创建一个DC。 CreateIC 为指定的设备创建一个信息场景(IC)。IC类似于设备场景,只是所需系统开销更少,它可用来获取关于设备的信息,但不能作绘图操作。 DeleteDC 删除一个已经建立的设备场景。可用它释放CreateDC,CreateIC和CreateCompatibleDC 函数建立的设备场景。 GetDC,GetDCEx 为指定窗口获取一个设备场景。若窗口类使用专用DC,用该函数取回设备场景,否则它从Windows缓存中获取一个DC,用GetDCEx才能在窗口使用专用DC时获得一个缓存场景。 GetWindowDC 该函数与GetDC类似,只是取回的设备场景是针对窗口整个,而不是客户区。 ReleaseDC 释放用GetDC,GetDCEx和GetWindowDC获得的设备场景。若DC是一个专用设备场景,该函数无效。 WindowFromDC 判断与指定设备场景关联在一起的窗口句柄。 以下的演示程序是一个非常敾奶茢的程序。这个程序的目的是这样的∶我们要在Text控件中画一个圆。当然我估计没有人会选择Text控件来绘图。我总是喜欢用API做出一些歪门邪道的事情。以下是程序的源代码,你也可以直接启动教程附送的Program1.vbp演示程序。使用方法是,当我们点击Text控件时那里将出现了一个空心圆。

    Option Explicit

    Private Declare Function GetDC& Lib user32 (ByVal hwnd As Long) Private Declare Function Ellipse& Lib gdi32 (ByVal hdc As Long, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) Private Declare Function ReleaseDC& Lib user32 (ByVal hwnd As Long, ByVal hdc As Long)

    Private Sub Form_Load() Text1.Text =  End Sub

    Private Sub Text1_Click() Dim TextDC As Long TextDC = GetDC(Text1.hwnd) 获取设备场景句柄

    Ellipse TextDC, 0, 0, 100, 100 画空心圆 ReleaseDC Text1.hwnd, TextDC 释放设备场景 End Sub 程序的工作原理是这样的。首先在Form_Load()事件中,我们用Text1.Text = 代码来清除Text控件内的所有文本。接着,当我们点击Text控件时,程序在那里画一个圆,用的是API函数Ellipse(这个函数是下一课堂的内容)。Ellipse函数的功能很简单,在左上角为(x1,x2),右下角为(x2,y2)的矩形中画一个内切的椭圆。而Ellipse函数需要一个DC(设备场景句柄),但Text控件却没有提供hDC属性,没有办法只好用GetDC函数来获取∶TextDC = GetDC(Text1.hwnd)。然后做绘图操作,最后用ReleaseDC函数来把设备场景释放掉(关联设备场景,不能删除)。

    如果我们把上述代码中的 TextDC = GetDC(Text1.hwnd) Ellipse TextDC, 0, 0, 100, 100 改成 TextDC = GetDC(0)

    Ellipse TextDC, 0, 0, 500, 500 会怎么样呢不妨你试试看。学会了什么还是直接告诉你吧∶整个屏幕的设备场景句柄总是等于0。

    有意识吧你也可以让Text控件输出一张图片(照片)。方法很简单。你可以使用Picture控件,在那里先装入一个图片,然后用API函数BitBlt把图片传送到Text控件就可以了,由你自己研究好了。

    哈,现在感觉到设备场景真的存在了吧呵呵,接着来吧!

    三、深入设备场景( 逻辑坐标,逻辑窗口,设备坐标,视口 )

    设备坐标(Device Coordinate)又称物理坐标(Physical Coordinate),是指输出设备(显示器,打印机等)上的坐标。通常将屏幕上的设备坐标称为屏幕坐标。屏幕坐标以对象距离屏幕左上角的水平距离和垂直距离来指定对象的位置,是以像素为单位来表示的。X轴方向向右,Y轴方向向下,坐标原点位于窗口左上角。 逻辑坐标(Logical Coordinate)是系统用作记录的坐标。在缺省的坐标模式(MM_TEXT坐标模式)下,逻辑坐标的方向和单位与设备坐标的方向和单位相同。但这还得有个前提条件∶1,窗口为非滚动窗口;2,窗口为滚动窗口,但垂直滚动条位于边框的最上端,水平滚动条位于边框的最左端。

    为了在不同领域使用逻辑坐标,Windows提供了以下8种坐标模式(也称影射模式)。

    坐标模式 说 明 MM_TEXT 设备场景默认坐标模式,也是VB窗体或图片控件的坐标模式,等同于ScaleMode=vbPixels。以像素为逻辑单位,X轴向右为正,Y轴向下为正。也是VC的缺省模式。 MM_HIENGLISH 以0.001英寸为逻辑单位,X轴向右为正,Y轴向上为正。 MM_LOENGLISH 以0.001英寸为逻辑单位,X轴向右为正,Y轴向下为正。 MM_HIMETRIC 以0.01毫米为逻辑单位,X轴向右为正,Y轴向下为正。 MM_LOMETRIC 以0.1毫米为逻辑单位,X轴向右为正,Y轴向下为正。 MM_TWIPS 以一旂緮(1/1440英寸)为逻辑单位,X轴向右为正,Y轴向下为正。 MM_ANISOTROPIC 可以自行设定逻辑单位的长度, X轴向右为正,Y轴向上为正。X轴和Y轴的长度单位可以不相同。 MM_ ISOTROPIC 可以自行设定逻辑单位的长度, X轴向右为正,Y轴向上为正。X轴和Y轴的长度单位必须相同。

    尽管在影射过程中,逻辑坐标到设备坐标的坐标转换是自动进行的,但你也可以用有关的API函数进行逻辑坐标与物理坐标间的坐标转换。有以下两个函数∶

    函 数 说 明 DPtoLP 将设备坐标系统中的点转换到相应设备场景的逻辑坐标系统 LPtoDP 将设备场景逻辑坐标系统中的点转换到物理或设备坐标系统

    逻辑坐标就是设备场景内的坐标。这个坐标系统内有一个逻辑窗口,我们就是在这个逻辑窗口上进行绘图操作的。逻辑窗口本身是有大小的。这个大小正是由设备场景的窗口起点(Window origin)和窗口范围(Window extents)两个属性来决定的。两个属性分别表示逻辑窗口在逻辑坐标上的左上角的点和由下角的点。由于逻辑坐标的坐标模式不一定是缺省的MM_TEXT,所以这两个属性中的x,y值有可能是负数(当然,这不是常见的情况)。 在大多数情况下,我们需要画在设备窗口中的图象实际地输出到设备窗口中。对显示器来讲,设备窗口就是屏幕了。那么这里自然存在这样一个问题∶即,逻辑窗口中的图象应该出现在屏幕的哪一个位置,也是说设备窗口的哪一个位置。Windows早已经为我们想到了这一点∶设备上对应于逻辑窗口的区域叫做视口(Viewport)。既然是和逻辑窗口对应的,那么,视口自然也有大小。这个大小,正是由设备场景的视口起点(Viewport origin)和窗口范围(Window extents)两个属性来决定。

    为了加深理解以上的内容,我做了一个影射过程图示(适合MM_TEXT坐标模式),建议看好了从新阅读以上内容。


    逻辑窗口和视口的大小并不相同的,两者的高宽比率也不一定相同。可以看出,逻辑窗口到视口的影射当中必然存在坐标转换。但不用操心,我们根本用不着管那些事情, Windows会自动的为我们做好这一切,你需要把握的只是逻辑窗口的大小和视口的大小。在MM_TEXT模式下,只要两者的大小一定,不管其大小是多大,逻辑窗口的图象正好伸缩成视口大小并在视口中显示。嗯,你大概能想起VB中的Scale方法吧,该方法用来更改Form、PictureBox或Printer的坐标系统。想必,你已经猜到了,它更改的实际就是设备场景的逻辑窗口的大小。

    设备场景影射函数

    函 数 说 明 GetDCOrgEx 用屏幕坐标定义窗口客户区起点位置。 GetGraphicsMode 定义是否允许增强影射模式。 GetMapMode 定义设备场景当前影射模式 GetViewportExtEx 获取视口范围 GetViewportOrgEx 获取视口起点 GetWindowExtEx 获取逻辑窗口范围 GetWindowOrgEx 获取逻辑窗口起点 OffsetViewportOrgEx 视口起点偏移 OffsetWindowOrgEx 窗口起点偏移 ScaleViewportExtEx 视口范围缩放 ScaleWindowExtEx 逻辑窗口范围缩放

    四、区域和剪裁区域

    请注意,注意、注意、注意!现在,估计有些朋友认为,视口就是窗口在屏幕中的实际位置和大小。如果真的这样认为的话,那你就大错特错了。因为在很多情况下你会发现,实际图象小于窗口或大于窗口。还存在另一个问题。即,屏幕中有很多窗口,按每个窗口一个视口算的话视口也就和窗口一样多了。这些视口会重叠的。那么在重叠的部分中应该显示哪一个又不应该显示哪一个呢为了解决这类问题,Windows提供了一个剪裁功能。 所谓剪裁功能是指若画面超出指定区域(剪裁区)范围将被自动剪切掉而不显示出来的功能。这个功能由Windows自动完成,你所要做的是要么保持默认的剪裁区域,要么重新设定它。至于区域,你就把它理解为多边型也无妨。标准的概念是,区域是指描述设备场景中某一块平面范围的GDI对象。每个区域都有一个对象句柄,并且,同任何GDI对象一样,在不用时可调用API函数DeleteObject(下一课堂中讲)删除。API相关的区域函数,由下表所列。

    区域有句柄

    函 数 说 明 CombineRgn 以选定的方法合并两个区域 CreateEllipticRgnCreateEllipticRgnIndirect 创建圆形和椭圆形区域 CreateRectRgnCreateRectRgnIndirect 创建矩形区域 CreateRoundRectRgn 创建圆角矩形区域 EquaRgn 确定两个区域是否相等 ExtCreateRegion 允许把某一转换应用于区域 GetRgnBox 获取一个矩形,该矩形为选定区域的边界 GetRegionData 取回描述区域的数据 OffsetRgn 平移选定区域 PtInRegion 确定某点是否在选定区域内 RectInRegion 确定矩形的一部分是否在选定区域内 SetRectRgn 修改选定区域以描述一个矩形区

    这样剪裁区域实际上也是属于一个区域,只是它多负有着剪裁的功能。你可以获取一个窗口的剪裁区域句柄,然后用区域函数加工它,再付给该窗口,或者干脆为窗体从新创建一个剪裁区域,从而改变一个窗口的剪裁效果。窗体的整个窗口(包括标题栏和状态栏等)、还有窗口的客户区都有着自己的剪裁区域。剪裁区域一般是矩形,但也可以是任何形状,甚至是由互相分离的多个区域组成。所谓窗口的剪裁区实际上就是设备场景的剪切区(Clipping region)属性。一个窗口默认的剪裁区就是改窗口本身。

    窗口也有剪裁区、窗口的客户区也有剪裁区,这无疑是说明窗口也有设备场景,客户区也有设备场景。由于习惯问题,经常说的斈骋淮翱诘纳璞赋【皵指的是该窗口的客户区的设备场景。你可以设置整个窗口的剪裁区为任何形状以实现任何形状的窗体。这就得靠宝贝函数SetWindowsRng啦!至于整个窗口的设备场景句柄,可以用API函数GetWindowDC来获取(WindowsDC.vbp演示程序中有此函数的用法)。

    区域剪裁函数

    函 数 说 明 ExcludeClipRect 在剪裁区中挖去一个特定巨型区 ExSelectClipRegion 用当前剪裁区组合一个区域 GetCliBox 获取一个矩形,该矩形为剪裁区的边界 GetClipRgn 获取某个窗口的剪裁区 IntersectClipRect 将当前区域和选定矩形的交集设为剪裁区 OffsetClipRgn 按定义量平移剪裁区 PtVisible 确定选定点(当前剪裁区中的)是否可见 RectVisible 确定当前矩形某部分(当前剪裁区中的)是否可见 SelectClipPath 为设备场景选择一个路径作为剪裁区(由于路径的概念问题,此函数在下一课堂中讨论) SetWindowRgn 为窗口设定剪裁区;这使您能够创建出任意形状的窗口。

    本教程附带的演示程序Clip.vbp是为了展示Windows的剪切功能而设计的。另外,坐标转换中还有一个叫世界转换的内容,但它只设计到WindowsNT用户,感兴趣朋友请自己查看有关资料。


    五、设备场景在VB

    有时候,我们可能对设备场景属性进行大量的修改。使用完一个设备场景后,作为一个好的编程习惯,应当把设备场景状态恢复到我们控制这个设备场景以前的状态,以便保证其他代码正确进行绘图工作。这倒并不是说一定,只是一个好的习惯问题而已。 要把VB设备场景恢复到VB控制之前的初始状态,可以使用API函数SaveDC和RestoreDC函数,前者用来把设备场景保存到设备场景堆栈,后者则从堆栈中恢复设备场景属性。还是保持统一风格,画一个表吧∶

    设备场景堆栈函数

    函 数 说 明 SaveDC 将某设备场景状态和属性保存到设备场景堆栈。 RestoreDC 从Windows设备场景堆栈恢复某一设备状态和属性。

    接下来是,让我们研究一下设备场景的自动重画属性方面的问题。我们知道,VB 中Form和PictureBox控件具有AutoRedraw属性,用来决定是否用持久图形或通过Paint事件重绘对象。当AutoRedraw属性设为Flase时,用GetDC函数获取的设备场景句柄和控件提供的hDC句柄正好相同,说明都是指同一个设备场景。但是,当将其设为True时,用GetDC 获取的设备场景句柄不同于hDC属性所返回的场景句柄。有一点是清楚的,在这种场合的确存在两个设备场景。但是,这两个设备场景不可能同时与某一窗体关联的,不然的话,窗体应该显示哪一个设备场景中的图象呢我们可以编写一个小小的程序来考察这个问题,(program2.vbp),程序代码如下∶

    Option Explicit

    Private Declare Function GetDC& Lib user32 (ByVal hwnd As Long) Private Declare Function WindowFromDC& Lib user32 (ByVal hdc As Long)

    Private Sub Command1_Click() Dim MyDC As Long

    Form1.AutoRedraw = True MyDC = GetDC(Form1.hwnd) Text1.Text = MyDC Text2.Text = Form1.hdc Text3.Text = WindowFromDC(MyDC) Text4.Text = WindowFromDC(Form1.hdc) Text5.Text = Form1.hwnd End Sub

    程序运行结果如下(在你的计算机上运行不一定是这些数据)∶ 1、用GetDC来获取的设备场景句柄∶10010,其关联窗体句柄为∶3024 2、hDC书信返回的设备场景句柄∶10234,其关联窗体句柄为∶0 3、Form窗体的句柄为3024

    1、 程序说明,用hDC来获取关联窗口时,函数执行出错,说明hDC并不与窗体关联。所以,当我们用以下代码来向窗体画一个圆时,我们将什么也看不到。(新建一个项目,把下面的代码粘贴上去,就可以运行) Private Declare Function Ellipse& Lib gdi32 (ByVal hdc As Long, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long)

    Private Sub Form_Click() Form1.AutoRedraw = True Ellipse Form1.hdc, 0, 0, 100, 100 End Sub 尽管什么也看不到,但如果你用其他窗体掩盖程序窗体后,让程序窗体再次显示时,你会发现图形出现了。为什么呢原来是Refresh方法所做所谓(请参看后面的表)。

    以下的代码能够直接的把绘图内容显示出来。(新建一个项目,把下面的代码粘贴上去,就可以运行) Private Declare Function GetDC& Lib user32 (ByVal hwnd As Long) Private Declare Function Ellipse& Lib gdi32 (ByVal hdc As Long, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long)

    Private Sub Form_Click() Dim MyDC As Long Form1.AutoRedraw = True MyDC = GetDC(Form1.hwnd) Ellipse MyDC, 0, 0, 100, 100 End Sub 尽管图形出现了,但这次的情况正好相反。也就是说,如果你用其他窗体掩盖程序窗体后,让程序窗体再次显示时,你会发现图形消失了。(但如果遮住图形的一半,图形仍然会存在)。是因为hDC中没有图象,Refresh方法用hDC来覆盖GetDC了。

    所以,如果你使用的是API绘图函数,那么在AutoRedraw = True时候,设计前对预期的结果应当有超前认识。在这种情况下windows是先在hDC中绘图,然后用GetDC来创建临时与窗体关联的设备场景,然后把hDC的内容拷贝到这个临时缓冲设备场景中来最终显示图形。我觉得这个过程已经封装在VB的Line等绘图函数里面了(注意,这只是我的猜测,不一定准确)。如下∶ Option Explicit

    Private Declare Function GetDC& Lib user32 (ByVal hwnd As Long)

    Private Declare Function Ellipse& Lib gdi32 (ByVal hdc As Long, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) Private Declare Function BitBlt& Lib gdi32 (ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) Private Declare Function ReleaseDC& Lib user32 (ByVal hwnd As Long, ByVal hdc As Long)

    Private Sub Form_Click()

    Dim MyDC As Long Form1.AutoRedraw = True Ellipse hdc, 20, 20, 80, 80 实际使用中可以用Form1.Refresh代替以下三行,以简化代码。 MyDC = GetDC(Form1.hwnd) BitBlt MyDC, 0, 0, Form1.Width, Form1.Height, Form1.hdc, 0, 0, vbSrcCopy ReleaseDC Form1.hwnd, MyDC End Sub 运行此程序后,可以发现,不管任何时候,图象总是显示在窗体之中。以下表格说明了绘制和刷新窗口有关的VB属性。

    VB属性

    AutoRedraw=FALSE


    AutoRedraw=TRUE


    hDC属性


    窗口设备场景句柄,即与窗口关联。


    指向内存设备场景的句柄(常说为内存设备场景),与窗口兼容,这幅位图叫作固定图象位图,针对窗口则叫屏幕。此设备场景不与窗口关联。


    Picture属性


    窗口背景图象位图的句柄


    背景图象位图的句柄,改变它将引起固定图象位图立即刷新以反映它。


    Image属性


    窗口固定图象位图句柄,绘制时该图象不能改变。


    窗口固定图象位图句柄,由hDC属性指给设备场景的句柄。


    Cls方法


    清除窗口使其只能现实背景位图


    清楚固定位图到背景色,并将复制到背景位图(若有)拷贝到固定固定位图。


    Refresh方法


    将背景图片(若有)复制给窗口,然后然后产生Paint事件。所有的绘图都是通过hDC设备场景直接对窗口进行。


    将固定图象复制到窗口,所有的绘图都是通过hDC设备场景直接的对固定图象位图进行。


    在这一节中要学会的关键的一个应用问题是,会使用内存设备场景。这里AutoRedraw = True时的以hDC为句柄的设备场景就是内存设备场景,它不与窗体关联。因此可以在此设备场景中进行多方面的图形加工处理,最后传送到用GetDC获取的设备场景,这样图象的加工过程就可以隐藏掉了。作为例子,你可以下载《VB前线》《源码解析》的Play024.zip文件(滤波器演示程序)。

    六、设备场景信息函数

    在本课堂一开始,讨论什么是设备场景的时候,我们已经给出了设备场景的各个属性。本节主要讨论有关获取这些属性信息的函数及其应用实例。设备场景的信息是用Windows通用设备接口(GDI)提供的GetDeviceCaps函数获取设备场景信息。此函数的功能是,根据指定设备场景代表的设备的功能返回信息。此函数有两个属性,其一是设备场景句柄,其二是返回信息的类型参数。对于使用该函数来说,主要的就是认识这些常数的含义。由于,本人精力有限,在此不能提供中文文档了,请参考MSDN等其他有关资料。

    总算把设备场景讲完了,下一堂课我们将讨论如何在这个设备场景中绘图。我想,那是很轻松的事情。当然,只要你理解好了设备场景。

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/9/6 9:02:00
     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客6
    发贴心情 
    第六课∶绘图函数
    一、位操作

    前几天,在很远很远又是那么远的地方,有位网友来信问一些有关位操作的内容。我一开始不大注意这个环节,认为估计大家都能知道。可现在来仔细一想,也并非如此。《Win32 API开发人员指南》一书中也讲了一些位操作的内容,但它位于一开始的象是概论的部分。那么,我想,对位操作不太熟悉的朋友,可以通过以下我对这位网友的回答,学习或加深一些认识。 这位网友的提问非常认真,叫我不得不也跟着认真回答。以下是来信中的一段内容∶ Not And Or 的用法(因为资料里只是讲了什么True呀False呀,请麻烦您了)经过实践(正所谓实践出真知嘛),得到以下结论

    12 And 15 =12 简化了的---返回小 12 Or 15 =15 返回大

    12 And 16 =0 返回零 12 Or 16 = 28 返回 相加

    Not 12 = -13 (取整数部分+1)的相反数 Not 100 = -101 (取整数部分+1)的相反数

    以下是我的回复内容∶(对原文做了很多改动和加工。) 是位操作运算符。 例如 3 and 1 = 1 来说,为什么结果会等于1呢 原因是这样的。3等于是11(2进制),1等于是01(2进制) (这里只考虑了两位,实际一般是8位、16位和32位等)

    And就是斖睌的意识。二进制数据中,1代表真(True),0代表假(False)。其运算规则就是∶在两个数相对应的位中,如果两个位同时是1则得1,否则就得0。 具体来讲,11 和 01,首先底位都同时是1,因此得到1,而高位来说前一个数(11)是1,而另一个(01)则是0,这样就不符合斖睌的要求了,结果得到0。这样11 and 01 就等于是01了 。二进制的01就是十进制的1。因此 可以判断 3 And 1=1 啦。不知,学会了没有 不访拿你的例子来说,比如 ∶12 And 15=12。结果为什么会等于12呢

    让我们分析一下。首先将各数据转换为2进制数,如下∶ 12=1100 15=1111

    这样从左开始按斖笔则得1,否则得0 數脑蚓涂梢缘玫100。表示如下∶

    1110

    1111 and

    1100

    我们知道二进制的1100是等于十进制的12,因此可以得出12 And 15 =12的结论

    接下来看一看or运算吧。意识当然是《或者》啦。两个数相对应的位中,只要有一个是1就能得到1,所谓∶这个等于1 或者那个等于1,反正有一个是1就得1,否则得0。 再看你的一个例子∶

    12 Or 15 =15 按照前述类似的步骤可得∶

    12=1100 15=1111

    然后按照刚才讲的规则,可做如下的计算∶

    1100

    1111 or

    1111

    我们知道二进制的1111是等于十进制的15,因此可以得出12 or 15 =15的结论

    or 操作一般用于位设定。比如说 (现在我是举例,并不一定这样) ,某一个数值代表一个窗体的样式,我们一般称之为样式位数据。第一位=1 则表示窗体有标题栏,第2位等于1则表明窗体有滚动条。其他各位也表示其他的信息,但现在我们只讨论这两个位。现在,我们要使窗体不但具有标题栏,而且还想让它具有滚动条, 一般采取的办法是,先用某特定函数获得这个窗体的样式位数据(在WIN32中,实际的窗体样式位数据经常是Long类型的数据),比如我们得到了一个样式位数据MYWINDOWSTYLE=12,当然等于是1100(二进制)。看它的第一位和第二位都是零,说明当前窗体确实没有标题栏和滚动条。要让窗体具有标题栏和滚动条,我们需要做到使其第一位和第二位都变成1。

    为此需要两个常数。当然这个常数自然是由API文本游览器提供的(这里是作为例,文本游览器中根本没有与此一致的内容) Const WS_CAPTION=1 Const WS_VSCROLL=2

    (注∶1=0001,2=0010)

    现在我们可以通过or操作进行具体的位设定了。如下∶

    MYWINDOWSTYLE = MYWINDOWSTYLE or WS_CAPTION or WS_CAPTION 其结果将等于MYWINDOWSTYLE =15,即1111。(1100 or 0010 or 0001 =1111) 最后,我们把这个数据,通过某一函数再传送到实际的窗体处理函数当中,窗体的样式就变化了,变成一个具有标题栏和滚动条的窗体。 这样,or 就变得有点象同时的意识了,如∶具有标题栏的 同时 又具有滚动条。但实际运算中仍然是表现为 或者 。千万不要混淆啦。

    Not 运算符就有点怪。如果凭空猜测的话很可能猜测成1则得0,0则得1。那么究竟是不是这样呢其实是这样。但以下的事实会使一些朋友惊奇∶ 即Not 12= -13 因为,按上述的规则得出结论的话Not 12,即Not 1100似乎应当等于是0011, 即3,可结果竟是 -13。那么该如何解释这一事实呢 其实,通常我们在数字的前面冠以敾驍-敺爬幢硎臼恼海诩扑慊惺怯脭0 敽蛿10表示正,1表示负)来表示的。如果按8位来考虑问题的话,12应当是00001100。那么通过Not运算以后,它应当是11110011了。

    那么11110011又应该如何解释呢请看,左边第一个1算是说明符号敚瓟吧,不过剩余的7位,即1110011也并不等于13呀应当是等于115。 -115  这是怎么一回事 原来呢,这里牵涉到补码计算问题。补码的计算规则是这样的。如果第一位(高段)为0,表示正数,用其他各位来表示数值部分;但如果第一位为1,表示负数,数值部分可用以下算法来获得∶ 设数据X的数据位总数为n,各位依次表示为 x1,x2...xn,那么 X补=2^(n-1)-0x2...xn (注∶这是整数补码定义,小数补码定义有所不同,读者可以自己翻阅有关材料)

    也就是说11110011=-(2^7-01110011)=-(10000000-01110011)=-(00001101)=-13 这么麻烦不用担心,有个很好的演算规则,这就是∶在原有的数据上加1后把符号取反。所以这也就很好理解了。 VB中TURE=-1的。Not(-1) = 0 , Not 0 = -1 ,您已经能够理解这一点。也因此Not TURE=FLASE,Not FLASE=TURE。但在IF判断语句中,您应当小心使用NOT运算符。 If 语句会把 所有不等于0的数据看成是TURE(条件成立)。有必要忠告您,如果您不能断定某一个数据n (不是逻辑型数据)肯定是-1或0两个数之间的一个数据(但我仍然不推荐这种余地),千万别用Not运算符号(这是我推荐的)。比如n=1的时候,If 1 then也好, If Not -1 then也好,都是一个样,都是满足If语句的条件成立。很多API函数是返回1的。而且API函数中不大能看到逻辑型数据,反正到目前为止我还没有看到。所以If Not n Then这样一个语句应当写成If n0 then 或者If n=0 then的形式来表示。尤其是在API,请记住在API千万要这样。

    现在回过头来想一想And和Or运算。请问,在那里不存在补码运算吗也是存在的。只是我们还没有讨论有负数的情况。您可以自己研究看看,很容易就能明白到的。好了继续来,让我们继续讨论我们的Not。当然以下的算式对您来说已经不是什么加一个  号的事情了。

    Not 12= -1×(12+1)= -13

    Not 13= -1×(-13+1)= 12 现在,请您不要想别的事情,不要老想那位白天在大街上看到的长得不怎么样的小姑娘,来记住这样一个要点∶Not运算符同And运算符相结合常用于 清除位 位操作运算。 比如∶ x%=x% and (not &h0001%) 作用是设置数据x%(x% 指的是Integer数据类型的数据x)的第1位为0。

    再给出一个例子。以下运算用于清除位9 x%=x% and (not &h0200%) 为什么呢。因为,&h0200%=0000000100000000 ,呵呵,不用我再多一嘴吧 那么为什么会有这种结果呢。对此我不想在这里深入了。因为,在上面,我都已经讲了,只要你按照这些规则认真思考一下,或者算一算就可以弄清楚其中的奥秘了。 位操作中还有其他一些运算符,比如xor等,感兴趣的朋友,就自己那个什么好了。另外,今后您可能遇到敼庹ぴ怂銛这样的名词,不是别的,就是位运算。 好了。接下来,一边回顾上述内容,一边来看看实际的应用程序吧。以下给出的程序代码是教程三中的一个附带应用程序的代码内容。这个程序的功能是,使单选框控件的那个小圆空来回设置到靠左和靠右。大概,您已经想起来了吧。

    Option Explicit

    Private Declare Function GetWindowLong& Lib user32 Alias GetWindowLongA (ByVal hWnd As Long, ByVal nIndex As Long) Private Declare Function SetWindowLong& Lib user32 Alias SetWindowLongA (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long)

    Private Const BS_LEFTTEXT& = &H20&

    Private Sub Command1_Click(Index As Integer) Dim f&, dl& f& = GetWindowLong(Option1.hWnd, GWL_STYLE) Debug.Print f& 这里设一个

    If Index = 0 Then f& = f& Or BS_LEFTTEXT Else f& = f& And Not BS_LEFTTEXT End If Debug.Print f& 这里,再设一个 dl& = SetWindowLong(Option1.hWnd, GWL_STYLE, f&) Option1.Refresh End Sub

    其实很简单,f& = GetWindowLong(Option1.hWnd, GWL_STYLE) 用来获取样式位数据 f& 。 然后,结合上面讲的内容,首先是∶ f& = f& Or BS_LEFTTEXT 的作用是设置数据f&的第6位为1。 这是因为&h20&=0000000000000000000000000100000 (注∶后面的& 说名此数据是长整型数据,共32位)

    其次就是上面所说的《Not运算符同And运算符相结合 常用于 清除位》这句话的具体应用了,即把f& 的第6位恢复为0。 f& = f& And Not BS_LEFTTEXT 可以看到Not运算符的优先级比And大(先计算Not)。最后,把改动的样式位数据回放到控制中∶ dl& = SetWindowLong(Option1.hWnd, GWL_STYLE, f&)

    为了便于观察,在上述的代码中,我特别加了两行 Debug.Print f& 调式代码 。我想,您应当知道他们会起什么作用 程序运行结果如下(两个Command1各被点击了一下,Index =0和Index=1)∶

    1409359876 1409359908 1409359908 1409359876

    可以看出,第一次点击过程中(Index=0),数据从1409359876 变成 1409359908 ,然后在第二次点击过程中(Index=1),从 1409359908恢复到了 1409359876 现在可以比较一下这两个数据(可以用WINDOWS提供的科学型计算器来转换看看∶10进制到2进制),如下∶

    1409359876 = 1010100000000010010000000000100 1409359908 = 1010100000000010010000000100100 是不是所谓水落石出  

    二、创建GDI绘图对象

    今天我们要讨论的是Win32 API中最有有趣的部分───用绘图函数完成图形输出。可以说,所有前面讲的内容都是本课程的前期准备。当时,我们在一些试例程序中偶尔用了一些绘图函数,可能当时您有些不太好理解。没有关系,只要您已经来到了这里,并且对前期的各内容有一点点的蒙胧记忆,那已经是足够了。因为,前期的各内容,必须与本课堂中内容相结合才能形成一个完整的理解。看完了本期教程以后,再回头看过去的几个教程,对您来说问题会变得更加清晰,透明。

    我们已经讲过,Windows中的绘图是在设备场景中进行的。设备场景有两种,一是关联设备场景,之所以说它是关联设备场景,只是因为设备场景是同某一窗口关联在一起的。关联的意识就是,只要你在这个设备场景中绘图,那么绘图内容自动反映(显现)在窗体中,使得您能够看到。那么另一种设备场景是不关联的设备场景啦。当然,正如你已经猜到的那样,这种设备场景,就算你在其中绘了图,它也不会显示在窗口中的。那么,这两种设备场景在其内部结构上有什么区别呢其实没有区别,大概同已婚和未婚的关系一样,一个有老婆,一个还没有老婆,只不过就是这样。

    既然,与窗口不关联的设备场景中的绘图内容是看不到的,那么它又有什么用途呢嗨,别小看它,很多图象是需要在这种设备场景中加工的,目的是为了让您看不到图形的加工过程。等到图形加工完了以后,可以一次性地把完成的图象传送到其他已经与窗口关联的设备场景,让它显示出来。当然效果是更好的。 那么,在真正的绘图以前,我们必须学会如何进行选笔,配色。是不是绘图总得去选择一个笔或者画刷吧,而且得考虑用什么颜色来绘图。 画笔和画刷是最常用的GDI绘图对象。其中,画笔是定义如何画线的GDI对象。WIN32的标准画笔具有三个属性,分别是颜色、宽度和线型。画笔颜色用来定义线条的RGB颜色,实际使用的颜色与设备有关。而GDI能自动选择与设备最接近的颜色。宽度属性的单位是逻辑单位。标准画笔可画出的线型有∶实线、不可见线和几种虚线、点线。注意,只有实线与不可见线的宽度能大于1。WIN32还提供了扩展画笔,准备以后接触的时候再讲。

    刷子的用途是填充区域。它定义一块小区域(一般是8×8像素),然后和WINDOWS95的桌面背景图案平埔操作一样,把这个小块中定义的图案复制到整个填充区域中。 刷子主要有三种类型。其一是,实体刷。块图为单一色的固定颜色,可用RGB来确定颜色。其二是,图样刷。这时块图就是一个用户指定的小位图,当然不能大于8×8像素点。最后一种是阴影刷。说是阴影刷,实际上是由一些各种类型的交叉的网格线来构成。这些究竟采用哪种网格线,就得由一些BS_为前缀的参数来指定。那么如何去获得这些画笔或花刷呢可以采用以下列出的API函数。(有关其函数说明均可在APIBROW中找到)

    函 数 说 明 CreateBrushIndirect 在一个LOGBRUSH数据结构的基础上创建一个刷子 CreateDIBPatternBrush 用一幅与设备无关的位图创建一个刷子,以便指定刷子样式(图案) CreateDIBPatternBrushPt 用一幅与设备无关的位图创建一个刷子,以便指定刷子样式(图案) CreateHatchBrush 创建带有阴影图案的一个刷子(阴影图案见注解) CreatePatternBrush 用指定了刷子图案的一幅位图创建一个刷子 CreatePen 用指定的样式、宽度和颜色创建一个画笔 CreatePenIndirect 根据指定的LOGPEN结构创建一个画笔 CreateSolidBrush 用纯色创建一个刷子 ExtCreatePen 创建一个扩展画笔(装饰或几何) GetStockObject 取得一个固有对象(Stock)。这是可由任何应用程序使用的windows标准对象之一

    例1∶创建一个红色实线画笔,画笔宽度为3个像素点 Dim NewPen As Long Private Const PS_SOLID = 0 NewPen&=CreatePen (PS_SOLID,3,RGB(255,0,0)) 注∶其中PS_SOLID常数代表实线例2∶创建阴影刷子 LOGBRUSH结构的定义如下∶ Private Type LOGBRUSH lbStyle As Long lbColor As Long lbHatch As Long End Type 以下代码餍了创建一个 刷子样式为 阴影(BS_HATCHED) ,阴影类型为十字交叉(HS_CROSS)的红色画笔。 Dim BrushInfo As LOGBRUSH BrushInfo.lbStyle = BS_HATCHED

    BrushInfo.lbColor = RGB(255,0,0) BrushInfo.lbHatch = HS_CROSS NewBrush = CreateBrushIndirect(BrushInfo)

    例3∶用纯色创建刷子(这个例子中是红色) NewBrush =CreateSolidBrush(vbRed)

    同样,用其他几个函数,按照其用法可以创建相应的GDI绘图对象。现在,您大概了解有以上这些函数和,理解给出的几个例子就可以了。稍后,我们结合实际例子,更深入地探讨这些函数的用法。

    三、拿起和放下画笔(GDI对象)

    现在我感觉好象向一群绘画系的学生讲课,尽管自己不怎么会绘画。首先是练基本功,怎样拿起画笔和放下画笔,这可能是绘画专业学生首先要学习的吧当然,更广义地讲应当是怎样选择和删除GDI绘图对象。 在通过前述方法来创建一个GDI对象句柄(上例中的NewBrush,NewPen等)以后,为了使用它们,我们必须用SelectObjecth API函数把它们选入相应的设备场景。一个设备场景在某一时刻、在每一种类型中只能拥有一个对象,如一个画笔和一个刷子一个位图等。 SelectObjecth函数的用法非常简单,需要记住的是,此被调用后,如果成功将返回旧的对象句柄。你需要把它保存起来。当然,这一过程只需要把返回值附值于某一Long型变量就可以了。如∶

    OldPen&=SelectObject(Picture1.hDC , NewPen&)

    接下来该做什么呢对对,这位同学说的对∶绘图。该怎么绘图呢不,不,不要着急,这个问题,我们留在下一节中讨论,现在你只需记住,这里可以画些圆呀、矩形呀、添充多边型呀的操作。那么,绘图操作结束以后该怎么办呢答案是∶应当把旧的绘图对象回设到设备场景中去。如下∶

    SelectObject Picture1.hDC,OldPen&

    这样,设备场景将恢复到我们为其选入绘图对象以前的状态。因为,我们不能断定其他绘图函数会使用什么样的绘图对象。因此把原来的绘图对象放回去乃是一个上策。但,如果你接着要为设备场景选择另一个对象,这个步骤可以留在后面进行。那么在这次的选入过程中就没有必要保存旧的对象句柄了,这是因为SelectObject函数返回的旧对象的句柄就是刚才我们为其选择的句柄。 绘图也完了,设备场景也恢复了原始状况,那么就操作告一段落了吗不,还有一点。您最好把您自己创建的GDI对象删除掉,释放掉刚才使用过的资源。操作如下∶

    DeleteObject NewPen 又如∶ DeleteObject NewBrush 其实,您不删除这些对象资源,应用程序退出时会自动释放的。这是因为在Win32中,资源为每个应用程序私有的。由于这种原因,应用程序之间也不能共享一个GDI对象。但是,删除你所创建的GDI对象仍是一个好的编程习惯。既然不用,留着它做什么呢,何必占用资源空间呢 外,您千万千万切记,切记,千万∶不要删除已经选入设备场景的系统GDI对象。 还有一点,GetStockObject函数返回的对象是系统对象,请不要用DeleteObject函数删除它,否则会出现非常非常可怕的事情───你的硬盘将被永远用不了啦。@@~ 呵呵,吓唬你一把,其实没那么严重。不过,我想您大概不是明知整了坏,偏向坏里整的人吧 如果是的话,随便整好了。

    OK,以下展示了使用GDI对象的API函数。

    函 数 说 明 DeleteObject 用这个函数删除GDI对象,比如画笔、刷子、字体、位图、区域以及调色板等等。对象使用的所有系统资源都会被释放 EnumObjects 枚举可随同指定设备场景使用的画笔和刷子 GetCurrentObject 用于获得指定类型的当前选定对象 GetObjectAPI 取得对指定对象进行说明的一个结构。windows手册建议用GetObject 这个名字来引用该函数。GetObjectAPI在vb中用于避免与GetObject关键字混淆 GetObjectType 判断由指定句柄引用的GDI对象的类型 SelectObject 每个设备场景都可能有选入其中的图形对象。其中包括位图、刷子、字体、画笔以及区域等等。一次选入设备场景的只能有一个对象。选定的对象会在设备场景的绘图操作中使用。例如,当前选定的画笔决定了在设备场景中描绘的线段颜色及样式 除了DeleteObject和SelectObject以外的其他函数用于从系统或指定设备场景中获取有关GDI对象的信息,一般不十分常用。 这样,假如我们要用画笔和刷子来做一些绘图朝着的话,编写代码的大概步骤是这样的。 Dim NewPen As Long Dim NewBrush As Long Dim OldPen As Long Dim OldBrush As Long

    NewPen& = CreatePen(PS_SOLID, 3, RGB(255, 0, 0))  创建画笔 NewBrush& = CreateSolidBrush(vbRed)  创建画刷 OldPen& = SelectObject(Picture1.hDC,NewPen&) 添加绘图操作代码

    OldBrush& = SelectObject(Picture1.hDC,NewBrush) 添加填充操作代码 SelectObject Picture1.hDC,OldPen& SelectObject Picture1.hDC,OldBrush& DeleteObject NewPen& DeleteObject NewBrush& 注意一点,要用API绘图函数,并非一定要创建画笔和刷子。完全可以使用现有的GDI对象,直接调用函数来绘图。记住,设备场景中总有个默认的画笔和刷子,问题是它符不符合您的要求了。但我觉得,在绘图之前选择画笔是个好习惯。有些VB功能也可以结合使用,比如你想用红色画笔,你可以设置Forcolor属性为红色、想加宽画笔的宽度,可以设置DrawWidth属性等。

    四、绘图属性与绘图函数

    到目前为止,我们已经学会了绘图所需要的一切准备工作。上一节中最后给出的代码就说明这一点。代码中,现在只缺少具体的绘图代码。本节就讨论关于如何绘图的问题。 在接触绘图函数之前,首先需要了解绘图属性。设备场景定义了一系列绘图属性。这些绘图属性定义了刷子和画笔与窗口或设备表面当前内容相互作用的方法。比如,当前画笔的位置、当前背景颜色、圆弧和矩形的绘制方向、光栅操作模式等等。虽然后面给出了很多属性控制函数,但用VB自身的函数和方法属性,更容易实现。比如,设置背景模式,只要设置控件的BackColor属性就可以很轻松、带愉快地完成。但是,如果是要在一个不与窗口关联的自建设备场景中绘图的话,想必依靠这些函数是不可逃避的。

    线光栅操作∶我们已经知道,光栅操作是一种位操作。通常你想用画笔进行绘图时,都假定画笔色彩只是简单的绘制到显示器或设备上。实际上,WINDOWS支持16种不同的线绘图模式,它们定义了一条线如何与显示器上已有的信息组合。这些模式就叫做线光栅操作(有时叫ROP2模式)。并且它们被作为绘图模式引入到了VisualBasic。ROP2光栅操作相当于设置VB的DrawMode属性。 背景模式∶阴影刷子、虚线画笔和文本都有一个背景。对于阴影刷,它是指阴影线之间的区域,对于虚线画笔,则指点和虚线之间的区域。而对于文本,它是指每个字符单元的背景。背景模式决定了WINDOWS如何处理这些背景区。它可以是不透明的,也可以是透明的。若是不透明的,则背景区设置为背景色;否则如果是透明的,则背景区域保持原状。

    当前位置∶在VB中,要画一条直线其实非常简单,采用Line方法就可以,而且能够在一个语句中表达完成。如Line (5,5)-(10,10) 但在API中并不这样简单了(但也不是太麻烦)。要画直线,需要首先设定直线的起点。一般用MoveToEx函数来完成。然后在下一行代码中绘制直线,如LineTo 10,10。MoveToEx函数是经常使用的函数之一,用来确定绘图前的起始位置。。

    绘图属性控制函数

    函 数 说 明 GetArcDirection 画圆弧的时候,判断当前采用的绘图方向 GetBkColor 取得指定设备场景当前的背景颜色 GetBkMode 针对指定的设备场景,取得当前的背景填充模式 GetCurrentPositionEx 在指定的设备场景中取得当前的画笔位置 GetMiterLimit 取得设备场景的斜率限制(Miter)设置——斜率限制是指斜角长度与线宽间的比率 GetNearestColor 根据设备的显示能力,取得与指定颜色最接近的一种纯色 GetPolyFillMode 针对指定的设备场景,获得多边形填充模式。 GetROP2 针对指定的设备场景,取得当前的绘图模式。这样可定义绘图操作如何与正在显示的图象合并起来 MoveToEx 为指定的设备场景指定一个新的当前画笔位置。 SetArcDirection 设置圆弧的描绘方向 SetBkColor 为指定的设备场景设置背景颜色。背景颜色用于填充阴影刷子、虚线画笔以及字符(如背景模式为OPAQUE)中的空隙。也在位图颜色转换期间使用。 SetBkMode 指定阴影刷子、虚线画笔以及字符中的空隙的填充方式 SetMiterLimit 设置设备场景当前的斜率限制 SetPolyFillMode 设置多边形的填充模式。 SetROP2 设置指定设备场景的绘图模式。与vb的DrawMode属性完全一致。

    同VisualBasic相比较,API提供了功能更强大的绘图函数。大部分绘图函数的用法都非常简单明了,只要按其说明使用就可以,觉得没有必要我多加说明。

    WindoesAPI绘图函数

    函 数 说 明 AngleArc 用一个连接弧画一条线,参考注解 Arc 画一个圆弧 ArcTo 画一个圆弧,并更新当前位置 CancelDC 取消另一个线程里的长时间绘图操作 Chord 画一条弦线(椭圆的平分线) Ellipse 描绘一个椭圆,由指定的矩形围绕。椭圆用当前选择的画笔描绘,并用当前选择的刷子填充 FillRect 用指定的刷子填充一个矩形 FloodFill 用当前选定的刷子在指定的设备场景中填充一个区域。区域是由颜色crColor定义的 FrameRect 用指定的刷子围绕一个矩形画一个边框(组成一个帧),边框的宽度是一个逻辑单位 GetPixel 在指定的设备场景中取得一个指定像素的当前RGB值 InvertRect 通过反转每个像素的值,从而反转一个设备场景中指定的矩形 LineDDA 枚举指定线段中的所有点 Pie 画一个扇形 PolyBezier 绘一条或多条贝塞尔(Bezier)曲线。 PolyBezierTo 绘一条或多条贝塞尔(Bezier)曲线,并将当前画笔位置设为前一条曲线的终点 PolyDraw 描绘一条复杂的曲线,由线段及贝塞尔曲线组成 Polygon 描绘一个多边形,由两点或三点的任意系列构成。windows会将最后一个点与第一个点连接起来,从而封闭多边形。多边形的边框用当前选定的画笔描绘,多边形用当前选定的刷子填充 Polyline 用当前画笔描绘一系列线段。使用PolylineTo函数时,当前位置会设为最后一条线段的终点。它不会由Polyline函数改动 PolylineTo 同上,并设置当前画笔位置用当前选定画笔描绘两个或多个多边形。根据由SetPolyFillMode函数指定的多边形填充模式,用当前选定的刷子填充它们。每个多边形都必须是封闭的 PolyPolygon 用当前选定画笔描绘两个或多个多边形。根据由SetPolyFillMode函数指定的多边形填充模式,用当前选定的刷子填充它们。每个多边形都必须是封闭的 PolyPolyline 用当前选定画笔描绘两个或多个多边形 Rectangle 用当前选定的画笔描绘矩形,并用当前选定的刷子进行填充 RoundRect 用当前选定的画笔画一个圆角矩形,并用当前选定的刷子在其中填充。X3和Y3定义了用于生成圆角的椭圆 SetPixel 在指定的设备场景中设置一个像素的RGB值,并返回该点的颜色 SetPixelV 在指定的设备场景中设置一个像素的RGB值

    我把上表中大部分的函数的用法例举到了本教程附带的program1.vbp中。另外, Bezier曲线的用法比较有趣。如果你用过3D Studio三维动画制作软件就知道,其中的很多绘图工作,尤其是二维平面绘图,就是采用Bezier曲线技术。本教程附带的program3.vbp 程序简单展示了这种技术的应用。《前线》网站源码解析中的第24号(滤波器演示程序)、第26号(如何用指定颜色填充不规则封闭线框区域)等程序,也是这里部分函数的好例程,可以下载看看。

    Windows还提供了一些更特殊的绘图函数,你可以在Windows的内部用它们来绘制控件外框、标题栏、3D控件和桌面等系统对象。

    Win32 API其他绘图函数

    函 数 说 明 DrawEdge 用指定的样式(包括3D效果)描绘一个矩形的边框 DrawEscape 换码(Escape)函数将数据直接发至显示设备驱动程序(在vb里使用:能够使用。但由于Escape对设备有较强的依赖性,所以除非万不得以,尽量不要用它) DrawFocusRect 画一个焦点矩形。这个矩形是在标志焦点的样式中通过异或运算完成的(焦点通常用一个点线表示)。如用同样的参数再次调用这个函数,就表示删除焦点矩形 DrawFrameControl 这个函数用于描绘一个标准控件。例如,可描绘一个按钮或滚动条的帧 DrawState 这个函数可为一幅图象或绘图操作应用各式各样的效果 GdiFlush 在绘图操作前注意队列。 执行任何未决的绘图操作。注释 GdiGetBatchLimit 判断有多少个GDI绘图命令位于队列中 GdiSetBatchLimit 指定有多少个GDI绘图命令能够进入队列 PaintDesktop 在指定的设备场景中描绘桌面墙纸图案

    这里有几个函数很有趣,比如DrawEdge、DrawFrameControl。使用他们可以非常轻松地绘出按钮控件、编辑框控件等的外观。我已经把常用的函数的用法包含到了附带程序program2.vbp。

    五、路 径

    应当说,路径是较为高级的话题,尽管它不是难于理解的。我学到的有关路径的知识,来自于Dan的《Visual Basic 5.0 WIN32开发人员指南》一书中的不到两页的内容中,在其他的书中尚未看到。 糟糕的是路径没有句柄,所以说它不是GDI对象的成员。不过,千万要记住一点,任何一个设备场景只有一个路径。从这一点来看,就算为路径设置了句柄也是多余的。从我的感觉来看,路径像是在一个设备场景中绘出的任意形状的多边形区域(尽管它不是区域)。

    我在路径中体验出的一个好处就是,创建一个路径后,可以把它转换为区域。这一点可以用PathToRegion函数来完成。一旦这一步成功了就好办了,得到区域句柄以后就可以和其他区域对象一样处理了。总而言之,通过路径我们可以很轻松地创建复杂的图形区域。 创建一个路径非常简单。具体形式如下∶

    dl& = BeginPath&(Out.hdc) (绘图) dl& = EndPath&(Out.hdc) 在(绘图)的位置上编写代码来绘出什么图形,就能形成什么样的路径了。不过,并非任何绘图函数都可以产生路径的。可以用来产生路径的函数如下所列。

    函数 Windows NT Windows 95 AngleArc Yes No Arc Yes No ArcTo Yes No Chord Yes No Ellipse Yes No ExtTextOut Yes Yes LineTo Yes Yes MoveToEx Yes Yes Pie Yes No PolyBezier Yes Yes PolyBezierTo Yes Yes PolyDraw Yes No

    Polygon Yes Yes Polyline Yes Yes PolylineTo Yes Yes PolyPolygon Yes Yes PolyPolyline Yes Yes Rectangle Yes No RoundRect Yes No TextOut Yes Yes

    看完这个表,我想Windows95的用户就可能有点心痛∶这么多函数用不了!嗨,我也没办法,只好责怪微软了。以下是有关路径的API函数∶

    API 路径函数

    函 数 说 明 AbortPath 抛弃选入指定设备场景中的所有路径。也取消目前正在进行的任何路径的创建工作 BeginPath 启动一个路径分支。在这个命令后执行的GDI绘图命令会自动成为路径的一部分。对线段的连接会结合到一起。设备场景中任何现成的路径都会被清除。参考下表,其中列出的函数都可记录到路径中 CloseFigure 描绘到一个路径时,关闭当前打开的图形(将当前路径段转为闭图) EndPath 停止定义一个路径。如执行成功,BeginPath函数调用和这个函数之间发生的所有绘图操作都会正式成为指定设备场景的路径 FillPath 关闭路径中任何打开的图形,并用当前刷子填充 FlattenPath 将一个路径中的所有曲线都转换成线段 GetPath 取得对当前路径进行定义的一系列数据 PathToRegion 将当前选定的路径转换到一个区域里 SelectClipPath 将设备场景当前的路径合并到剪切区域里 StrokeAndFillPath 针对指定的设备场景,关闭路径上打开的所有区域。用当前画笔描绘路径的一个轮廓,并用当前刷子填充路径 StrokePath 用当前画笔描绘一个路径的轮廓。打开的图形不会被这个函数关闭 好了,其实也没啥,很简单的,请阅读program4.vbp演示程序吧,你会触目惊心!哇!这就是路径!

    课后练习

    A、制作画笔和画刷观察器。

    提示∶

    ①创建一个图片框控件,用当前画笔来画一个矩形(使用Rectangle,巨型的内部将自动被当前刷子填充)。我们要准备依此来观察当前画笔和当前刷子。

    ②设置5个滚动条,用来分别调整画笔的颜色,画笔的宽度,画笔的样式(实线、虚线实体画刷颜色,阴影画刷样式。画笔的创建可以用函数CreatePen,实体画刷的创建可以用函数CreateSolidBrush,阴影画刷的创建可以用函数CreateHatchBrush。(图样刷子的创建留在下一课再讨论,暂时可以不练)

    通过以上思路,当某个滚动条移动的时候,图片框中的图象相应地进行变化,如宽度加厚, 颜色变暗等。

    B、创建一个类模块来模仿Check控件的最基本功能。

    提示∶

    ①需要添加的属性有Wight,Height,Value属性

    ②需要添加的事件有Click事件。可以用Timer控件来作事件源。

    ③控件外观可以用DrawFrameControl函数来绘制。

    说真的,还真的想不起来好例子。如果学完了位图就好了。请等待,下期就是位图。

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/9/6 9:03:00
     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客7
    发贴心情 
    第七课∶位图
    一、概述

    在Windows中每屏是一个图形图像,灵巧的Windows制作系统,面对庞大的图形编程任务,建立了为绘画多彩的边界、按钮、图标、字体的函数库。当然啦,通过Windows API,这些函数都是可调用的。所谓Windows显示屏幕以及数量众多的打印机其实都是属于“光栅设备”。在光栅设备中,一幅图象由多条扫描线以及能访问的单独像素构成。Windows也支持非光栅设备,比如绘图仪等,本人对此一无经验,无从谈起,想来也差不到哪儿去。以下只以显示器为重点展开讨论。

    计算机视频系统的核心是内存。该内存包含代表着显示图案的数据,而这些图案显示在监视器(显示器)上。每次鼠标移动时,内存中的少量数据发生变化。然后你会看到鼠标指针在屏幕上移动。每次以及每一个图形操作都会影响视频内存,因为GDI执行计算并以相应方式更改视频内存。计算机中还有一个存放图象数据的内存,叫做位图内存。位图内存与视频内存的重要的一个区别是∶位图内存看不到,而视频内存则能够看到。也就是说放在位图内存的图象数据并不反映在屏幕上,而视频内存中存放的图象信息则反映在监视器上。如果把驻留在位图内存的数据移动到视频内存中,那么图象将显示在监视器上。以一个桌面图标为例,图标从磁盘加载到内存中(位图内存)中,然后内存被移到视频内存中的适当地址上,这样当视频内存通过视频硬件被显示到屏幕上时,图标成为可见的。

    从图象的种类来讲,Windows中存在两种位图,一种叫与设备有关位图(或叫设备相关位图),另一种叫与设备无关位(或叫设备无关位图,或DIB)。除非特别声明,W indows中的位图都是与设备有关的位图。有些朋友一说位图,就可能想到BMP图象,但它是属于与设备无关的位图。在编程的领域我们常常说位图,一般并不是指BMP图象,而是指对他们来说也是想象中存在的那个叫与设备有关的位图。另一个区别与设备有关位图和与设备无关位图的重要依据是,判断该位图是否具有句柄。具有句柄的位图便是与设备有关的位图,因为它是GDI对象之一。常见的BMP图象则属于与设备无关的位图。我们一般不叫它位图,而是叫做“BMP图象”或者叫做“DIB”,它是一种数据的组织方式,并非GDI绘图对象。 可以把与设备无关位图理解为对与设备有关位图数据的一种标准格式的数据保存方式。比如我们平常看到的附加名为BMP的文件。这种位图文件会在文件头上放置文件的组织信息,形式上讲和一些数据库文件的文件头起着同样的作用━━描述文件的结构。文件头后面紧跟着的是图象的颜色数据。由于这种机制的存在,使得与设备无关的位图可以在各种设备之间进行读写。

    与设备无关的位图只有一种格式,而与设备有关的位图则可能...大概有多少种设备就有多少种格式吧鬼才知道!幸运的是您根本没有必要了解它的格式,只需简单理解为保存图象数据的内存块就可以了。就好像您跟本不用了解我长得什么模样,也照样能和我打交道一样。

    PC的视频系统显示一个像素的矩阵(整齐排列的小光点)。在一个基于字符的环境中,视频系统包含一套硬件编程的形状,代表标准字符的位置及各种画图字符(直线、角线、实线等等)和几个符号(笑脸、钻石、铲等)。通过发送给系统一个ASCII码或ANSI字符码来显示一个字符。而在Windows这样一个图形显示系统中,计算机及其软件定义出现在屏幕上的图形形状。这些形状是用位图来代表的。用这种方法的最主要的优点是图象和文本能以不同的大小、字体、方式来显示。那么,准确地说,什么是位图呢它们是数据元素的集合,这些数据元素决定在每个屏幕位置上显示什么样的颜色。在单色的(黑和白)图形图像中,每位代表一个屏幕像素,0对应黑(没有颜色),1对应白(显示色)。不久您就会看到,位图能描述彩色图像。每个像素的位数决定能在单个位图中出现的不同色彩的数目。除了每个像素一位的单色视频,还有其他三种∶每个像素4位产生16种颜色,8位产生256种颜色,24位产生16777216种颜色(补充我已经亲眼看到了32位显卡)。如下表所示∶

    Windows支持的彩色

    每个像素的位数 颜色总数 典型设备 1 2 单色图形 4 16 标准备VGA 8 256 256色VGA 16 32768或65536 32K或64K色SVGA 24 16777216 24位真彩色设 计算机显示系统的性能决定Windows对彩色图象的处理。现在常看到的显示器是SVGA(AGP也出来了,我的就是,但资料少无从谈起)。理论上讲,SVGA系统能显示惊人的含有16777216种颜色数组。把能够显示16000000种颜色范围的系统通常称为真彩色(true color)。那么真彩色的真实意思是什么呢它源于对是否一个彩色显示器能显示无限大范围的颜色的想象,图象将看起来完全逼“真”。真彩色的显示用24位来决定每个像素的颜色。24位被分成三组,每组8位,来表示红、绿、蓝三种颜色。(参考∶自然界的颜色是通过红、绿、蓝(RGB)三种颜色来组合而成的。叫做三元色。)

    二、与设备无关位图(DIB)

    所谓DIB是指与设备无关的位图。DIB并不是Windows的对象,它没有自己的句柄,实际上是一种数据文件。和很多数据文件相同,DIB位图文件的开头部分有它内在的数据结构描述部分。不少书做了一些示意图来说明这个问题,但我觉得还是亲眼目睹DIB文件的内部更好一些,百闻不如一睹嘛。 现在我们准备要看的BMP文件是一个256色位图,文件名叫Hua.bmp ,这个文件曾在《VB前线》的演示程序第25号中使用过。

    首先让我们查看一下文件在磁盘中的大体状况∶ Dvbplayvbplay25dir

    Volume in drive D is PRO Volume Serial Number is 268F-AB4B Directory of Dvbplayvbplay25

    . DIR 03-27-99 2047 . .. DIR 03-27-99 2047 .. PLAY25 VBW 111 09-20-99 2343 play25.vbw MSSCCPRJ SCC 193 04-07-99 2352 MSSCCPRJ.SCC BITMAP CLS 12,711 06-16-99 2127 Bitmap.cls MYMEMORY CLS 3,419 04-08-99 207 MyMemory.cls PLAY25 VBP 636 06-16-99 2142 play25.vbp HUA BMP 96,446 04-04-99 1206 hua.bmp PL25_1 FRM 3,865 06-16-99 2125 pl25_1.frm 7 file(s) 117,381 bytes 2 dir(s) 569,049,088 bytes free

    看到了吧它的大小是9646个字节。接下来,我们可以用DEBUG的d命令以16进制形式来显示文件的文件头信息。操作如下∶

    Dvbplayvbplay25debug hua.bmp -d 100 1000 0EAE0100 42 4D BE 78 01 00 00 00-00 00 36 04 00 00 28 00 BM.x......6...(. 0EAE0110 00 00 69 01 00 00 06 01-00 00 01 00 08 00 00 00 ..i............. 0EAE0120 00 00 88 74 01 00 C4 0E-00 00 C4 0E 00 00 00 00 ...t............ 0EAE0130 00 00 00 00 00 00 00 00-00 00 80 80 80 00 00 00 ................ 0EAE0140 80 00 00 80 80 00 00 80-00 00 80 80 00 00 80 00 ................

    0EAE0150 00 00 80 00 80 00 40 80-80 00 40 40 00 00 FF 80 ......@...@@.... 0EAE0160 00 00 80 40 00 00 FF 00-40 00 00 40 80 00 FF FF ...@....@..@.... 省略了57行∶50 - 16 -1 = 39(16进制) = 57(10进制) 0EAE0500 CC 00 AD E8 FD 00 78 AE-D3 00 1F 38 65 00 66 89 ......x....8e.f. 0EAE0510 F0 00 63 9B C3 00 79 AD-DA 00 08 13 73 00 0A 1C ..c...y.....s... 0EAE0520 A5 00 BB ED FD 00 83 C1-E5 00 2A 50 74 00 7C B3 ..........Pt..

    0EAE0530 ED 00 56 82 BD 00 B3 66-B3 B3 6C B3 BD 8B C9 C9 ..V....f..l..... 0EAE0540 BD 50 21 60 6C 6C 44 44-B3 44 6C 86 44 66 44 D9 .P!`llDD.Dl.DfD. 0EAE0550 44 B3 6C C9 87 7B B9 7B-44 BD 87 60 87 C9 6C AB D.l..{.{D..`..l. 0EAE0560 60 D6 6C AB AB 2A 88 55-88 7B 7B 7B 7B 3D 3D 3D `.l...U.{{{{=== 0EAE0570 C5 3D C5 24 7B 7B 7B 7B-B4 B9 7B B9 B5 C6 3D 7B .=.${{{{..{...={ (以下全部省略) 0EAE1000 B3 .

    -q 注∶若使用Dvbplayvbplay25debug hua.bmp  Temp.txt则可以把结果保存到Temp.txt文件,并可加以分析研究。但这样给出命令后什么都看不见。这时,你应当键入d 100 1000这个命令后回车,然后再按q就可以返回到DOS命令行提示符下。这段内容是这样获取的。详细内容,可参考有关DOS命令参考手册。用pctools5.0也可以。

    通过上述方式获取的是该位图文件第一个字节开始的若干个字节的16进制形式的文本内容。以下,我们具体分析一下。在分析以前为了在整体上把握DIB文件的结构,有必要先给出其整体情况,如下表格所示∶

    DIB文件的组成

    三大部分 字节 说明 BITMAPFILEHEADER 14 用红色表示的部分 BITMAPINFO BITMAPINFOHEADER 40 用深绿色表示的部分 RGBQUAD(结构数组) 最小0,最大4×256。(在16位以上的位图中,根本不存在此部分) 用深红色表示的部分数据部分 大于0(具体大小信息记录在BITMAPINFOHEADER结构中) 用下划线表示的部分 可以看出,DIB的文件头主要由两个部分组成,即BITMAPFILEHEADER和BITMAPINFO, 而BITMAPINFO又分为BITMAPINFOHEADER和RGBQUAD两个小部分。让我们一个一个分析。首先是开头以红色表示的14个字节,如下

    42 4D BE 78 01 00 00 00-00 00 36 04 00 00 这是BITMAPFILEHEADER结构部分。BITMAPFILEHEADER结构如表所示∶

    BITMAPFILEHEADER结构

    结构内各字段 数据类型 说明 bfType Integer 指定文件类型,必须 BM bfSize Long 指定位图文件大小,以字节为单位 bfReserved1 Integer 保留未用,必须设为0 bfReserved2 Integer 同上 bfOffBits Long 从此结构到位图数据位的字节偏移量 此结构,主要记录了DIB文件大小以及结构有关的信息。在很多情况下,可以计算来获取这些这些信息,所以很多人并不访问这个结构内的数据。不少人说 BITMAPFILEHEADER结构记录着无大用处的信息。其中bfOffBits说明的是从此结构到位图数据位的字节偏移量。通过对此字段的访问,我们可以知道这个位图文件的颜色数据是从哪里开始的,或者可以知道DIB文件头的结构有多长。

    m_BMFileHeader.bfType = 42 4D

    这是一个Integer整型数据,对DIB文件来说此数据必须是42 4D。那么为什么定为42 4D呢,其实是在ASCII码表中42即十进制的66表示大写B,4D即十进制的77表示M,也就是说字符擝M數囊馐丁?梢远哉飧稣褪莸姆梦世磁卸衔募欠袷荄IB(或BMP)文件。

    m_BMFileHeader.bfSize = BE 78 01 00 = &H178BE = 96446个字节

    说明文件的总长度。注意,右侧的数据是高字节,即右侧字节的地址比左侧字节的地址值大,所以BE 78 01 00 = &H178BE。在上面,用dir命令观察文件时,我们已经看到文件的长度正是96446个字节。如∶ HUA BMP 96,446 04-04-99 1206 hua.bmp

    m_BMFileHeader.bfReserved1 = 00 00 (系统保留,没有设置数据) m_BMFileHeader.bfReserved2 = 00 00 (系统保留,没有设置数据)

    m_BMFileHeader.bfOffBits = 36 04 00 00 = &H00000436 = 1078 =14 + 40 + 256  4

    (从文件开始处起,带下划线的数据位的以字节为单位的长度,总共1078个字节。对真彩色图象来说,这个数据经常是36 00 00 00,即54。说明文件头只有BITMAPFILEHEADER(14个字节) + BITMAPINFOHEADER(40个字节)的部分,即没有RGBQUAD部分。为什么没有RGBQUAD部分也可以稍后你就明白怎么一回事。继续往下看,继续)

    与此结构紧连在一起的下一个叫BITMAPINFO的结构,其组成如下表所示∶

    BITMAPINFO结构(表二)

    结构内各字段 数据类型 说明 bmiHeader BITMAPINFOHEADER 一个 BITMAPINFOHEADER bmiColors RGBQUAD 一个 RGBQUAD结构组成的数组 从此表中可以看出,结构内部还存在两个结构,即BITMAPINFOHEADER和RGBQUAD。因此,从某种意义上讲,位图文件的文件头结构可以说为三个结构来构成的 BITMAPFILEHEADER 、BITMAPINFOHEADER和RGBQUAD。但,必须认清的是BITMAPINFO结构并不是离开BITMAPINFOHEADER和RGBQUAD这两个结构而独立存在的。BITMAPINFOHEADER结构的长度是固定着的,为40个字节。BITMAPINFOHEADER结构的有关说明,可参考如下表∶

    BITMAPINFOHEADER结构(表三)

    结构内各字段 数据类型 说明 biSize Long 结构长度(40) biWidth Long 指定位图的宽度,以像素为单位 biHeight Long 指定位图的高度,以像素为单位 biPlanes Integer 指定目标设备的级数(必须为 1 ) biBitCount Integer 每一个像素的位(1,4,8,16,24,32) biCompression Long 指定压缩类型(BI_RGB 为不压缩) biSizeImage Long 指定图象的大小,以字节为单位 biXPelsPerMeter Long 指定设备水平分辨率,以每米的像素为单位 biYPelsPerMeter Long 垂直分辨率,其他同上 biClrUsed Long 在颜色表中实际使用的色彩索引的个数,用O表示全要使用 biClrImportant Long 指定认为重要的颜色索引个数,用 0 表示所有颜色均重要 BITMAPINFOHEADER结构主要记载了数据区的大小及颜色信息。必须认清的是, biSizeImage字段说明的是图象的大小,也就是数据部分的大小,而不是文件的大小。如果我们只把位图的数据部分读入到内存(而把结果部分读入到数组),就需要按此大小来申请内存大小。为了便于对照,我把上面给出的示范数据在这里重贴一下了∶ 0EAE0100 28 00 BM.x......6...(. 0EAE0110 00 00 69 01 00 00 06 01-00 00 01 00 08 00 00 00 ..i.............

    0EAE0120 00 00 88 74 01 00 C4 0E-00 00 C4 0E 00 00 00 00 ...t............ 0EAE0130 00 00 00 00 00 00 ................

    分析说明如下∶

    biSize = 28 00 00 00 = &H28 = 40 此结构的长度,总共40个字节。总是这个数据。

    biWidth = 69 01 00 00 = &H169 = 361 图象的宽度(像素质为单位)。把此图片放到PictureBox控件,并运行如下命令∶ Text1.Text = Picture1.ScaleX(Picture1.Picture.Width, vbHimetric, vbPixels) 结果为∶360.9827。可以看出VB的计算存在一点误差。但如果你用整形变量来读取这个数据,那么就一样了。

    biHeigh = 06 01 00 00 = &H106 = 262 图象的高度,请参照bitWidth .

    biPlanes = 01 00 = &H1 = 1 必须为1。

    biBitCount = 08 00 = &H8 = 8 每个像素的位。当前我例举的这个叫hua.bmp的位图文件是以8个位来表示一个像素的,即一个字节。一个256(2的8次方)色位图能显示的颜色总数限定在256种颜色,但这并不意味着任何256色位图都只能显示相同的256种颜色。一个256色位图所能够显示的256种颜色被规定在该位图的RGBQUAD结构中。256色位图所能显示的颜色范围,能够达到设备所允许的范围(16位增强模式中可能的颜色数为2的16次方,24位真彩模式中可能的颜色数为2 的24次方),但必须是其中的256个颜色。RGBQUAD结构所描述的颜色值用来产生调色板。在这种情况下数据区内存放的是对这256种颜色的索引值,而不是实际颜色。当用1位来表示一个像素的时候,RGBQUAD的数组个数为2,存放着黑色和白色两个颜色,而数据区存放着1和0两个索引值;当用4位来表示一个像素的时候,则RGBQUAD的数组个数为16,存放着16种颜色,数据区存放着0至15的索引号;当8位的时候存放256种颜色,数据区存放着0至255的索引号;而当像素位超过或等于16位的时候,RGBQUAD结构部分不存在,数据区存放着以16位或24位表示的实际颜色。在这种时候就不存在调色板了。其中的原因一想就知道,难道我们要设上万种的调色板项

    biCopression = 00 00 00 00 = &H0 = 0 指定压缩类型。不是说BI_RGB 为不压缩吗让我查查,BI_RGB究竟多少 来喽! Const BI_RGB& = 0& ,呵呵,原来这种文件叫做没有压缩。还有∶Const BI_RLE4& = 2& 和 Const BI_RLE8& = 1& 估计没有什么用处,想压缩就采用JPG吧,何必用BMP

    biSizeImage = 88 74 01 00 = &H17488 = 95368 图象数据的长度(大小)。从数据关系上来讲,应该是∶图象数据的长度+文件头长度=文件长度。让我们验证一下,是否是这样∶

    biSizeImage + m_BMFileHeader.bfOffBits = 95368 + 1078 = 96446 = m_BMFileHeader.bfSize @@~果然如此!


    biXPelsPerMeter = C4 0E 00 00 = &HEC4 = 3780

    biYPelsPerMeter = C4 0E 00 00 = &HEC4 = 3780

    水平分辨率和垂直分辨率。

    ibClrUsed = 00 00 00 00 = &H0 = 0

    颜色表(调色板)中使用的索引个数。既然是0,说明全要使用了。全就是256个,再多了不可能。你千万不要用此数据来判断RGBQUAD数组的个数。

    biClrImportant = 00 00 00 00 = &H0 = 0

    指定认为重要的颜色索引个数,用 0 表示所有颜色均重要。请大家多多去选用0吧,这样才能有利于团结。

    与BITMAPINFOHEADER 结构不同RGBQUAD是一个数组,其长度(RGBQUAD结构长度×数组元素个数)可以是多种情况,但不能超过256个。RGBQUAD结构如表所示∶

    RGBQUAD结构(表四)

    结构内各字段 数据类型 说明 rgbBlue Byte 指定彩色中蓝色成分的多少 rgbGreen Byte 指定彩色中绿色成分的多少 rgbRed Byte 指定彩色中红色成分的多少 rgbReserved Byte 保留,可设为0 0EAE0130 00 00-00 00 80 80 80 00 00 00 ................ 0EAE0140 80 00 00 80 80 00 00 80-00 00 80 80 00 00 80 00 ................ 0EAE0150 00 00 80 00 80 00 40 80-80 00 40 40 00 00 FF 80 ......@...@@.... 0EAE0160 00 00 80 40 00 00 FF 00-40 00 00 40 80 00 FF FF ...@....@..@.... 以下省略(还很长)

    您大概已经注意到了,我为什么用红色标示了一些 00 呢原因是因为它们都是白痴,只占地方,不起作用,是微软公司保留的RGBQUAD结构中的rgbReserved字段。每个 00 和 00 和之间有三个字节,当然,他们分别表示RGB颜色的了。这样颜色表中索引号为0的颜色应当是等于RGB(&H0,&H0,&H0),索引号为1的颜色应当是等于RGB(&H80,&H80, &H80),索引号为2的颜色那就是等于RGB(&H0,&H0,&H80),依次类推,至到索引号为255的那个颜色。

    OKay!现在已经把文件头讲得够细的了,不能再细,再细了就没戏了。接着看一下数据部分。

    0EAE0530 ED 00 56 82 BD 00 B3 66-B3 B3 6C B3 BD 8B C9 C9 ..V....f..l..... 0EAE0540 BD 50 21 60 6C 6C 44 44-B3 44 6C 86 44 66 44 D9 .P!`llDD.Dl.DfD.

    以下省略

    我们可以看到第一个像素点的数据是B3,即179,说明此颜色相当与颜色表中的索引号为237的那个颜色。那么那个颜色究竟什么颜色呢糟糕,给我省略了,没有列出来。来找一个没有省略的吧,看看有没有。好象没有。算了,就到这里吧。其实找也是那么一回事。不过我还是告诉你吧,因为你自己找的话可能找不到。原因是∶行是以逆序存放的。也就是说,位图文件的像素数据部分中的第一行实际上是图象中的最后一行。Dan说∶除非将BITMAPINFOHEADER 结构的 biHeight 段设为负值,否则起点就位于左下角。但大多情况下biHeight并非负值,所以起点一般也就在左下角了。但这并不是说扫描行的起点等于图象的高度,起点还是等于0,终点才等于高度。也就是说扫描行的编号是从最后一行开始顺序排列为 0,1,2,3 的。

    现在,我们已经完成了对一个位图文件的全面分析。如果还有需要讲的话,那么有两点。第一点是关于真彩色位图。已经在上面提到过,真彩色位图是没有颜色表的。这种图的数据部分存放的是实际的RGB颜色。那么如何知道没有颜色表呢其实很简单,好象上面也已经讲到了,m_BMFileHeader.bfOffBits 将等于 &H36,即54。还有biBitCount 会是大于等于16位。

    第二点,是一个很讨厌的规定。我们把位图中的一行数据信息叫做扫描线。Windows 规定,每一个扫描线必须结束与一个32位的边界。也就是说,一个扫描线的位长度(按位计算)必须整除于32,或字节长度必须整除于4 。也就是说如果只有8位,那么拿空白的24 位来补充,如果只有48位,那么也就拿空白的16位来补充。我们可以证实一下∶

    在刚才的图片中,我们已经知道图象的宽度为361个像素点,高度为262个像素点。那么数据区的总的像素点个数为361×262=94582。本例中一个字节代表一个像素,但字节个数并不等于94582。正如已经看到的那样,是为biSizeImage = 95368。还缺少786个字节。这说明一些位给补上了。总共是262行,因此说明每行补充了786÷262=3个字节,即24位。为什么每行补充了24位呢现在看来只有一种可能。也就是,32位等于是4个字节,因此一行361个字节除以4以后,必须剩下1,Windows才补充了不足的3 个字节。呵呵,361 MOD 4 = 1 @@~

    计算DIB各行中的字节数(扫描线长度)的VB代码总结如下∶

    imageWidth --位图的宽度(像素为单位) bmBits --每像素的位个数

    Function GetLineWidth(bmBits As Integer, imageWidth As Long) As Long

    GetLineWidth = imageWidth

    Select Case bmBits

    Case 1

    GetLineWidth = (GetLineWidth + 7)  8

    Case 4

    GetLineWidth = (GetLineWidth + 1)  2

    Case 8

    Case 16

    GetLineWidth = GetLineWidth  2

    Case 24

    GetLineWidth = GetLineWidth  3

    Case 32

    GetLineWidth = GetLineWidth  4

    Case Else

    GetLineWidth = 0 error

    Exit Function

    End Select

    If (GetLineWidth And 3&)  0 Then _

    GetLineWidth = (GetLineWidth And &HFFFFFFFC) + 4

    End Function

    以下给出了与设备无关位图有关的函数

    函 数 说 明 CreateDIBitmap 在一幅DIB的基础上创建与设备有关位图 CreateDIBSection 创建一个DIBSection对象(一种新的位图对象,和普通设备相关位图相似,但也能在内存中指定一个缓冲区,用以保存DIB格式的位图数据。具体内容已超出本讲座的范围,读者自己参考有关资料) CreateDIBColorTalbe 为DIBSection对象取得颜色表信息。 GetDIBits 在与设备无关位图里载如来自一幅与设备有关位图的数据。很不错的函数。 SetDIBColorTalbe 为DIBSection设置颜色表信息。 SetDIBits 在与设备相关位图里用来自一个DIB的数据设置图象。 SetDIBitsToDevice 将来自DIB的数据直接设到一个设备。可用它将数据从DIB直接传给屏幕或打印机。 StretchDIBits 将来自DIB的数据设到设备场景,同时根据需要伸缩图象。有些设备支持用这个函数将数据直接设到输出设备。可用GetDeviceCaps函数判断具体是否支持。常用函数之一。 好了,大家稍微休息以下,下一节我们学习与设备有关的位图。
    微软技术社区联盟成员、全球最大的华语Excel资源站点、学习Office技术的最佳技术社区          | ExcelHome首页 |  新闻  |  文章  |  下载  | 论坛  |  Blog       dvbbs收藏本页
    联系我们
    论坛帮助 excelhome恢复默认设置
    回家注册 搜索中文快报我能做什么 >> 与大家分享你所知道的Office相关资源吧 搜一搜相关精彩主题 Excelhome技术论坛 → Office Resource → 『 资源共享 』 → [转载]Win32 API讲座
    论坛使用帮助与技巧 ExcelHome最佳学习方法 ExcelHome优秀会员奖励计划

    三、与设备有关的位图(DDB)

    与设备有关的位图,相对与与设备无关位图来讲,比较简单。只所以简单是因为我们根本没有必要去学习它的结构。因为,与设备有关的位图的格式总是与其设备场景(顺便说一下,很多书把设备场景叫做“设备上下文”)有关。但2色位图除外。如果你正坐在计算机前,不要认为计算机中只存在一种与设备有关的位图,其实不然。所谓设备并不是指一台计算机,内存,显示缓存,打印机,等都有各自的位图数据格式。不要对与设备有关的位图进行对其结构的任何猜测。你见过我吗?没有吧?但你是否觉得与我交往仍然是很有效呢?我们可以通过Email,因特网进行交流。你就把我理解为一个人,一个远方的朋友就可以了,是不是?对与设备有关的位图也就这样想就可以了。它的确是存在着的,就在计算机的某个设备内部(比如内存,打印机)。当然我们不能通过Email跟它交往,而是要靠函数,范围包括创建,获取信息,设置信息,销毁等等。用惯了你会发现,这也很得劲儿,其难度也就相当与给我发一个E-Mail── 填写需要的地址,需要的标题和内容,然后发送。当然,这里要做的是为一个函数的调用为其添写需要的参数。如果你真的想见它一面,我倒有个方法,是家传秘方呦!好好听,你可以拿锤子把你的计算机用力,请记住一定要用力,用力砸一下,然后打开机箱看看它出来了没有,一般成功率能达到99%。但由于存在那1%的失败的可能,我得先声明如果见不到后果自负∶)

    使用与设备无关位图的过程当中始终不能忘记的一点是,确保位图与设备的兼容性。你不能把一个内存设备场景中的位图直接选入到打印机设备场景,会出现异常或错误。这里所说的设备其实就是指设备场景。为了创建一个与指定设备场景兼容的设备相关位图,可以使用CreateBitmap或者CreateCompatibleBitmap。但两个函数的功能是不同的。我的经验是,在创建一个Mask(掩模位图)图象的时候使用CreateBitmap,而创建一个彩色位图的时候使用CreateCompatibleBitmap。还有几个函数,如CreateDIBitmap,它可以根据一个DIB(设备无关位图)的基础上创建一个与设备相关位图。反正,你应该注意到,几乎所有的与设备相关位图有关的API函数都具有一个hDC参数,有时候看起来没有必要的,但应当清楚这主要是为了保证设备的兼容性而给出的。说明与设备有关位图的有些信息是记录在设备场景中的。

    作为GDI对象,设备相关位图被创建后,需要把它选入到设备场景,这和向设备场景选入一个画笔完全一样,可以用SelectObject函数,只是在先前要写画笔句柄的参数位置上写下位图句柄即可。当你不再使用一个被你创建的位图的时候,应当用DeleteObject函数删除之,以释放系统资源。为了删除它,你还需要用SelectObject函数把原来的位图选回到设备场景,这样你为设备场景选入过的位图就退了回来,处于可以删除或重新被选入的状态。这需要你在为设备场景选入一个你自己创建的位图时保存旧的(原先的)位图句柄。反正这和操纵画笔或画刷之类的GDI对象完全一样。同样,不可将一个位图同时选入两个不同的设备场景。

    创建和使用位图的常用技巧是,创建一个DC或多个DC以及相应的位图,然后在后台(用户看不到)进行各种光栅运算和加工处理后,最后把形成的图象一次性发送到关联设备场景中(关联设备场景中的图象会自动影射到视频内存,如PictureBox和hDC所代表的内存。详细情况请参考前期教程设备场景部分)中(常用BitBlt),以获取速度和最大限度地避免屏幕闪烁(后面给出了例子)。这主要是因为向屏幕输出图象的函数执行速度比较慢,而且图象的加工过程对用户来说没有必要看到的。为了创建若干个设备场景,你需要多次用CreateCompatibleDc函数(这也是最简单的方法),它可以使你获得一个与指定设备场景兼容的设备场景。反正你应该保证创建位图中所使用的hDC参数和创建设备场景中所使用的hDC参数保持一致。别忘了最后用DeleteDC来删除你自己创建的设备场景。在删除设备场景前,应当从该设备场景中抽出为其选入的位图,并将其销毁。以上这种技巧在一本VC书上叫做“双缓冲”,意识是把操作分成前台和后台两部分同时进行。

    接下来给出与设备有关位图的函数吧,请您不要客气随便看。

    与设备有关位图函数

    函 数 说 明 CreateBitmap 创建一幅位图,并选择性地初始化位图数据 CreateBitmapIndirect 在一个BITMAP数据结构的基础上创建一幅位图 CreateCompatibleBitmap 创建与指定设备场景兼容的一幅位图 GetBitmapBits 获取位图的像素位数据 GetBitmapDimensionEx 取得一幅位图的大小 LoadBitmap 从资源文件中载入一幅固有的系统位图 LoadImage 一个常规用途的函数,用于装载图象、图标及指针 SetBitmapBits 根据一个数据缓冲区设置位图图象。使用GetDIBits吧,更好用。 SetBitmapDimensionEx 设置位图的大小。

    位图传输函数

    函 数 说 明 BitBlt 位块传输。将一个图象区域传到另一个图象区域。必须掌握到熟背。 PatBlt 图案块传输。根据指定图案(就象由一个刷子表示的那样)填充一个图象区域。 GetStretchBltMode 进行伸缩处理时,用于判断Windows删除线段或像素方式。 MaskBlt 执行复杂的图象传输,同时进行掩模(MASK)处理 PlgBlt 平行四边形块传输。允许我们伸缩、扭曲及放置一幅图象。这个函数我在Windows95中用过,可始终没有成功。功能看起来很不错。 SetStretchBltMode 进行伸缩处理时,用于决定Windows删除线段或像素的方式。 StretchBlt 将一幅位图从一个设备场景复制到另一个。源和目标DC相互间必须兼容。这个函数会在设备场景中定义一个目标矩形,并在位图中定义一个源图象。源矩形会根据需要进行伸缩,以便与目标矩形的大小相符。 DIB并不存在于设备场景中(device context)。在它被选入设备场景或在它被画入设备场景前,DIB必须被翻译为DDB。你可以用StretchDIBits函数完成这一工作。本教程提供了一个源代码例程。通过它你能够学习DDB到DIB,再从DIB到DDB的转化过程。程序名为vbplay46.vbp。实际上是源码解析中的演示程序第46号。

    为了使用好位块传输,你需要掌握图形光栅方面的知识。其内容和上一期给出的位操作运算在其原理上相同。只是需要进行的并不是一个、两个或四个直接等的数据,而是一个位图数据。位图数据也是以字节来构成的,只是长度要长得多。为了使用光栅运算,你需要有一个自己的表格,以便从中查找并计划光栅方案。以下是Dan的书中的,也是我最喜欢的一个表(详细的中文说明格式的表不如它好用)∶

    SRCCOPY Destination = Source

    SRCPAINT Destination = Source OR Destination

    SRCAND Destination = Source AND Destination

    SRCINVERT Destination = Source XOR Destination

    SRCERASE Destination = Source AND (NOT Destination)

    NOTSRCCOPY Destination = NOT Source

    NOTSRCERASE Destination = (NOT Source) AND (NOT Destination)

    MERGECOPY Destination = Source AND Pattern

    MERGEPAINT Destination = (NOT Source) OR Destination

    PATCOPY Destination = Pattern

    PATPAINT Destination = (NOT Source) OR Pattern OR Destination

    PATINVERT Destination = Pattern XOR Destination

    DSTINVERT Destination = NOT Destination

    BLACKNESS Destination = 0

    WHITENESS Destination = All bits set to 1

    可惜,少一个很重要的。可别担心,本老师有办法∶

    &H220326 Destination=(NOT Source) AND Detination

    注∶以上,Destination指的是目标位图(BitBlt中前一个DC中的),Source是来源位图(BitBlt中的后一个DC中的)。

    以下给出一个例子,是我常用的一个函数。它用来实现透明复制位图。你可以在你的程序中直接粘贴使用。它总结了本节给出的内容。

    作者(xing) 1999年10月17日 整理备用

    功能∶

    透明复制位图

    参数表∶

    hDestDC -------- Long,目标设备场景

    x,y ------------ Long,对目标DC中目标矩形左上角位置进行描述的那个点。用目标DC的逻辑坐标表示

    nWidth,nHeight - Long,欲传输图象的宽度和高度

    hSrcDC --------- Long,源设备场景。如光栅运算未指定源,则应设为0

    xSrc,ySrc ------ Long,对源DC中源矩形左上角位置进行描述的那个点。用源DC的逻辑坐标表示

    TransColor ----- OLE_COLOR,被透明处理的颜色

    Sub TransBlt(ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal TransColor As OLE_COLOR)

    Dim dl As Long


    Dim OrigColor As Long

    Dim OrigMode As Long


    Dim saveDC As Long

    Dim maskDC As Long

    Dim invDC As Long

    Dim resultDC As Long


    Dim hSaveBmp As Long

    Dim hMaskBmp As Long

    Dim hInvBmp As Long

    Dim hResultBmp As Long


    Dim hSavePrevBmp As Long

    Dim hMaskPrevBmp As Long

    Dim hInvPrevBmp As Long

    Dim hDestPrevBmp As Long


    saveDC = CreateCompatibleDC(hDestDC)

    maskDC = CreateCompatibleDC(hDestDC)

    invDC = CreateCompatibleDC(hDestDC)

    resultDC = CreateCompatibleDC(hDestDC)


    按照规定的格式创建一幅与设备有关位图

    hMaskBmp = CreateBitmap(nWidth, nHeight, 1, 1, ByVal 0&)

    hInvBmp = CreateBitmap(nWidth, nHeight, 1, 1, ByVal 0&)


    创建一幅与设备有关位图

    hResultBmp = CreateCompatibleBitmap(hDestDC, nWidth, nHeight)

    hSaveBmp = CreateCompatibleBitmap(hDestDC, nWidth, nHeight)


    hSavePrevBmp = SelectObject(saveDC, hSaveBmp)

    hMaskPrevBmp = SelectObject(maskDC, hMaskBmp)

    hInvPrevBmp = SelectObject(invDC, hInvBmp)

    hDestPrevBmp = SelectObject(resultDC, hResultBmp)


    产生Mask图象

    OrigColor = SetBkColor(hSrcDC, TransColor)

    dl& = BitBlt(maskDC, 0, 0, nWidth, nHeight, hSrcDC, xSrc, ySrc, vbSrcCopy)

    TransColor = SetBkColor(hSrcDC, OrigColor)

    invDC的图象将与maskDC图象相反

    dl& = BitBlt(invDC, 0, 0, nWidth, nHeight, maskDC, 0, 0, vbNotSrcCopy)

    resultDC的图象将成为被写位置的图象

    dl& = BitBlt(resultDC, 0, 0, nWidth, nHeight, hDestDC, x, y, vbSrcCopy)

    resultDC中,需要新写的位置将变为黑色

    dl& = BitBlt(resultDC, 0, 0, nWidth, nHeight, maskDC, 0, 0, vbSrcAnd)

    dl& = BitBlt(saveDC, 0, 0, nWidth, nHeight, hSrcDC, xSrc, ySrc, vbSrcCopy)

    resultDC中不被写入的颜色成为黑色

    dl& = BitBlt(saveDC, 0, 0, nWidth, nHeight, invDC, 0, 0, vbSrcAnd)

    将两幅图合并起来

    dl& = BitBlt(resultDC, 0, 0, nWidth, nHeight, saveDC, 0, 0, vbSrcInvert)

    完工后输出

    dl& = BitBlt(hDestDC, x, y, nWidth, nHeight, resultDC, 0, 0, vbSrcCopy)


    SelectObject saveDC, hSavePrevBmp

    SelectObject resultDC, hDestPrevBmp

    SelectObject maskDC, hMaskPrevBmp

    SelectObject invDC, hInvPrevBmp


    DeleteObject hSaveBmp

    DeleteObject hMaskBmp

    DeleteObject hInvBmp

    DeleteObject hResultBmp


    DeleteDC saveDC

    DeleteDC maskDC

    DeleteDC invDC

    DeleteDC resultDC

    End Sub

    函数需要的API

    Private Declare Function BitBlt& Lib "gdi32" (ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long)

    Private Declare Function CreateCompatibleBitmap& Lib "gdi32" (ByVal hdc As Long, ByVal nWidth As Long, ByVal nHeight As Long)

    Private Declare Function CreateCompatibleDC& Lib "gdi32" (ByVal hdc As Long)

    Private Declare Function DeleteObject& Lib "gdi32" (ByVal hObject As Long)

    Private Declare Function SelectObject& Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long)

    Private Declare Function SetBkColor& Lib "gdi32" (ByVal hdc As Long, ByVal crColor As Long)

    Private Declare Function CreateBitmap& Lib "gdi32" ( ByVal nWidth As Long, ByVal nHeight As Long, ByVal nPlanes As Long, ByVal nBitCount As Long, lpBits As Any)

    四、关于ImageIo.dll

    在前一段时间,有很多朋友来信问我如何把BMP图象或图片框中的图象保存为JPG或GIF格式文件。我还没有回答过一次。因为我始终没有找到称心的DLL(动态连接库)。最近3个月,我一直学了VC,也算是勉强入门。我发现VC界的很多人都在使用一个叫ImageLoad.dll的动态连接库来完成这些工作。可惜,这个动态连接库到VB就不好使了。因为其函数使用了指针(而不是句柄)。然而,可但是,只要本老师在,你就可以用这个动态连接库了。本老师写了一个叫ImageIo.dll的动态连接库,用VC编写的。此连接库完全面对VB编写的,自然参数上完全考虑了VB用户。该连接库可以把你在VB中传送的句柄适当地改成指针并再调用ImageLoad.dll这个动态连接库,然后把处理结果以VB能够接受的方式返回给VB用户。另外一点是,ImageLoad.dll的函数比较低级的,用起来也不方面。为此,我在ImageIo.dll中写了一些代码,使得使用起来更加方便,更加可靠。

    然而,也出现了一个接着一个的让我伤心的事情。第一个是,函数不能读取部分GIF格式的文件(有些可以读出,但不是动画,只有一张静态画面);另一个是无法存成GIF格式,最后一点就古怪了,当像素位不等于24的时候位图文件不能存成JPG格式。该连接库可以操纵6中格式文件,有BMP,GIF,JPG,PCX,TGA,TIF。我对TGA和TIF没有怎么考虑过,因为在GIF中出现一些问题后感到这个ImageIo.dll不能使其功能齐全了,而且TGA和TIF也并不是常用文件,也就不大测试了。那么,是因为我还不懂VC吗?不,并不是我写错了代码,而是该动态连接库提供的演示程序同样不能解决我遇到的问题。据图象处理方面的书上说,一个JPG库代码,需要一个人用半年的时间才能写完。我总不能花半年去写一个这种库文件吧。其实我手里有一个,而我却懒得去用它。

    我考虑了,你能拿ImageIo.dll能做什么?想编写一个图象处理软件?如果你这样想的话,那么我建议你不要用它,如果你真的想编一个图象处理软件,最好还是自己从头编写一个完整的DLL文件。我想,算是出了著名的图象处理软件的公司都是如此。而且我建议你不要用VB来编写这种软件,不适合。我想,对绝大部分VB届的朋友来讲,对JPG的需求无疑是将一些图象文件压缩起来,以节约磁盘空间。比如,你需要编写一个人事档案管理系统,把人员照片扫描后保存成JGP文件。那么我想,你用这个ImageIo.dll文件,算是很不错的。我曾经在网络上下载过一个DLL文件,但其压缩质量实在差,而ImageLoad.dll却做得很出色。

    你在本教程的打包文件中可以找到一个Test.vbp文件,正是对这个ImageIo.dll写的。有关ImageIo.dll的技术文档,现给出如下,欢迎提一些宝贵意见∶

    ImageIo.dll

    本动态连接库是为了解决VB用户不能使用ImageLoad.dll动态连接库文件而编写的。它可以作为一个桥梁,使VB用户能够使用ImageLoad.dll。但它并不是对ImageLoad.dll的简单转化,而是添加了诸多代码,使其用起来更加简单、方便。它可以帮助用户完成 Bmp,Gif,Jpg 等多种图象文件的数据的读取和存盘工作。

    制作者∶Xing

    编程环境∶VC6.0

    日期∶1999,12,20

    常量∶

    1∶文件格式

    Const IMAGETYPE_NONE = 0 未知格式

    Const IMAGETYPE_BMP = 1 bmp 文件格式

    Const IMAGETYPE_GIF = 2 gif 文件格式

    Const IMAGETYPE_PCX = 3 pcx 文件格式

    Const IMAGETYPE_TGA = 4 tga 文件格式

    Const IMAGETYPE_JPG = 5 jpg 文件格式

    Const IMAGETYPE_TIF = 6 tif 文件格式

    Const IMAGETYPE_FIRSTTYPE = IMAGETYPE_BMP 第一个文件格式 (bmp) = 1

    Const IMAGETYPE_LASTTYPE = IMAGETYPE_TIF 最后一个文件格式 (tif) = 6

    函数∶

    GetImageType

    VB∶

    Private Declare Function GetImageType Lib "ImageIo.dll" (lpsFilename As String) As Long

    VC∶

    __API int GetImageType(LPTSTR & pszFilename);

    说 明∶

    获取图形文件类型

    返回值∶

    文件类型 0--6,0 表示出错或未知文件类型

    参数∶

    lpsFilename String 文件名

    ImageLoad

    VB∶

    Declare Function ImageLoad Lib "ImageIo.dll" (lpsFilename As String, ByVal hDC As Long, ByVal nX As Long, ByVal nY As Long) As Long

    VC∶

    _API HGLOBAL ImageLoad( LPTSTR & pszFilename,HDC hDC, int nX, int nY);

    说明∶

    装载一个图象文件,并必要时输出到指定的DC

    返回∶

    返回指向装有图象文件数据的一个缓冲区的指针。不管你打开任何文件,装载到缓冲区的数据都已经是转换为DIB格式,也就是说和BMP文件一样。在不在使用该缓冲区的时候,应该用API函数GlobalFree或者本动态连接库提供的KillImage函数释放掉。

    参数∶

    KillImage:

    VB∶

    Declare Function KillImage Lib "ImageIo.dll" (ByVal hDib As Long) As Long

    VC∶

    __API HGLOBAL KillImage(HGLOBAL hDib);

    说明∶

    本函数与API函数GlobalFree在其功能上完全一样,当然可以改用GlobalFree 。VC代码如下∶

    HGLOBAL CImageIoApp::KillImage(HGLOBAL hDib)

    { return(GlobalFree(hDib)); } 注∶如果成功返回0,否则返回hDib。

    ImageDraw: VB∶ Declare Function ImageDraw Lib "ImageIo.dll" (ByVal hDib As Long, ByVal hDC As Long, ByVal nX As Long, ByVal nY As Long) As Long VC∶ __API BOOL ImageDraw(HGLOBAL hDib,HDC hDC, int nX, int nY ); 说明∶ 将图象输出到指定DC。返回值∶ 成功返回1,否则返回0。参数∶ hDib 一个指向缓冲区的指针。该缓冲区中装有DIB位图数据。可以用LoadImage函数获取它。也可以自己创建。

    hDC 需要绘制图象的设备常场景句柄 nX,nY 设备场景中被绘制图象的左上角的坐标。

    GetInf VB: Declare Function GetInfo Lib "ImageIo.dll" (ByVal hDib As Long, BIH As BITMAPINFOHEADER) As Long VC: __API BOOL GetInfo(HGLOBAL hDib,BITMAPINFOHEADER & BIH); 说明∶ 本来是想用GetImageInfo作为函数名,但VC中用此名发生一些冲突,改用了GetInfo。此函数用于获取DIB文件中的BITMAPINFOHEADER结构信息。返回值∶ 成功返回1,失败返回0。

    参数∶ hDib 一个指向缓冲区的指针。该缓冲区中装有DIB位图数据。 BIH 一个准备装载信息的BITMAPINFOHEADER结构。传递前没有必要设置其biSize(结构长度)成员变量。

    __API BOOL ImageSave(LPTSTR & pszFilename,HGLOBAL hDib,int nType,int nQuality);

    只适用于BMP,JPG,TGA

    五、图表与指针

    图表是一幅小位图,图表文件的常用附加名是ICO。它的一项特殊能力就是不仅允许任何像素使用某种位图颜色,也允许它们使用屏幕或反转的屏幕颜色。Windows3.x中的图表通常是32×32像素大小,但在Windows95(98)和NT4.0中,对16×16和64×64像素图标的支持已非常普遍了。图标实际包含两幅独立位图。第一幅位图可能是单色的或彩色的,其中包含了要与显示屏幕图象合并到一起的一幅图象。这种合并是通过异或(XOR)操作实现的。我们将其称为XOR位图,其中包含了一个掩模。在显示屏幕图象与XOR位图合并到一起之前,这个掩模会先与屏幕图象进行AND操作,然后才是XOR位图和屏幕进行XOR。

    鼠标指针在其内部结构上和图表完全一致,只是它多出一个斎鹊銛,即该指针位图中代表指针位置的准确坐标点。该文件的附加名一般是CUR。能对图标进行操作的大部分函数也能对(鼠标)指针。

    图标被装入到内存之后将产生一个句柄。但需要注意的是,图表并不属于GDI对象,而是属于USER对象。所以有可能在应用程序间共享图表资源。

    以下给出了有关图标和指针的API函数

    函 数 说 明 CopyCursor 复制指针,使用CopyIcon CopyIcon 复制图表。 CreateCursor 创建指针 CreateIcon 创建图表 CreateIconIndirect 在一个ICONINFO结构的基础上创建一个图标 DestroyCursor 清除指针 DestroyIcon 清除图表 DrawIcon 向指定设备场景绘制图标 DrawIconEx 用附加的选项描绘图标 ExtractIcon 从一个可执行文件或DLL中载入图标 ExtractAssociatedIcon 载入指定文件内部或与它相关的一个图标 GetIconInfo 取得关于图标的信息 LoadCursor 从一个资源文件中载入指针,或者装载一个固有系统指针。 LoadCursorFromFile 通过读取标准指针文件(.cur)或动画指针(.ani)创建指针 LoadIcon 从一个资源文件中载入图表,或载入一个固有系统图标。

    六、颜色区间

    RGB 我们已经知道IBM兼容视频系统上的颜色由RGB三色体现。RGB三色中的每一种颜色都包含颜色中的红色、绿色和蓝色成分的值。这三部分的值组合在一起确定屏幕上所显示的颜色。RGB是常见颜色空间,红色、绿色和蓝色被认为是基本色,不能够被进一步的分解。而色彩系统可以分为两类,加法色彩系统和减法色彩系统。加法色彩系统,例如RGB系统中的颜色可通过将颜色添加到黑色中创建新颜色。添加的颜色越多,结果颜色也就越趋向于白色。足够的主要颜色可以创建出纯白色,而缺少所有主要颜色只能得出纯黑色。

    一个RGB是用四个字节来构成的。好象该字节中除了表示红、绿、蓝三个字节以外的剩余一个字节是多余的,其实不然。从一个API绘图函数引用一种颜色,都不只是用容纳红、绿、蓝颜色值的三个字节,而且还用第四个字节(最高字节)。这个字节包含一个标志值。顺序排列的最低字节包含红色值,其次两个字节分别是绿色和蓝色,而最后位字节则包含一个标志。此标志用于指示是否引用一个高频脉动色,或者一个调色板匹配色,或者一个明显的调色板索引。这个字节的值决定顺序排列的三个低位字节如何选择一种颜色。当你想指定一个建立这些对象的API函数中的笔或刷子的颜色时,可以把高位字节设置为如下表中的三个值之一。

    高字节值 结果 &H00 在对象绘出后,Windows高频脉动20个保留色,这叫RGB颜色引用。(普通情况) &H01 最低字节不是红色的值,而是指一个调色板项的数目或索引值,因此Windows使用在那个调色板项中的颜色,中间的两个字节(字节1和2,原绿色和蓝色字节)应保持值&H00,这叫调色板索引引用。 &H02 Windows定位与由红、绿、蓝决定的在三个顺序排列的低位字节指定的颜色最相匹配的调色板项。这叫作调色板RGB引用。因此,严密地讲,一个RGB值是通过以下方式构成的。

    Dim RGBColor As Long

    RGBColor=RedValue + (GreenValue * 256) + (BlueValue * 65536) + _

    (FlagValue * 16777216)

    这种技术的使用请参考本教程附带的程序Palette.vbp。

    CMY 在计算机领域还存在几种RGB颜色区间的变种。一个是叫做CMY的颜色区间。指的是蓝绿色、紫红色以及*。打印机和照相机通过加墨乳剂渲染颜色时,采用的是减法色彩系统。它由将颜色色素沉淀到白色纸上的大部分硬拷贝设备使用,例如激光打印机和喷墨打印机。当被现实时,这三种颜色都吸收与自身互补的浅颜色。蓝绿色吸收红色,紫红色吸收绿色,*吸收蓝色。例如,可增加*墨的量,图象中的蓝色量减少。RGB模式和 CMY模式之间的转换是非常简单的。为了计算蓝绿色,请从255中减去RGB中的红色值;对于紫红色,则从255中减去RGB中的绿色值;至于*嘛,喔!你已经猜测到了,即从255中减去RGB中的蓝色值。例如,RGB(240,12,135)的CMY值分别是15,243和120。然而,这种颜色区间大概并不是我们所感兴趣的,当然也有人感兴趣的。但下面给出的颜色区间更吸引我们,起码是我。

    HSV(颜色,浓度,亮度) HSV是众多色彩系统中的一个。如果你打开了通用对话框的颜色对话框家会发现,除了RGB调解器,还有一个类似的但其值很古怪的调解器。很多3D动画软件和图象处理软件中,这种HSV颜色空间也被广泛采用。它的基本原理是更改颜色属性值以创建新颜色,而不是使用颜色本身的混合色。色调般指颜色,例如红色、桔*、蓝色等等。饱和度(也被称为浓度)指示指示色调中的白色量∶全饱和和色调不包含白色,显示纯色。而部分包含色调根据所混合的白色的情况,显示的颜色浅。例如,50%的饱和度的红色色调显示为粉红色。值(也被称为亮度)是颜色自身的发光度,也就是它所发出的光的多少。高饱和度色调非常亮,而低饱和度色调非常暗。

    HSV与画家以及其他通过将白色、黑色和灰色增加到纯色素中以创建色彩、底纹、以及色调的艺术家们使用的色彩系统非常相似。色彩是纯色,是组合了白色的全饱和度颜色,而底纹是组合了黑色的全饱和度颜色。如果使用这两种颜色混合,那么HSV的饱和度是白色量,值是黑色量,色调是增加了黑白两色后的颜色。 更详细的情况不再这里说了,说起来也费劲,也不常用,以后做个程序放到站点里。

    七、关于调色板的基础知识

    关于调色板,很少书谈得深入,Dan的书上讲得也就那么一点点,少得实在可怜,连一个有关调色板的API都没有给出。为了弄懂它,我也付出了不少时间。当然,现在就给你讲解讲解。把各个角落中学来的知识总结在这里,想来也是满不错的主意感兴趣的朋友可以看,不看也无访。。

    用户显示于简报的图片很可能需要大量不同的颜色。可以想象∶艳绿色可滚动的爱尔兰小山的照片会比火星的图片需要更多的套不同的颜色、若用户想同时显示这两幅图象,会碰到一个很现实的问题。即使不限制逼真度,甚至用256种颜色,用户都不可能有足够的浓淡级别同时提供给两个图象。值得庆幸的是,若一次只有一幅图片显示,用户可以要求Windows交换颜色,使每个图象能激活自己的256种颜色选择--它自己的颜色调色板。

    那么,什么是调色板?它是一种Windows对象,但适当地,如果你把它想象成一种特定长度的结构数组,对理解会有帮助的。还记得RECT结构吧,如果声明 Dim MyRect(0 to 255) as RECT 以上,我们定义了一个RECT结构数组。当然,所有的笨蛋都知道调色板并不是RECT的。调色板结构比RECT要复杂一点。首先调色板是一种叫LOGPALETTE 的结构来构成的。如下∶

    Type LOGPALETTE palVersion As Integer palNumEntries As Integer palPalEntry ( 1 ) As PALETTEENTRY

    End Type

    palVersion 指Windows的版本号,数值300代表Windows3.0或3.1,不过请记住,在Windows9X中仍然使用该数值,即300。第二个参数值调色板登录项的数目。也就是说,它指的是第三个参数palPalEntry的元素个数。这个例子中显然是1喽!那么在以下的情况呢?

    Type LOGPALETTE palVersion As Integer palNumEntries As Integer palPalEntry ( 255 ) As PALETTEENTRY End Type 当然是256。但这也不一定,比255小也无妨。比如在4位模式中,应当是16,就算你声明为palPalEntry ( 255 ),创建调色板时,函数只使用其前面的0到15的数组元素。

    接下来,正如你已经看到的那样,第三个参数是一种结构数组。当然不是RECT喽,而是 PALETTEENTRY。它的内容如下∶

    Type PALETTEENTRY peRed As Byte peGreen As Byte peBlue As Byte peFlags As Byte End Type

    Red,Green,Blue,不用我说了吧?最后一个peFlags是什么呢? 它是包含用来描述调色板项类型的一个或多个颜色标志的值。如下表显示了个些标志∶

    标志 值 说 明 PC_EXPLICIT &H2 创建一个调色板条目,该调色板条目在系统调色板中指定一个索引而不是颜色。有显示系统调色板内容的程序使用。 PC_NOCOLLAPSE &H4 创建一个被影射到系统调色板中未用条目上的调色板条目,即使该颜色条目已经存在。用于当两个条目影射到同一颜色上时确保调色板颜色的唯一性。 PC_RESERVED &H1 创建一个某应用程序专有的调色板条目。当PC_RESERVED条目被添加到系统调色板时,它不被影射到其他逻辑调色板中的颜色上,即使这些颜色匹配。由执行调色板动画的程序使用。

    在8位显示器上,一个像素的颜色是通过查看一个色彩表中自己的8位像素值来决定的。一个调色板包含一套(数组)24位RGB色彩值(peRed,peGreen,peBlue)。一个调色板中的颜色数目最多256个(从0到255)。每个显示内存的像素选项包含一个从0到255的值,这个像素指定哪一个调色板项被用来为这个像素着色。要改变一个像素的颜色,用户有两个选择∶其一,可改变这个像素的颜色索引值;其二,改变调色板登录项上的颜色值(RGB值,24位)。在后一种情况下,这种改变使得所有引用这个调色板登录项的像素均同时引起颜色变化。

    然而你应当清楚,初始化一个LOGPALETTE变量,并不能说它是一个调色板。因为调色板是GDI对象的一员,就像画笔、画刷之类的GDI对象一样,必须通过特定的函数创建它。创建后,我们可以获得一个调色板句柄,那么这个句柄所代表的就是真正的调色板了。

    每个用户显示的图象能带自身的色彩调色板。另外,每个活动窗口能根据自身目的来操作调色板。但是,记住,应用于全屏幕,具有256种颜色的限制,这并不是对于每个窗口或应用程序而言。对于这些,还得用Windows调色板管理器来进行管理。Windows调色板管理器决定在给定时间哪个窗口拥有调色板控制。在前台活动的窗口常常拥有优先权。若此窗口不使用调色板,优先权则传给Z向(Z-order,听说过吧?它垂直于屏幕。因为我们常把屏幕坐标用X和Y来表示。而三维坐标是用X,Y,Z来表示的。这里指的Z并不是说真的三维坐标中的那个Z轴,而只是一个形象的比喻中被命名的,是指桌面窗口垂直方向)的下一个窗口。一旦带最高优先权的窗口认清自己的调色板作为前台调色板,其他窗口将被依次通知为背景调色板,这是由调色板管理器来通知。在一般情况下调色板管理器是自动工作的。

    刚才我说了一句“认清”这么一个单词。那么,什么叫“认清一个调色板”呢?每个用户显示的图象能带自己的调色板(几个调色板能被同时放在内存中)。存放在内存的调色板叫做逻辑调色板。在用户显示系统的调色板决定着哪一种颜色实际出现在屏幕上,这个调色板被叫做系统调色板或者硬件调色板(集中精力!系统调色板是硬件层的)只有一个硬件调色板并且调色板管理器保留一个它的复制调色板。当一个应用程序激活自己的颜色时,它必须在设备场景(device context)中选入逻辑调色板并认清它。这意味着,应用程序必须要求调色板管理器装载它的逻辑调色板给系统(硬件)调色板。所以,所谓“认清”实际上是一种“拜托”,或者就象日本人常说的“多多关照”∶请多多关照,使用我的调色板吧。因为只有这样,应用程序才能显示出符合自己口味的颜色。

    由于调色板的大小会发生改变,调色板管理器不会傻呵呵地硬着从逻辑调色板复制一个固定大小的256种颜色元素的块给硬件调色板。而是,这个调色板管理器只装载它在每个逻辑调色板中找到的那么多的颜色。系统调色板可以容纳多个逻辑调色板,只要颜色总数不超过256即可。Windows为它静态颜色(用于画按钮、边框、文本、图表等等,叫做系统保留色。可以用GetSystemColor函数获取的那几种)保留了20种调色板登录项。因此,只为我们剩下236个可变颜色位置。但这并不意味着一个调色板将只包括它需要的236种颜色来支持它的位图。把8位图象调色板转化为Windows本体调色板是明智的。Windows本体调色板包括20种保留色,特别是用户打算用调色板绘图时,否则不能用保留的颜色。本教程附带的Palette.vbp程序如果在16位或24位模式下运行,将只显示这20种颜色。若用户愿意,可以用系统静态颜色把可定义的颜色范围扩展到256。GDI就是为这个目的提供了特殊函数。但这将干扰其他活动程序的基本命令,因为它将动态改变活动程序的外观。因此除非你的程序独占整个屏幕(比如一些游戏就是这样的,不与其他窗口程序同时显示),否则这种做法是不应采取的。

    一个逻辑调色板中的颜色在系统调色板中一般不占据与在逻辑调色板中相同的位置。这是因为在认清一个调色板的时候,调色板管理器建立一个交叉引用表,也叫做调色板映像(palette mapping),正如它把一个逻辑调色板加载给系统调色板。这个表用于GDI绘图函数把像素从逻辑调色板翻译到系统调色板索引。很好理解的。可以把它想象为一种两列的表,左列中填写有逻辑调色板的颜色索引值,对应的右列中填写有系统调色板的索引值。就像英语词典里一个英语单词对应着一个中文单词。记住,计算机中所谓映像都是指这种情况。比如消息映像,它为各种消息值调用相应的消息处理函数。接着来。正如前面所解释的,一个像素的颜色是通过在颜色表中查看它的值来决定的。在设备相关位图和DIB(设备无关位图)的情况下,这是256种颜色位图文件的最通用窗体,组成位图像素数据的字节包含在文件的颜色表中的登录项的值。当GDI把图象从文件转移到屏幕时,也就是说,从一个设备相关位图转移到另一个依赖于设备的位图或DDB(设备相关位图)时,它用调色板映像来改变像素值,使它能引用系统调色板中当前正确的颜色。为一个逻辑调色板建立的调色板映像叫做前台映像。

    若从活动窗口没有获取所有调色板登录项,则剩下的位置被填入从非活动窗口拿来的颜色,直到或者所有的位置占据,或者没有其他的窗口要求识别自己的调色板位置。若前台窗口需要所有的236个自由颜色位置,则所有的非活动窗口必须受前台活动调色板的支配。调色板管理器还能自动地执行这个服务,这是通过把非活动窗口中的颜色映像给当前被辨认出的调色板中的最匹配的颜色来实现的。这就是所谓的后台影像。这有时会产生有趣的但对大多数人来讲是糟糕的结果。比如,最匹配色=很不匹配,其他色=更不匹配的情况下。另外,你需要记住的一点是,当焦点每次从一个基于调色板的应用程序更换到另一个基于调色板的应用程序时,整个辨认过程重复执行一次。

    DIB中的像素包含与位图一起的逻辑调色板中的颜色索引,通常一个颜色表存在于DIB文件中;DDB中的像素包含系统调色板中的颜色索引。当用户是根据索引值来访问一种颜色时,用户是在逻辑调色板中根据它的位置来访问颜色的,而不是在系统调色板中的它的位置。也就是说,你只能用逻辑调色板的索引值。因此,假如逻辑调色板中不存在20中系统颜色,那么我们就无法访问它,这并不是说它不存在。因此,如果你需要系统颜色,就应当把这些颜色值装载的逻辑调色板。

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/9/6 9:03:00
     
     GoogleAdSense射手座1984-11-30
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/5/10 11:50:53

    本主题贴数7,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    453.125ms