第六课∶绘图函数 一、位操作 前几天,在很远很远又是那么远的地方,有位网友来信问一些有关位操作的内容。我一开始不大注意这个环节,认为估计大家都能知道。可现在来仔细一想,也并非如此。《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
是不是所谓水落石出 ? 二、创建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绘图对象。现在,您大概了解有以上这些函数和,理解给出的几个例子就可以了。稍后,我们结合实际例子,更深入地探讨这些函数的用法。 |