成立磁性窗体
副标题#e#
一些著名的共享软件不单成果卓著,并且在措施界面的设计能力上往往率领了一种时尚,WinAmp就是个中的一个代表。WinAmp有两个绝活,一是可以改换窗体的外观,也就是此刻俗称的给软件换“皮肤”;另一个等于磁性窗体能力。
磁性窗体即若干窗体接近到必然间隔以内时会相互粘在一起,可能说彼此吸附在一起,然后在拖动主窗体时,粘在其上的其它窗体也一起随着移动,仿佛酿成了一个窗体。海内的MP3播放器新秀CDOK也实现了这种能力,并且更绝,把几个窗体粘在一起后,窗体没有主从之分,拖动个中任意一个窗体城市使其它的窗体一起移动。在CSDN上有关奈何设计磁性窗体的帖子很是多,说明这个能力深得宽大措施员的青睐。
本文先把几位网友的要明确加阐明,然后给出我认为较量可行的实现要领和源代码。
实现磁性窗体根基上分为两步,第一步是实现当两个窗体接近到必然间隔以内时实现窗体间的粘贴操纵,第二步是移动窗体时,同时移动与它粘在一起的其它窗体。
实现窗体的粘贴
实现粘贴的难点在于什么时候举办这个操纵,假设有两个窗体Form1和Form2,移动Form2向Form1接近,当Form2与Form1的最近间隔小于distance时粘贴在一起。显然,应该在移动Form2的进程中举办判定,问题是在措施的什么位置插入判定代码呢?
CSDN上有人认为可以利用按时器,每隔必然的时间查抄各个窗体的位置。这种要领有着明明的弊病,不说按时器要无谓地挥霍系统资源,单单它的即时性就难以担保。假如缩短计时值,挥霍的CPU资源就更多了,所以我也就不多说了。
公道的要领是操作系统发生的动静,可是操作什么动静呢?窗体在移动时会发生WM_WINDOWPOSCHANGING和WM_MOVING动静,移动竣事后会发生WM_WINDOWPOSCHANGED和WM_MOVE动静。WM_WINDOWPOSCHANGING和WM_WINDOWPOSCHANGED动静的参数lParam是布局WINDOWPOS的指针,WINDOWPOS界说如下:
#p#副标题#e#
typedef struct _WINDOWPOS {
HWND hwnd; // 窗口句炳
HWND hwndInsertAfter; // 窗口的Z顺序
int x; // 窗口x坐标
int y; // 窗口的y坐标
int cx; // 窗口的宽度
int cy; // 窗口的高度
UINT flags; // 符号位,按照它设定窗口的位置
} WINDOWPOS;
可以看出,WM_WINDOWPOSCHANGED动静不只仅在窗口移动时发生,并且在它的Z顺序产生变革时也会发生,包罗窗口的显示和埋没。所以我认为这个动静不是最佳选择。
WM_MOVING和WM_MOVE动静的参数lParam是一个RECT布局指针,与WM_WINDOWPOSCHANGED动静对较量为纯真,我回收的等于这个动静。下面我给出用C++ Builder写的示例措施。
为了利便措施的阅读,先界说了一个列举数据范例,暗示窗体的粘贴状态。同时界说了一个类,封装了窗体粘贴相关的数据,个中的Enable是为了防备反复举办操纵,要领是操纵时配置Enable为否,操纵竣事时规复为真,而在操纵前查抄这个符号是否为否,不然直接返回。
图2 窗体的粘贴状态示例
// 窗体粘贴状态,寄义见图2
enum enumAttachStyle
{
AS_NONE, // 没有粘贴
AS_TOP,
AS_BOTTOM,
AS_T_TOP,
AS_LEFT,
AS_RIGHT,
AS_L_LEFT
};
// 处理惩罚窗体粘贴的类,为了简化,回收了public声明
class CFormAttachStyle
{
public:
bool Enabled; // 防备反复举办粘贴相关的操纵
HWND AttachTo; // 被粘贴到哪个窗口
int XStyle; // 阁下偏向的粘贴状态
int YStyle; // 上下偏向的粘贴状态
int xPos; // 粘贴到的x坐标
int yPos; // 粘贴到的y坐标
CFormAttachStyle() // 初使化数据
{
XStyle =AS_NONE;
YStyle =AS_NONE;
Enabled=true;
hAttachTo=NULL;
}
};
函数DistanceIn用于判定两个整数的间隔是否在指定范畴内:
// 整数i1和i2的差的绝对值小于i3
bool DistanceIn(unsigned int i1,unsigned int i2,unsigned int i3)
{
if(i1>i2)
{ // 确保i2>=i1;
int t=i1;
i1=i2;
i2=t;
}
return i2-i1<=i3;
}
//---------------------------------------------------------------------------
// i1<=i2 bool Mid(unsigned int i1,unsigned int i2,unsigned int i3)
{
return ((i1<=i2) && (i2 }
//---------------------------------------------------------------------------
#p#分页标题#e#
AttachToForm是处理惩罚窗体粘贴的要害函数,假如举办了粘贴,则生存粘贴到的窗体的句柄,并调解窗体的位置。在函数中利用了窗体的Tag属性生存了一个CFormAttachStyle类的实例指针,原因将在稍后举办说明,参数distance暗示可以举办粘贴的间隔。窗口粘贴在上下、阁下各有3种形式,都需要加以判定。
// 把窗体My粘到主窗体上
bool AttachToForm(TForm *My, TForm *Form, RECT *r,int distance)
{
CFormAttachStyle *MyStyle=(CFormAttachStyle *)My->Tag;
if(MyStyle==NULL)return false; // 这个窗体不支持粘贴
//筹备粘贴到的窗体的位置
RECT rMain;
GetWindowRect(Form->Handle,&rMain);
MyStyle->AttachTo=NULL;
MyStyle->yPos=r->top;
MyStyle->xPos=r->left;
// 上下偏向判定
MyStyle->YStyle=AS_NONE;
if( Mid(rMain.left,r->left,rMain.right)
|| Mid(r->left,rMain.left,r->right)
|| (MyStyle->XStyle!=AS_NONE))
{
if(DistanceIn(r->top,rMain.bottom,space))
{
MyStyle->YStyle=AS_BOTTOM;
MyStyle->yPos=rMain.bottom;
}else if(DistanceIn(r->top,rMain.top,space))
{
MyStyle->YStyle=AS_TOP;
MyStyle->yPos=rMain.top;
}else if(DistanceIn(r->bottom,rMain.top,space))
{
MyStyle->YStyle=AS_T_TOP;
MyStyle->yPos=rMain.top-(r->bottom-r->top);
}
}
// 阁下偏向判定
MyStyle->XStyle=AS_NONE;
if( Mid(rMain.top,r->top,rMain.bottom)
|| Mid(r->top,rMain.top,r->bottom)
|| (MyStyle->YStyle!=AS_NONE))
{
if(DistanceIn(r->left,rMain.left,space))
{
MyStyle->XStyle=AS_LEFT;
MyStyle->xPos=rMain.left;
}else if(DistanceIn(r->left,rMain.right,space))
{
MyStyle->XStyle=AS_RIGHT;
MyStyle->xPos=rMain.right;
}else if(DistanceIn(r->right,rMain.left,space))
{
MyStyle->XStyle=AS_L_LEFT;
MyStyle->xPos=rMain.left-(r->right-r->left);
}
}
My->Left=MyStyle->xPos;
My->Top=MyStyle->yPos;
if(MyStyle->XStyle!=AS_NONE || MyStyle->YStyle!=AS_NONE)
{ // 粘贴乐成
MyStyle->AttachTo= Form->Handle;
}
return bool(MyStyle->AttachTo);
}
函数Do_WM_MOVING在动静轮回中处理惩罚WM_MOVING时挪用,参数My为处理惩罚动静的窗体,Msg为动静参数。
// 处理惩罚WM_MOVING事件
void Do_WM_MOVING(TForm *My,TMessage &Msg)
{
CFormAttachStyle *MyStyle=(CFormAttachStyle *)My->Tag;
if(MyStyle && MyStyle->Enabled)
{
MyStyle->Enabled=false; // 防备反复操纵
RECT *r=(RECT *)Msg.LParam ;
// 处理惩罚粘贴,这里只对粘贴到主窗体举办判定
TForm *FormApplication->MainForm;
AttachToForm(My,r,12); // 查抄是否可以粘贴窗体
MyStyle->Enabled=true; // 规复操纵状态
}
Msg.Result=0; // 通知系统,动静已经处理惩罚
}
实现窗体的关联移动
与处理惩罚窗体粘贴对比,关联窗体的难度小一些。可是从CSDN上的帖子看,回收的要领都单调并且不佳,我都不推荐。
较量直观的要领是利用窗体的MOUSEDOWN、MOUSEMOVE和MOUSEUP事件,先界说一个符号鼠标是否按下的变量:
bool bMouseDown;
在MOUSEDOWN事件中配置:
bMouseDown=true;
在MOUSEUP事件中配置:
bMouseDown=false;
在MOUSEMOVE事件中作如下处理惩罚:
if(bMouseDown)
{
// 移动当前窗体
……
// 计较窗体移动的位移
int dx;
int dy;
// 计较出dx和dy
……
// 移动其它粘贴到当前窗体的窗体
……
}
#p#分页标题#e#
这个要领的最明明的问题有两个:1、鼠标在窗体上的控件上按下时,不能收到窗体的MOUSEDOWN和MOUSEUP事件,假如同时监控各个控件的事件,贫苦是相当大的。2、窗口标题栏的鼠标事件难以正常处理惩罚。
其实,同上一段落雷同,处理惩罚窗体的WM_MOVING事件是较量好的要领。即在WM_MOVING事件中同步移动其它窗体。
移动其它窗体的要领也有多种,有人回收发送动静的方法,详细如下:
// dx和dy是当前窗体移动的间隔
// hMove是要移动的窗体
// WM_MOVEFORM是自界说的动静
PostMessage(hMove, WM_MOVEFORM,dx,dy);
被移动的窗体处理惩罚WM_MOVEFORM动静时,移动本身到新的位置。
假如是VB、Delphi一类的语言,可以直接配置其Left和Top属性。我回收的要领是利用API函数SetWindowPos,该函数从头配置指定窗口的位置。我的参考代码如下:
// 移动被粘贴在一起的其它窗体
void UnionOtherForm(TForm *My,TForm *Form,int dx,int dy)
{
if(Form==NULL)return;
CFormAttachStyle *MyStyle=(CFormAttachStyle *)(Form->Tag);
if(MyStyle)
{
if(MyStyle->Enabled && MyStyle->AttachTo==My)
{
MyStyle->Enabled=false;
int X1=Form->Left;
int Y1=Form->Top;
SetWindowPos(Form->Handle,My->Handle,
X1+dx,Y1+dy,Form->Width,Form->Height,
SWP_NOSIZE|SWP_NOACTIVATE);
MyStyle->Enabled=true;
}
}
}
// 移动被粘贴在一起的其它窗体
void AdjuctFormPos(TForm *My,RECT *r)
{
// 调解窗口位置
int dy=r->top-My->Top;
int dx=r->left-My->Left;
My->Top=r->top;
My->Left=r->left;
// 逐一查抄建设的窗体
for(int i=0;iFormCount;i++)
{
TForm *Form=Screen->Forms[i];
if(Form!=My)
{
// 调解被吸附的窗口位置
UnionOtherForm(My,Form,dx,dy);
}
}
}
// 处理惩罚WM_MOVE事件
void Do_WM_MOVE(TForm *My,TMessage &Msg)
{
// 处理惩罚粘贴乐成后的位置调解
CFormAttachStyle *MyStyle=(CFormAttachStyle *)My->Tag;
if(MyStyle && MyStyle->Enabled)
{
if(MyStyle->Enabled && MyStyle->AttachTo)
{ // 粘贴乐成
My->Left=MyStyle->xPos;
My->Top=MyStyle->yPos;
}
}
Msg.Result=0; // 通知系统,动静已经处理惩罚
}
在这里有一个C++ Builder编程的能力,纵然用Screen全局工具。假如在初使化需要利用粘贴成果的窗体时,把一个CFormAttachStyle实例的指针赋值给该窗体的Tag窗体,那么除了处理惩罚它的WM_MOVING和WM_MOVE事件外,其它的操纵都可以省略了。要害的代码如下:
// 注:应把这个函数的声明加到TForm1的类声明中
void __fastcall TForm1::WndProc(TMessage &Msg)
{
TForm::WndProc(Msg);
switch(Msg.Msg)
{
case WM_MOVING: // 处理惩罚移动事件
{
Do_WM_MOVING(this,Msg);
break;
}
case WM_MOVE: // 处理惩罚移动事件
{
Do_WM_MOVE(this,Msg);
break;
}
}
}
void __fastcall TForm1::FormCreate(TObject *Sender)
{
// 成立磁性窗体特性类
CFormAttachStyle *AttachStyle=new CFormAttachStyle;
Tag=(int)AttachStyle;
}
void __fastcall TForm1::FormDestroy(TObject *Sender)
{ // 删除CformAttachStyle实例
CFormAttachStyle *AttachStyle=(CFormAttachStyle *)Tag;
delete AttachStyle;
}
以下是主窗体处理惩罚WM_MOVING动静的代码:
void __fastcall TfmMain::WndProc(TMessage &Msg)
{
TForm::WndProc(Msg);
switch(Msg.Msg)
{
case WM_MOVING: // 处理惩罚移动事件
{
AdjuctFormPos(this,(RECT *)(Msg.LParam));
break;
}
}
}
到此,实现磁性窗体的步调根基上都先容完了