ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
楼主: new4w

[分享] Win32 API讲座

  [复制链接]

TA的精华主题

TA的得分主题

发表于 2014-7-29 16:15 | 显示全部楼层
感谢楼主分享            

TA的精华主题

TA的得分主题

发表于 2014-7-29 16:16 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
不明觉厉,顶一下!

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-7-29 16:34 | 显示全部楼层
Win32 API讲座 第六课∶绘图函数

第六课∶绘图函数

一、位操作

    前几天,在很远很远又是那么远的地方,有位网友来信问一些有关位操作的内容。我一开始不大注意这个环节,认为估计大家都能知道。可现在来仔细一想,也并非如此。《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
敽蛿1?0表示正,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 n<>0 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
   是不是所谓水落石出 ?

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-7-29 16:35 | 显示全部楼层
二、创建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绘图对象。现在,您大概了解有以上这些函数和,理解给出的几个例子就可以了。稍后,我们结合实际例子,更深入地探讨这些函数的用法。

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-7-29 16:37 | 显示全部楼层

三、拿起和放下画笔(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属性等。

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-7-29 16:38 | 显示全部楼层
四、绘图属性与绘图函数

      到目前为止,我们已经学会了绘图所需要的一切准备工作。上一节中最后给出的代码就说明这一点。代码中,现在只缺少具体的绘图代码。本节就讨论关于如何绘图的问题。
      在接触绘图函数之前,首先需要了解绘图属性。设备场景定义了一系列绘图属性。这些绘图属性定义了刷子和画笔与窗口或设备表面当前内容相互作用的方法。比如,当前画笔的位置、当前背景颜色、圆弧和矩形的绘制方向、光栅操作模式等等。虽然后面给出了很多属性控制函数,但用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。

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-7-29 16:39 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
五、路  径

     应当说,路径是较为高级的话题,尽管它不是难于理解的。我学到的有关路径的知识,来自于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函数来绘制。

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

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-7-29 16:40 | 显示全部楼层
Win32 API讲座 第七课∶位图

第七课∶位图

一、概述

      在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位产生16'777'216种颜色(补充:我已经亲眼看到了32位显卡)。如下表所示∶

TA的精华主题

TA的得分主题

发表于 2014-7-29 16:41 | 显示全部楼层

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-7-29 16:41 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
Windows支持的彩色

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

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

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

      首先让我们查看一下文件在磁盘中的大体状况∶
D:\vbplay\vbplay25\>dir

Volume in drive D is PRO        
Volume Serial Number is 268F-AB4B
Directory of D:\vbplay\vbplay25

.              <DIR>        03-27-99  20:47 .
..             <DIR>        03-27-99  20:47 ..
PLAY25       VBW           111  09-20-99  23:43 play25.vbw
MSSCCPRJ  SCC            193  04-07-99  23:52 MSSCCPRJ.SCC
BITMAP       CLS            12,711  06-16-99  21:27 Bitmap.cls
MYMEMORY CLS            3,419  04-08-99   2:07 MyMemory.cls            PLAY25       VBP            636  06-16-99  21:42 play25.vbp
HUA            BMP           96,446  04-04-99  12:06 hua.bmp
PL25_1       FRM           3,865  06-16-99  21:25 pl25_1.frm
         7 file(s)        117,381 bytes
         2 dir(s)     569,049,088 bytes free


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

D:\vbplay\vbplay25\>debug hua.bmp
-d 100 1000
0EAE:0100  42 4D BE 78 01 00 00 00-00 00 36 04 00 00 28 00   BM.x......6...(.
0EAE:0110  00 00 69 01 00 00 06 01-00 00 01 00 08 00 00 00   ..i.............
0EAE:0120  00 00 88 74 01 00 C4 0E-00 00 C4 0E 00 00 00 00   ...t............
0EAE:0130  00 00 00 00 00 00 00 00-00 00 80 80 80 00 00 00   ................
0EAE:0140  80 00 00 80 80 00 00 80-00 00 80 80 00 00 80 00   ................

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

0EAE:0530  ED 00 56 82 BD 00 B3 66-B3 B3 6C B3 BD 8B C9 C9   ..V....f..l.....
0EAE:0540  BD 50 21 60 6C 6C 44 44-B3 44 6C 86 44 66 44 D9   .P!`llDD.Dl.DfD.
0EAE:0550  44 B3 6C C9 87 7B B9 7B-44 BD 87 60 87 C9 6C AB   D.l..{.{D..`..l.
0EAE:0560  60 D6 6C AB AB 2A 88 55-88 7B 7B 7B 7B 3D 3D 3D   `.l..*.U.{{{{===
0EAE:0570  C5 3D C5 24 7B 7B 7B 7B-B4 B9 7B B9 B5 C6 3D 7B   .=.${{{{..{...={
(以下全部省略)
0EAE:1000  B3                                                .

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

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-12-25 13:36 , Processed in 0.048109 second(s), 7 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表