拖动虚框绘制

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;iDEBUG 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资源,相当于
在屏幕设备资源中增加了无数矩形区域。
对于窗口拖动框的擦除操作,只需在拖动框绘制函数中将屏幕的图形
画笔操作方式设置为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;kx1;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 为拖动框宽度。同时需要将鼠标控制权交给当前被拖动窗口、
设置拖动窗口标志单元、保存当前鼠标在屏幕上的位置并显示被拖动
窗口的拖动框。拖动功能启动函数的原形代码如下:
void DragBegin(
LPRECT WinRect, //拖动框的矩形区域
LPARAM lParam, //鼠标光标当前指针
HWND hwnd, //当前窗口句柄
unsigned int kk) //拖动框显示的宽度
{
SetCapture(hwnd); //拖动时窗口必须具有鼠标输入权
MoveFlag=TRUE; //设置拖动标志
oldmx=LOWORD(lParam);//记录当前鼠标屏幕坐标X
oldmy=HIWORD(lParam);//记录当前鼠标屏幕坐标Y
DrawMoveRect(WinRect->left,WinRect->top,//显示拖动框
WinRect->right,WinRect->bottom,kk);
}
第二步,需要处理鼠标拖动窗口时的拖动框移动过程,这需要在窗口
函数中进行WM_MOUSEMOVE消息处理。拖动框的移动包括上次显示拖动
框的清除和本次拖动框的显示两步,由于拖动框绘制函数中对当前的
绘制方式进行重新设置,异或方式使得只要重新在原屏幕坐标位置处
调用一次该函数即可清除拖动框,因此,在鼠标拖动窗口移动过程中
显示和清除拖动框只需要调用两次拖动框绘制函数即可。另外,拖动
框在屏幕上位置的计算方法也非常简单,就是将当前取得的屏幕位置
坐标值减去保存的前次屏幕位置坐标值所得鼠标移动偏移量,再用原
来窗口屏幕左上角坐标值加上这个偏移量,就可以确定被拖动窗口和
拖动框新的屏幕位置坐标值。其处理过程的描述性代码如下:
case WM_MOUSEMOVE:
DragMove((LPRECT)&WinRT,WinWT,WinHi,lParam,2);
//WinRT为窗口矩形区域,WinWT为窗口宽度,WinHI为窗口高度
} else {进行其它处理}
break;
鉴于高级窗口应用程序一般为多个子窗口,所以将拖动框移动处理过
程单独编制成函数,并且对鼠标拖动窗口过程中,窗口不能完全位于
屏幕可见区域之内进行了特殊处理,开发者可根据需要自行调整其位
置,以便被拖动的窗口能够完全被显示于屏幕可视区域内,其拖动过
程函数原形代码部分如下:
void DragMove(
LPRECT rcwin, //拖动框矩形区域
unsigned int wi, //被拖动窗口宽度
unsigned int hi, //被拖动窗口高度
LPARAM lParam, //鼠标位置指针
unsigned int kk) //拖动框边框宽度
{
DrawMoveRect(rcwin->left,rcwin->top,
rcwin->right,rcwin->bottom,kk);//清除上次画拖动框
rcwin->left+=LOWORD(lParam)-sImeG.oldmx;//计算窗口
rcwin->top+=HIWORD(lParam)-sImeG.oldmy; //新位置
sImeG.oldmx=LOWORD(lParam); //保存当前坐标值
sImeG.oldmy=HIWORD(lParam);
if (rcwin->left<0) rcwin->left=0;//对窗口超越屏幕
if (rcwin->left>sImeG.xScrWi-wi) //可视区域处理
rcwin->left=sImeG.xScrWi-wi;
ii=sImeG.yScrHi-hi-(sImeG.WinVer<0x35f ? 0:BOTOFF);
if (rcwin->top<0) rcwin->top=0; //对WIN95进行底部
if (rcwin->top>ii) rcwin->top=ii;//特殊保留处理
rcwin->right =rcwin->left+wi-1;
rcwin->bottom=rcwin->top+hi-1;
DrawMoveRect(rcwin->left,rcwin->top,
rcwin->right,rcwin->bottom,kk);//画新位置拖动框
}
第三步,在鼠标拖动窗口结束时需要进行窗口的实际移动处理,这就
需要在处理WM_LBUTTONUP消息时利用MOVEWINDOW()命令进行实际移动
处理。同样鉴于多窗口原因仍然需要将这个处理过程单独形成一个函
数,而且在移动窗口前还需要利用绘制函数清除屏幕上所画的拖动
框,如果窗口未完全位于屏幕的可见位置,还必须进行适当调整使被
拖动的窗口能够完全位于屏幕可视区内,同时释放鼠标控制权并清除
拖动窗口标志单元。结束过程的描述性代码部分如下:
case WM_LBUTTONUP:
if (sImeG.MoveFlag==TRUE){//拖动标志有效
DragEnd((LPRECT)&WinRT,WinWT,WinHI,hWnd);
}
拖动结束处理函数的原形代码部分如下:
void DragEnd(
LPRECT rcwin, //拖动框矩形区域
unsigned int wi, //被拖动窗口宽度
unsigned int hi, //被拖动窗口高度
unsigned int kk) //拖动框边框宽度
{
DrawMoveRect(rcwin->left,rcwin->top,
rcwin->right,rcwin->bottom,1); //清除拖动框
if (rcwin->left<0) rcwin->left=0;//对窗口超越屏幕
if (rcwin->left>sImeG.xScrWi-wi) //可视区域处理
rcwin->left=sImeG.xScrWi-wi;
ii=sImeG.yScrHi-hi-(sImeG.WinVer<0x35f ? 0:BOTOFF);
if (rcwin->top<0) rcwin->top=0; //对WIN95进行底部
if (rcwin->top>ii) rcwin->top=ii;//特殊保留处理
rcwin->right =rcwin->left+wi-1;
rcwin->bottom=rcwin->top+hi-1;
MoveWindow(hwnd,rcwin->left,rcwin->top,
wi,hi,TRUE); //将窗口实际移到新位置
sImeG.MoveFlag=FALSE; //清除拖动标志单元
ReleaseCapture(); //释放鼠标控制权
}

