来源:
互联网
作者:
若水
2008-03-17/17:34
WINDOWS应用程序窗口一般包括两种:普通窗口和常居顶层的无标题条高级窗口。前者是由WINDOWS内部功能定制的,它具有WINDOWS应用程序窗口的所有普通特性:具有标题条、窗口边框、最大化按钮、最小化按钮和系统默认的快捷键及鼠标支持功能等,利用鼠标左键拖动该种窗口的标题条可以在屏幕上任意移动窗口,当鼠标光标停在窗口边框上时可以改变窗口大小;后者是一种定制的高级窗口,它不具有普通窗口的任何属性,整个窗口的控制必须由编程者来一一确定,使用这种窗口的典型实例有WINDOWS中的IME输入法应用程序、UCWIN4.0平台、各种浮动工具箱、OFFICE中的桌面工具栏和第三方开发的汉字输入平台等。
WINDOWS 这种无标题条常居顶层高级窗口的一个显著特点是,不需改变窗口大小但必须具有窗口的客户区域拖动功能。由于普通窗口的拖动功能是由系统来完成的,编制普通的应用程序根据无须考虑客户区域拖动问题,因此一般编程人员很难遇到这个问题,更谈不上如何实现这一功能了。开发者往往希望自己开发出来的软件具有经典软件中的窗口客户区域拖动功能,笔者曾经利用模仿系统鼠标点击标题条拖动窗口和WINDOWS系统内部提供的API发送函数发送内部拖动命令来实现无标题常居顶层高级窗口的客户拖动功能,结果都不理想。后来只好在窗口函数中通过直接处理WM_LBUTTONDOWN、WM_MOUSEMOVE和WM_LBUTTONUP消息,自行控制窗口拖动的客户命令区、拖动开始、窗口移动、拖动虚框绘制、虚框移动和拖动结束等过程,来实现高级顶层窗口的客户区域拖动方案。下面就自己实践经验详细介绍实现该方案的具体方法和主要技巧。
一、WINDOWS检测客户拖动命令及鼠标光标动态提示的实现方法
WINDOWS 无标题条常居顶层高级窗口的客户区域一般分为两种:特定客户命令区域和非特定客户命令区域。特定客户命令区域是指利用"RECT"定义的特定子矩形区域,窗口函数对发生在该区域内的鼠标命令进行检测并处理;非特定客户命令区域是指没有明确定义的窗口客户区域部分,即所有特定客户命令区域之外的部分,窗口函数根据实际需要来确定是否对该区域内发生的鼠标命令进行处理。实现常居顶层高级窗口拖动功能的首要问题,是如何检测和处理特定客户命令区域和非特定客户命令区域内的鼠标命令,以及如何利用鼠标光标来动态提示用户此时可以进行窗口的拖动操作。
1、在特定客户区域检测鼠标命令的方法
当窗口中设置了实现拖动功能的图标命令按钮时,就必须在资源文件中定义命令按钮的特定客户区域,该区域一般也就是显示命令按钮中图标的矩形区域,这个区域的定义方法为"RECT DragRT",其中DragRT为定义的检测鼠标命令矩形区域,它用DragRT.LEFT、DragRT.TOP、DragRT.RIGHT和DragRT.BOTTOM四个参数来描述矩形区域相对于窗口客户区域左上角的相对坐标值,这四个参数必须事先定义具体的数值,也可以利用"SETRECT"函数直接填充。
窗口函数在处理鼠标消息WM_LBUTTONDOWN时,在接收系统传递的鼠标位置参数lParam后,通过MAKEPOINT( )函数将其转换为窗口坐标值,利用判断某坐标点是否位于特定矩形区域内的函数PtInRect(),就可以判断鼠标指针是否点击在拖动命令按钮之内,从而完成窗口拖动功能的启动任务。其描述性功能代码示例如下:
case WM_LBUTTONDOWN://鼠标光标点击处理
POINT pt;//鼠标在屏幕上位置指针,包括pt.X和pt.Y两个参数,
//该指针值利用MAKEPOINT通过lParam参数转换而来
pt=MAKEPOINT(lParam); //获取鼠标当前屏幕位置指针
if(PtInRect(&DragRT,pt)){//判断鼠标是否点击在拖动按钮内
//实现鼠标拖动窗口方案的启动功能
} else {
//进行其它特定或非特定命令客户区域判断处理
}
break;
2、在非特定客户区域检测鼠标命令的方法
当窗口应用程序中采取了非特定客户区域拖动方法时,必须在资源文件中事先确定各个特定客户区域的矩形坐标,这时非特定客户区域是不规则的区域,它需要根据实际的应用程序窗口及各个命令按钮矩形区域来确定,也就是各个命令按钮相对于窗口矩形区域的“非”子集。窗口函数在处理鼠标消息WM_LBUTTONDOWN时,首先利用函数PtInRect()判断当前鼠标指针是否点击在各个命令按钮矩形区域内,如果未点击在任何命令按钮区域内,则可确定鼠标点击在非特定客户区域内,从而实现窗口拖动功能的启动。其描述性功能代码示例如下:
case WM_LBUTTONDOWN: //鼠标光标点击处理
POINT pt; //定义鼠标在屏幕上的位置指针
pt=MAKEPOINT(lParam); //取得鼠标光标当前位置指针
for(I=0;I
if(PtInRect(&DragRT[I],pt)){//DragRT[I]为按钮矩形数组
break; //鼠标点击在其它按钮上中断
}
}
if(I
//鼠标点击在其它特定客户区域内则处理其它按钮功能
}else{
//鼠标点击在非客户区域内则完成窗口拖动方案的启动
}
break;
3、窗口拖动功能的鼠标光标动态提示方法
在无标题条常居顶层高级窗口应用程序中,既可以采用将特定客户区域作为拖动命令按钮的方法,也可以采取在非特定客户区域检测窗口拖动命令的方法,或者两种方法兼顾使用。在使用第一种方法时,可以在命令按钮中用特定的图标或文字来提示用户该命令按钮的功能,而后一种方法由于矩形区域无法确定不可能用图标或文字来提示,或根本无法显示图标和文字(如非特定客户区域为窗口边界区域等),用户根本无法知道非特定客户区域具有拖动窗口功能,这时唯有充分利用鼠标光标的动态提示功能,就象WINDOWS 普通窗口中鼠标光标停在窗口边框上时鼠标光标变成双箭头形状来提示用户此时可以改变窗口大小那样,这个功能在高级窗口界面设计中非常重要。
实现鼠标光标动态提示功能前需要定制鼠标光标形状,窗口拖动功能的动态提示光标形状一般为四箭头图案,这可以利用微软公司的SDK、FPT3.0和VC++4.1等高级开发软件中的资源编辑器"IMAGE EDIT"等来实现。光标资源文件一般为32X32的2色或16色.CUR图形文件,可根据实现的功能来具体确定光标图案或直接使用WINDOWS 系统中提供的光标资源文件,当自己利用资源编辑器绘制光标图案后,还需要利用DEBUG. EXE程序修改光标资源文件中的鼠标光标显示偏移坐标,以便光标图案能象WINDOWS 系统中的动态提示光标一样,动态提示时光标图案中心点正好处于屏幕的当前位置。这个偏移坐标值位于光示资源文件中的10和12处的双字节位置,如动态提示光标资源文件名为MOUSEM.CUR,要使32X32(2色)的光标图形显示时图案的中心点正好处于当前屏幕位置,其修改方法如下: #p#分页标题#e#
C>DEBUG MOUSEM.CUR
-E 10A
XXXX:10A 00.10 00.00 00.10 00.00
-W
建立起自己的鼠标光标资源文件后,首先需要在应用程序的资源文件中定义鼠标光标,资源文件中的定义方法为:
imecurm CURSOR mousem.cur
鼠标光标资源文件只有在定义之后,才能在应用程序中利用LoadCursor()函数调入内存使用,其调用方法为:
HCURSOR hCurm;//将鼠标光标资源文件数据调入内存
hCurm=LoadCursor(hInstance,"imecurm");
当需要动态改变鼠标光标形状的客户区域为整个窗口或某个子窗口的全部客户区域时,在注册客户应用程序窗口类时定义相应的鼠标光标资源句柄,当鼠标光标移到相应窗口内时立刻变成定制的光标形状,移出相应窗口时自动恢复原来光标形状。实现鼠标光标这一动态提示功能的定义方法如下:
wc.hCursor=hCurm;
当鼠标光标需要在窗口的特定客户命令按钮区域内或非特定客户命令区域内进行动态提示时,就不能使用上述定义方法,必须在窗口函数处理WM_MOUSEMOVE消息时进行特殊处理:首先判断鼠标光标指针当前位置是否在拖动命令按钮或非特定客户区域内移动,如果鼠标指针位置满足拖动窗口功能区域的要求,则利用API函数SETCURSOR()改变鼠标光标图案,提示用户此时可以进行窗口拖动操作,并将鼠标输入控制权交给当前窗口,同时设置改变鼠标光标标志;当鼠标指针移出拖动窗口启动命令区域时,恢复原来鼠标光标图案同时释放鼠标输入焦点控制权,并清除鼠标光标动态提示标志单元。其功能性代码描述如下:
BOOL DragFlag; //动态提示光标标志
case WM_MOUSEMOVE: //鼠标光标移动处理
pt=MAKEPOINT(lParam); //鼠标光标当前位置指针
if(PtInRect(&DragRT,pt)){//鼠标指针在拖动命令区域内则
SetCursor(hCurm); //动态改变鼠标光标形状
SetCapture(hWnd); //将鼠标输入控制权交当前窗口
iFlag=TRUE; //设置鼠标光标形状改变标志
} else if(iFlag==TRUE){ //鼠标指针未在拖动命令区域内
SetCursor(LoadCursor(NULL,IDC_ARROW));//恢复原形状
ReleaseCapture() //释放鼠标输入控制权
iFlag=FALSE; //恢复鼠标光标形状改变标志
}
break;
二、WINDOWS高级窗口拖动方案中拖动框的客户定制方法
以上介绍了窗口拖动前鼠标光标位置检测及客户命令区域内拖动功能的鼠标光标动态提示方法,当用户通过鼠标光标动态提示功能取得满足拖动窗口条件时,通过点击鼠标左键来启动拖动方案,这时最关键的技术问题是鼠标拖动窗口移动过程中的拖动框显示与擦除功能实现。窗口拖动虚框就是在WINDOWS 整个屏幕区域内显示描述被拖动窗口大小的线框,它的大小需要根据被拖动窗口的矩形区域大小和实际需要来具体确定,一般情况下为被拖动窗口的矩形区域大小。
WINDOWS 系统中的绘图方法是通过显示设备描述表实现的,绘图操作需要占用一定的GDI 资源,系统为窗口、菜单、对话框、字体和各种绘图函数分配足够的GDI资源,WINDOWS 95中的GDI资源要比WINDOWS3.X中的GDI资源大得多。WINDOWS中有两种使用显示设备描述符表的方法:更新窗口显示客户区域和直接操作窗口显示客户区域。更新窗口显示客户区域是直接针对应用程序窗口矩形区域而言的,在窗口函数响应WM_PAINT消息时利用图形操作命令进行窗口更新处理:
InvalidateRect(hWnd,&WinRECT,TRUE);//WinRECT为要更新区域
UpdateWindow(hWnd);
窗口初始建立时默认更新窗口的全部区域,当要更新的矩形区域为NULL时表示更新窗口所有矩形区域。函数UpdateWindow()通知系统向要更新矩形区域的窗口发送WM_PAINT消息,窗口函数接收到WM_PAINT消息后首先利用BeginPaint()函数取得设备描述符表,然后利用图形命令直接对显示设备进行更新操作,最后利用EndPaint()函数通知系统更新操作结束。其描述性功能代码如下:
case WM_PAINT:
PAINTSTRUCT ps;
hdc=BeginPaint(hWnd,&ps);//取得设备描述符表
SetBkMode(hdc,OPAQUE); //设备更新方式
SetBkColor(hdc,0x00c0c0c0);
//更新矩形区域内图形操作
EndPaint(hWnd,&ps); //结束更新操作
break;
更新窗口矩形区域直接使用窗口类中定义的屏幕画刷,即使利用SelectObject()函数选择相应屏幕画刷也无效,而且更新矩形区域范围是通过InvalidateRect()函数累加的,由UpdateWindow()函数通知系统开始进行窗口更新操作,整个过程是由系统来调度的,因此使用这种方法无法实现窗口的拖动虚框绘制和实时操作。
直接操作窗口客户区域的方法是利用GetDC( )函数直接取得显示设备句柄,利用各种图形操作命令直接对显示设备进行绘图,它使用屏幕当前设置的画笔和画刷来实现各种图形绘制操作,无须系统任何消息应用程序就可以实时地对屏幕窗口进行更新和绘图操作。其操作过程是首先取得显示设备描述符句柄:
HDC hDC;
hDC=GetDC(hWnd);//取得hWnd窗口设备描述符表句柄
当hWnd参数为NULL时取得的是整个屏幕的设备描述符表句柄,然后利用SelectObject()函数设置当前屏幕的画笔和画刷,就可以利用各种画图函数完成屏幕的绘图操作,最后利用ReleaseDC( )函数释放获取的显示设备描述表。由于这种方法可以直接控制当前屏幕的画笔和画刷,并且无需系统调度就可以直接对屏幕设备进行操作,因此利用这种方法完全可以实现窗口的拖动虚框。窗口的拖动虚框是用来描述要移动窗口大小的虚线框和实线框,当矩形拖动框为虚线时,需要利用画点或画线函数经过一定算法来实现,这就需要设置当前的屏幕画笔;当窗口的拖动框为实线框时,如果利用画线函数只需设置屏幕画笔即可,如果利用画矩形函数Rectangle( )在设置当前屏幕画笔的同时必须使用SelectObject(hDC,GetStockObject(NULL_BRUSH))屏蔽掉任何屏幕画刷,否则WINDOWS程序会很快吞筮掉所有GDI资源,相当于在屏幕设备资源中增加了无数矩形区域。 #p#分页标题#e#
对于窗口拖动框的擦除操作,只需在拖动框绘制函数中将屏幕的图形画笔操作方式设置为R2_XORPEN异或方式,即SetROP2(hDC2,R2_XORPEN),在拖动框绘制结束时注意恢复,然后在窗口拖动框移动到下一个位置前,在原屏幕位置重新调用绘制函数一次将原来拖动框擦除。下面给出笔者利用画矩形、画线和画点函数实现的拖动框函数,用户在使用时可选择自己喜欢的实线或虚线拖动框函数。
函数1为利用画矩形函数实现的拖动实框,其特点是函数的效果高,拖动框作图速度快;函数2为利用画线函数实现的拖动框,其特点是通过设置不同的画线类型可以画虚框也可以画实框;函数3为利用画线函数实现的拖动虚框函数,特点是拖动虚框图案变化灵活,不足是函数效率低作图速度慢。函数通过参数可选择不同的拖动虚框图案或密度。函数3参数XY为1时与WINDOWS 3.X窗口拖动缺省虚框相同为单虚线框,如果XY参数为2 则拖动虚框为矩齿形边框。也可以根据需要选择不同的拖动虚框图案和相应画笔和画刷以达到不同的效果。
//函数1:利用画矩形函数实现拖动实框
void DrawMoveRect(int xx1,int yy1,int xx2,int yy2,int xy)
{
HDC hDC;
int oldrop2,m,k;
hDC = GetDC(NULL); //取得全屏幕设备描述句柄
oldrop2= GetROP2(hDC); //取得原来屏幕画图方式
SetROP2(hDC,R2_XORPEN); //设置异或屏幕画图方式
SelectObject(hDC,GetStockObject(NULL_BRUSH));//屏蔽画刷
SelectObject(hDC2,GetStockObject(WHITE_PEN));//选择画笔
for (k=0;k
xx1-=1;
xx2+=1;
yy1-=1;
yy2+=1;
Rectangle(hDC2,xx1,yy1,xx2,yy2);
}
SetROP2(hDC2,oldrop2); //恢复原来作图方式
ReleaseDC(NULL,hDC2); //释放设备描述符表
}
//函数2:利用画线函数实现拖动实框或虚框
void DrawMoveRect(int xx1,int yy1,int xx2,int yy2,int xy)
{ HDC hDC2;
int oldrop2,m,k;
hDC = GetDC(NULL); //取得全屏幕设备描述句柄
oldrop2= GetROP2(hDC); //取得原来屏幕画图方式
SetROP2(hDC,R2_XORPEN); //设置异或屏幕画图方式
SelectObject(hDC,GetStockObject(NULL_BRUSH));//屏蔽画刷
SelectObject(hDC2,GetStockObject(WHITE_PEN));//选择画笔
for (k=0;k
xx1-=1;
xx2+=1;
yy1-=1;
yy2+=1;
MoveTo(hDC2,xx1,yy1);
LineTo(hDC2,xx2,yy1);
MoveTo(hDC2,xx1,yy1);
LineTo(hDC2,xx2,yy1);
}
SetROP2(hDC2,oldrop2); //恢复原来作图方式
ReleaseDC(NULL,hDC2); //释放设备描述符表
}
//函数3:利用画点函数实现不同图案的拖动虚框
void DrawMoveRect(int xx1,int yy1,int xx2,int yy2,int xy)
{ HDC hDC2;
int oldrop2,I,j,x1,x2,y1,y2;
hDC = GetDC(NULL); //取得全屏幕设备描述句柄
oldrop2= GetROP2(hDC); //取得原来屏幕画图方式
SetROP2(hDC,R2_XORPEN); //设置异或屏幕画图方式
SelectObject(hDC,GetStockObject(NULL_BRUSH));//屏蔽画刷
SelectObject(hDC2,GetStockObject(WHITE_PEN));//选择画笔
for (j=0;j
x1=xx1-j; //带注释部分为另一图案
x2=xx2+j;
y1=yy1-j;
y2=yy2+j;
for (I=x1;I
SetPixel(hdc,I,y1,RGB(255,0,0));
//if (I
for (I=y1;I
SetPixel(hdc,x2,I,RGB(255,0,0));
//if (I
for (I=x2;I>x1;I-=2)
SetPixel(hdc,I,y2,RGB(255,0,0));
//if (I>x1+2) SetPixel(hdc,I-1,y2-1,RGB(255,0,0));}
for (I=y2;I>y1;I-=2)
SetPixel(hdc,x1,I,RGB(255,0,0));
//if (I>y1+2) SetPixel(hdc,x1+1,I-1,RGB(255,0,0));}
}
SetROP2(hDC2,oldrop2); //恢复原来作图方式
ReleaseDC(NULL,hDC2); //释放设备描述符表
}
三、WINDOWS高级窗口客户区域拖动技术实现的“三步曲”
WINDOWS 高级窗口的客户区域拖动命令判断、拖动功能的鼠标光标动态提示和定制窗口拖动框函数之后,就需要实现整个拖动方案中的拖动过程启动、窗口拖动框移动和拖动结束处理的三步曲过程。于是必须在窗口函数中直接处理WM_LBUTTONDOWN、WM_MOUSEMOVE和WM_LBUTTONUP消息,来具体处理上述三个步骤中的细节问题。
第一步,在窗口函数中对鼠标点击消息WM_LBUTTONDOWN进行判断处理,以处理用户通过鼠标光标动态提示功能获取满足窗口拖动条件时,按下鼠标左键产生的启动拖动过程消息,其功能性代码如下:
POINT pt;
BOOL MoveFlag=FALSE;
case WM_LBUTTONDOWN:
pt = MAKEPOINT(lParam); //获取鼠标光标指针
if(PtInRect(&DragRT,pt)){//DragRT为拖动命令区域
DragBegin((LPRECT)&WinRT,lParam,hWnd,2);
//启动窗口拖动过程
} else {进行其它处理}
break;
上述DragBegin( )函数为笔者开发的窗口拖动启动函数,由于一个高级窗口应用程序中往往存在很多窗口,所以将其作为一个单独函数处理。其中WinRT 为高级窗口矩形区域,这里作为拖动框矩形区域参数来传递,lParam为鼠标光标指针长整数,hWnd为当前被拖动窗口的句柄,2 为拖动框宽度。同时需要将鼠标控制权交给当前被拖动窗口、设置拖动窗口标志单元、保存当前鼠标在屏幕上的位置并显示被拖动窗口的拖动框。拖动功能启动函数的原形代码如下: #p#分页标题#e#
void DragBegin(
LPRECT WinRect, //拖动框的矩形区域
LPARAM lParam, //鼠标光标当前指针
HWND hwnd, //当前窗口句柄
unsigned int kk) //拖动框显示的宽度
{
SetCapture(hwnd); //拖动时窗口必须具有鼠标输入权
MoveFlag=TRUE; //设置拖动标志
oldmx=LOWORD(lParam);//记录