一般说来,应用程序的窗体都是规则的,即是矩形窗体。窗体一旦存在就含有一个矩形区域。但这样的窗体千篇一律。其实我们可以制作一些非规则的窗体,比如说圆形(椭圆形)窗体,扇形窗体,多边形窗体(平行四边形窗体,五角形窗口),以及用任何图象来作为应用程序的窗体。这样,我们的程序就不会显得那么呆板,而就很有活力了。下面,我们就用VB来实现这几种不规则窗体。值得注意的是,一般不规则窗体没有标题栏。我们知道,窗体的拖动就是拖动窗体的标题栏,但不规则窗体中,由于没有标题栏,窗体的拖动就没有规则窗体拖动那么容易。所以,在不规则窗体,拖动窗体成了我们应该解决的问题。在不规则窗体中,一般窗体的大小都是固定的,所以窗体的大小我们并不需要控制。无标题窗口的菜单一般是弹出式菜单,也是我们考虑的对象。在创建不规则窗体,一般步骤是用绘图函数绘出不规则的图形,然后将窗体的指向该区域,这样,窗体看起来就是我们用绘图函数画出的不规则图形了。在处理无标题窗体拖动时,也即处理消息循环,我们可以截取消息并根据消息来执行相应的功能。如双击窗体,单击窗体,拖动窗体等。我们应该考虑怎样在VB中接收、处理窗体的消息(双击、单击、左键、右键)。C++、Delphi等语言对消息循环的处理较简单,但在VB中处理消息循环时必须应用Win32的SetWindowLong、CallWindowProc这两个API函数。SetWindowLong 函数利用GWL_WNDPROC 索引来创建窗口类的子类(窗口类是用来创建窗口的),它使用AddressOf关键字和回调函数(WindowProc)来截取消息并根据消息来执行相应的功能,如窗口的最大化、最小化、隐藏、退出等。CallWindowProc函数调用原窗口类缺省的指针,程序最后退出时可通过SetWindowLong来关闭子类,重新使原来的Windows过程成为回调函数(具体说明见后)。现在,介绍几个必须用到的API函数。 1、SetWindowRgn:将窗体指向指定的区域用法: rtn=SetWindowRgn(ByVal hWnd As Long, ByVal hRgn As Long, ByVal bRedraw As Boolean),参数表中:hWnd,将设置其区域的窗口;hRgn,将设置的区域的句柄,一旦设置了该区域,就不能使用或修改该区域句柄,也不要删除它;bRedraw,若为TRUE,则立即重画窗口。 2、SetWindowPos:为窗口指定一个新位置和状态。它也可改变窗口在内部窗口列表中的位置,我们用它来使窗口显示在最上面。用法:rtn=SetWindowPos(ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long),参数表中: hwnd,欲定位的窗口;hWndInsertAfter,窗口句柄;x,窗口新的x坐标;y,窗口新的y坐标;cx,指定新的窗口宽度;cy,指定新的窗口高度;wFlags,包含了旗标的一个整数,可以为:SWP_DRAWFRAME(围绕窗口画一个框)、SWP_HIDEWINDOW(隐藏窗口)、SWP_NOACTIVATE(不激活窗口)、SWP_NOMOVE(保持当前位置,x和y设定将被忽略)、 SWP_NOREDRAW(窗口不自动重画)、SWP_NOSIZE(保持当前大小,cx和cy会被忽略)、SWP_NOZORDER(保持窗口在列表的当前位置,hWndInsertAfter将被忽略)、SWP_SHOWWINDOW(显示窗口)、SWP_FRAMECHANGED(强迫一条WM_NCCALCSIZE消息进入窗口,即使窗口的大小没有改变)。 3、DefWindowProc:用缺省的WINDOWS程序为任何windows消息提供缺省处理,这样应用程序的处理方法就被屏蔽。这个函数主要是用来保证所有的消息都能有相应的程序来响应。用法:rtn=DefWindowProc(ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long),参数表中:hwnd,响应消息的窗体句柄,一般指本窗体;wMsg,消息类型,如鼠标移动等;wParam、lParam,这两个参数根据wMsg的不同,表示的意义也不一样,它们是wMsg的附加消息,如,若wMsg是鼠标移动的消息的话,那么这两个参数的意义就是鼠标移动时的位置(x,y)。 4、CallWindowProc:把消息给指定的窗体程序处理。用法:rtn=CallWindowProc(ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long),参数表中:lpPrevWndFunc,windows消息处理程序的地址;hWnd,指定的窗体句柄;wMsg,消息类型;wParam、lParam,wMsg的附加消息。 5、SetWindowLong:该函数是一个针对窗体的功能强大的函数。使用它在窗体结构中为指定的窗口设置信息。如窗体样式,窗体消息处理程序。用法:PROROC=SetWindowLong(ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) ,参数表中:hwnd,要设置信息窗体的句柄;nIndex,窗体要设置的信息类型,可以为以下值:GWL_EXSTYLE(设置窗体扩展样式)、GWL_STYLE(设置窗体样式)、GWL_WNDPROC(设置窗体处理程序的地址,这意味着这里可能改变窗体消息处理程序)、GWL_HINSTANCE(设置应用程序实例句柄)、GWL_ID(设置窗体的标识)、GWL_USERDATA(设置与窗体相关联的一个32位值),当参数hwnd是一个对话框时,也可以用到其他参数,这里就不多介绍了;dwNewLong,新的信息的值。 6、DeleteObject:用这个函数删除GDI对象,比如画笔、刷子、字体、位图、区域以及调色板等等。对象使用的所有系统资源都会被释放。用法:rtn=DeleteObject(ByVal hObject As Long),参数表中:hObject,一个GDI对象的句柄。 7、CreateEllipticRgn:创建一个椭圆,该椭圆与指定坐标点确定的矩形内切。 用法:rgn=CreateEllipticRgn (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long),参数表中:X1、Y1、X2、Y2为一个矩形的坐标。注意:该API函数不用时一定要用DeleteObject函数删除该区域,释放系统资源。 8、CreatePolygonRgn: 创建一个由一系列点围成的区域,windows在需要时自动将最后点与第一点相连以封闭多边形。用法:rtn=CreatePolygonRgn(lpPoint As POINTAPI, ByVal nCount As Long, ByVal nPolyFillMode As Long),参数表中:lpPoint,nCount个POINTAPI结构中的第一个POINTAPI结构;nCount,多边形的点数;nPolyFillMode,描述多边形填充模式。可为ALTERNATE(交替填充) 或 WINDING(根据绘图方向填充)常数。注意:该API函数不用时一定要用DeleteObject函数删除该区域,释放系统资源。 9、CombineRgn:将两个区域组合为一个新区域。用法:rtn=CombineRgn(ByVal hDestRgn As Long, ByVal hSrcRgn1 As Long, ByVal hSrcRgn2 As Long, ByVal nCombineMode As Long),参数表中:hDestRgn,包含组合结果的区域句柄;hSrcRgn1,源区域1句柄;hSrcRgn2,源区域2句柄;nCombineMode,组合两区域的方法。可设为下述常数,RGN_AND(hDestRgn被设置为两个源区域的交集)、RGN_COPY(hDestRgn被设置为hSrcRgn1的拷贝)、RGN_DIFF(hDestRgn被设置为hSrcRgn1中与hSrcRgn2不相交的部分)、RGN_OR(hDestRgn被设置为两个区域的并集)、RGN_XOR(hDestRgn被设置为除两个源区域OR之外的部分)。 10、GetWindowRect:获得整个窗口的范围矩形,窗口的边框、标题栏、滚动条及菜单等都在这个矩形内。用法:rtn=GetWindowRect(ByVal hwnd As Long, lpRect As RECT),参数表中:hwnd,想获得范围矩形的那个窗口的句柄;lpRect,屏幕坐标中随同窗口装载的矩形。 11、CreateRectRgn:创建一个矩形区域用法:rtn=CreateRectRgn(ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long),参数表中:X1,Y1,矩形左上角X,Y坐标; X2,Y2,矩形右下角X,Y坐标。 注意:该API函数不用时一定要用DeleteObject函数删除该区域,释放系统资源。 12、GetPixel:在指定的设备场景中取得一个像素的RGB值用法:rtn=GetPixel(ByVal hdc As Long, ByVal x As Long, ByVal y As Long),参数表中:hdc,一个设备场景的句柄;x,y,逻辑坐标中要检查的点的坐标 13、POINTAPI结构:由x,y组成,分别表示横向与纵向的坐标值。 14、 RECT结构:类型定义了矩形中左上角坐标与右下角坐标。在运用中,SetWindowRgn函数与绘图函数一起使用,就可以完成不规则窗体的绘制。而SetWindowLong、CallWindowProc、DefWindowProc三个函数联合使用可以完成无标题窗体的拖动。关于无标题窗体的菜单,我试了一下,如果一个窗体有菜单,那么它就不能作为无标题窗体存在,运行的时候就会把标题显示出来。在这里我们可以这样做:建立两个窗体,一个为主窗体frmmain,另外一个为菜单窗体frmmenu,在窗体frmmenu中加入一个菜单popmenu设置为不可见,再加入一些子菜单,如“退出”,名称为:exit;“图像窗体”,名称为:“PictureForm”;“多边形窗体”,名称为“PolygonForm”;“圆形窗口”,名称为“RoundForm”;“扇形窗体”,名称为“SectorForm”等。然后我们在frmmenu窗体中加入以下代码:
Option Explicit Private Sub exit_Click()
Unload Frmmain
End Sub
public Sub PictureForm_Click()
Frmmain.CreatePictureForm
PictureForm.Checked = True
PolygonForm.Checked = False
RoundForm.Checked = False
SectorForm.Checked = False
End Sub
public Sub PolygonForm_Click() Frmmain.CreatePolygonForm PictureForm.Checked = False PolygonForm.Checked =
True RoundForm.Checked = False
SectorForm.Checked = False
End Sub
public Sub RoundForm_Click()
Frmmain.CreateRoundForm
PictureForm.Checked = False
PolygonForm.Checked = False
RoundForm.Checked = True
SectorForm.Checked = False
End Sub
public Sub SectorForm_Click()
Frmmain.CreateSectorForm
PictureForm.Checked = False
PolygonForm.Checked = False
RoundForm.Checked = False
SectorForm.Checked = True
End Sub
这样,我们一点击菜单“退出”,就可以退出该程序。然后我们在frmmain中调用该菜单就可以了。PopupMenu Frmmenu.popmenu语句就可以弹出该菜单(我不知道还有没有更好的方法,若有,可以写email与我交流)。这样,菜单的问题就解决了。 这里我们建立这样几个不规则的窗体,每个窗体建立时使用的方法都不一样,但无标题窗体拖动的方法都是一样的。 1、椭圆形窗体:建立椭圆形窗体要先绘制一个椭圆形的区域,API函数CreateEllipticRgn可以做到这一点。 2、多边形窗体:多边形窗体的绘制用API函数CreatePolygonRgn 3、组合图形-扇形窗体:建立象扇形,心形等不规则的窗体,怎么办呢?是不是也有API函数绘制扇形或者心形呢?其实在WINDOWS api函数中没有相应的绘制图形函数,但实际上我们仔细观察,扇形,心形等图形都是组合的图形的,比如扇形就是由圆与平行四边形组合出来的,心形是由圆组合而来的。在建立象扇形这样不规则的窗体时,我们可以分别建立圆形窗体与平行四边形窗体,然后将他们组合起来就可以了。 4、任何图象窗体:图象都是由像素组成的。我们可以看图象看作是由一个个只包含一个像素的小区域组成,每一个小区域都可以认为是一个矩形。这样,整个图象是由一个个小矩形组成。在这些小矩形中,有的矩形对于图象窗体是不必要的,如在明星头像是一个不规则的似圆形,除了这个似圆形内的小矩形需要外,其他的矩形我们都可以不要。这样,可以用图形的组合来实现图象窗体的建立。在这里,那些小矩形是需要的,那些小矩形是不需要的呢?我们可以这样做,用图像处理软件,如Photoshop做一个透明图像,然后我们从一整张明星图像中用套索工具把该明星的头像复制下来,当然,这个头像就是一个极不规则的图形。把这个头像粘贴到透明图像中,然后再将该透明图像存储为jpg文件,图像就算制作完成了,下面我们在程序中要调用这张透明图像。好了,有了这些准备工作,我们开始编写。增加一模块,添加如下代码:
Option Explicit
Public Declare Function CombineRgn Lib "gdi32" (ByVal hDestRgn As Long, ByVal hSrcRgn1 As Long, ByVal hSrcRgn2 As Long, ByVal nCombineMode As Long) As Long Public Declare Function CreateEllipticRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
Public Declare Function CreatePolygonRgn Lib "gdi32" (lpPoint As POINTAPI, ByVal nCount As Long, ByVal nPolyFillMode As Long) As Long Public Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
Public Declare Function CreateRectRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long Public Declare Function GetPixel Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long) As Long
Public Declare Function SetWindowRgn Lib "user32" (ByVal hwnd As Long, ByVal hRgn As Long, ByVal bRedraw As Boolean) As Long Public Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
Public Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long
Public Declare Function DefWindowProc Lib "user32" Alias "DefWindowProcA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Public Type POINTAPI
x As Long
y As Long
End Type
Public Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Public Const WINDING = 2
Rem 无标题拖动参数
Public Const WM_NCHITTEST = &H84; Public Const HTCAPTION = 2
Public Const HTCLIENT = 1
Public Const GWL_WNDPROC = (-4)
Public Const WM_NCRBUTTONUP = &HA5;
Public Const SWP_NOMOVE = &H2;
Public Const SWP_NOSIZE = &H1;
Public Const FLAGS = SWP_NOMOVE Or SWP_NOSIZE
Public Const HWND_TOPMOST = -1
Public Const HWND_NOTOPMOST = -2
Public Const RGN_XOR = 3
Public PROROC As Long
Rem 实现无标题拖动的处理函数
Function WindowProc(ByVal hwnd As Long, ByVal msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Dim rv As Long
Rem 如果消息为鼠标右键弹起,则弹出菜单
If msg = WM_NCRBUTTONUP Then
Frmmain.PopupMenu
Frmmenu.popmenu
End If
Rem 如果消息为鼠标消息,就交给缺省消息处理程序处理
If msg = WM_NCHITTEST Then
rv = DefWindowProc(hwnd, msg, wParam, lParam)
If rv = HTCLIENT Then
WindowProc = HTCAPTION
Else
WindowProc = rv
End If
Else
Rem 将其他的消息传给默认的窗口函数进行处理
WindowProc = CallWindowProc(PROROC, hwnd, msg, wParam, lParam)
End If
End Function
新建一窗体,将BorderStyle属性设为:0-none,即无标题窗体。再上面添加一PictureBox控件,将其Picture属性设置为一刚才制作好的图像。在其代码窗口中添加以下代码: Option Explicit Private Sub Form_Load() Rem 以下这条语句将消息处理程序设置为自定义的程序中 PROROC = SetWindowLong(hwnd, GWL_WNDPROC, AddressOf WindowProc) Rem 设置该窗体在最前面 SetWindowPos Frmmain.hwnd, HWND_TOPMOST, 0, 0, 0, 0, FLAGS Frmmenu.PictureForm_Click End Sub Private Sub Form_Unload(Cancel As Integer) Dim rv As Long Rem 退出程序时的处理工作,恢复标题栏拖动 rv = SetWindowLong(hwnd, GWL_WNDPROC, PROROC) Unload Frmmenu End Sub Rem 该函数创建一个扇形窗体 Function CreateSectorForm() Dim wid As Single Dim hgt As Single Dim rgn1 As Long Dim rgn2 As Long Dim Pos(1 To 4) As POINTAPI Const RGN_DIFF = 4 wid = 200 hgt = 200 Pos(1).x = 100 Pos(1).y = 100 Pos(2).x = 200 Pos(2).y = 100 Pos(3).x = 200 Pos(3).y = 200 Pos(4).x = 52 Pos(4).y = 200 Frmmain.Width = ScaleX(wid, vbPixels, vbTwips) Frmmain.Height = ScaleY(hgt, vbPixels, vbTwips) Rem 以下两条语句分别绘制一个圆形区域和一个梯形区域 rgn1 = CreateEllipticRgn(0, 0, wid, hgt) rgn2 = CreatePolygonRgn(Pos(1), 4, WINDING) Rem 合并圆形区域和梯形区域为扇形区域 CombineRgn rgn1, rgn1, rgn2, RGN_DIFF Frmmain.Picture = LoadPicture() Rem 设置窗体为扇形区域 SetWindowRgn Frmmain.hwnd, rgn1, True DeleteObject rgn1 DeleteObject rgn2 End Function Rem 该函数创建一个圆形窗体 Function CreateRoundForm() Dim wid As Single Dim hgt As Single Dim rgn As Long Rem 以下两句是得到窗体宽、高的像素点 wid = ScaleX(Width, vbTwips, vbPixels) hgt = ScaleY(Height, vbTwips, vbPixels) Frmmain.Picture = LoadPicture() Rem 以下三句将窗体设置成椭圆形窗体 rgn = CreateEllipticRgn(0, 0, wid, hgt) SetWindowRgn Frmmain.hwnd, rgn, True DeleteObject rgn End Function Rem 该函数创建一个多边形窗体 Function CreatePolygonForm() Dim Pos(1 To 4) As POINTAPI Dim rgn As Long Rem 定义多边形的坐标 Pos(1).x = 50 Pos(1).y = 200 Pos(2).x = 150 Pos(2).y = 100 Pos(3).x = 300 Pos(3).y = 100 Pos(4).x = 200 Pos(4).y = 200 Frmmain.Picture = LoadPicture() Rem 以下三条语句创建多边形窗体 rgn = CreatePolygonRgn(Pos(1), 4, WINDING) SetWindowRgn Frmmain.hwnd, rgn, True DeleteObject rgn End Function Rem 该函数创建一个图像窗体 Function CreatePictureForm() On Error Resume Next Dim hRgn As Long, hRect As RECT, hTempRgn As Long, tColour As Long, OldScaleMode As Integer, AbsoluteX As Long, AbsoluteY As Long Dim Color As Long, Hrect1 As RECT Dim xx As Long, yy As Long Dim rtn As Long Frmmain.Picture = Frmmain.Picture1 Frmmain.Width = Frmmain.Picture1.Width Frmmain.Height = Frmmain.Picture1.Height OldScaleMode = Frmmain.ScaleMode Frmmain.AutoRedraw = True Frmmain.ScaleMode = 3 Rem 白色,透明图象的背景为白色, Color = vbWhite Rem 得到窗体的整个区域 rtn = GetWindowRect(Frmmain.hwnd, hRect) Rem 创建一个矩形区域,大小为整个窗体大小 hRgn = CreateRectRgn(0, 0, hRect.Right, hRect.Bottom) For AbsoluteX = 0 To Frmmain.ScaleWidth For AbsoluteY = 0 To Frmmain.ScaleHeight Rem 得到该点像素的颜色 tColour = GetPixel(Frmmain.hdc, AbsoluteX, AbsoluteY) Rem tColour的参数可为任何RGB颜色(属于color会被去掉) If tColour = Color Then Rem 创建一个小矩形区域,此小矩形区域像素的颜色为白色,应该去掉 hTempRgn = CreateRectRgn(AbsoluteX, AbsoluteY, AbsoluteX + 1, AbsoluteY + 1) Rem 通过组合函数,用异或关系将该点去掉 rtn = CombineRgn(hRgn, hRgn, hTempRgn, RGN_XOR) Rem 删除临时区域 rtn = DeleteObject(hTempRgn) End If Next AbsoluteY Next AbsoluteX Rem 设置窗体为该图像窗体 rtn = SetWindowRgn(Frmmain.hwnd, hRgn, True) DeleteObject hRgn Frmmain.ScaleMode = OldScaleMode If Err Then MsgBox Error, 16, Err End Function 加入窗体FrmMenu,然后编译运行该程序,过一会儿,图片窗体就出来了哈,不过,若是透明图像过大的话,启动速度会变慢的。你也可以改进该程序的算法,使之运行速度更快,到时候别忘记告诉我哟^_^。点击右键,我们可以选择显示各种不规则的窗体。到此为止,你已经掌握了建立不规则窗体的方法了,通过这些方法,再发挥你的想象,任何漂亮的窗体我们都可以做出来的。最后特别要提到的是:上面所有的程序在VB中调试均不能通过,你在VB的环境下运行时,系统会告诉你“执行了非法操作”,这是因为使用了循环消息函数造成的。所以调试很困难,唯一的方法是编译成exe文件再执行该文件才不会出错的。还有,在VB中使用函数指针将使程序变得脆弱,在运用与其相关的函数及关键字时要多加小心。该文中所有的程序在中文Win98中文+VB6中通过。