四、WINDOWS高级窗口的客户区域拖动技术的实际应用
上述介绍的WINDOWS 高级窗口客户区域拖动技术的有关技术和拖动方
案“三步曲”的实现过程,这些技术原理在WINDOWS95和WINDOWS3.X
下同样适应,但由于消息是WINDOWS系统中的最后一道防线,如果处
理得不好就会使应用程序“误入歧途”,影响开发效率和程序效果,
若处理得恰到好处就会使你的应用程序具有很高的专业水准,如虎添
翼。因此,实现适合自己应用程序的有效拖动方案,对开发不同应用
的影响和程序的运行效率具有深远的影响。虽然实现WINDOWS 高级窗
口应用程序拖动方案的方法不止一种,但笔者仍未见过更加简捷高效
的拖动方案,本文介绍的实现方案较具有很好的适应性和优秀的运行
效果,具体表现在:
开发者可根据自己的实际需要控制窗口拖动框的大小、拖动框颜色和
拖动框的具体图案,具有拖动命令区域的鼠标光标动态提示功能,窗
口拖动功能的启动、拖动过程和拖动结束处理均是独立的子程序可提
供给多窗口应用程序直接调用,启动过程选择灵活,在拖动结束时可
随时控制被拖动窗口全部显示在屏幕可见区域内,其它功能扩充简便
灵活,程序的运行效果理想等等。
本文给出的WINDOWS 高级窗口拖动方案描述性功能代码和通用子程
序,均在笔者开发的“轻松使用汉字输入法”程序中实际应用,是这
个程序中实现窗口拖动功能的关键代码,均在WINDOWS95和WINDOWS
3.X下试用效果很好,因此推荐读者开发应用时将其作为首选方案。
在本文介绍的基础上,相信读者对WINDOWS 高级窗口的客户区域拖动
技术有了全面了解,同时为开发具有客户区域拖动窗口的应用程序提
供了可行的实现方案,希望读者在此基础上进行深入研究,以开发出
更加理想的WINDOWS 高级窗口客户区域拖动方案,编制出更加具有专
业特色的WINDOWS高级应用程序。

你可能感兴趣的:(拖动虚框绘制)