1,目标
实现类似360悬浮窗口这样的效果,当窗口在屏幕边缘时,鼠标移开,就自动向边缘隐藏,鼠标放上去,就又平滑显示出来。
正常状态:
边缘自动隐藏:
2,原理
首先是实现圆角或椭圆这种不规则形状的窗口,可以参考另一篇文章:
然后需要给没有标题栏的窗口增加拖拽移动的功能,这个就是自己手动发送一个消息,使windows认为鼠标在标题条上
对于窗口的移动显示隐藏,使用了定时器。
其中有一些做判断的函数,如判断在窗口在屏幕某个边缘,判断鼠标是否在窗口内部等。
3,实现
①新建MFC对话框程序Test360.去掉默认控件和属性中的边框。参考上面所说的文章实现一个带圆角及背景图片的窗口。
由于这里还是截图然后用PS简单选择了个范围,所以还有毛边,若是有美工原图或PS仔细些,是没问题的。
②给Dlg类CTest360Dlg添加一条消息响应OnLButtonDown,在其中传送WM_NCLBUTTONDOWN消息,达到拖动效果。
void CTest360Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{
CDialog::OnLButtonDown(nFlags, point);
// 实现拖动窗口
// 发送WM_NCLBUTTONDOWN消息
// 使Windows认为鼠标在标题条上
// 或SendMessage(WM_SYSCOMMAND,SC_MOVE | HTCAPTION,0);
PostMessage(WM_NCLBUTTONDOWN,HTCAPTION,MAKELPARAM(point.x, point.y));
}
③添加几个判断窗口是否在屏幕边缘的函数:
//是否靠近屏幕左边缘
BOOL CTest360Dlg::NearLeftBorder()
{
CRect rc;
GetWindowRect(rc);
//窗口左边界在屏幕左边界20像素内都算“靠近”
if (rc.left < 20)
{
return TRUE;
}
return FALSE;
}
//是否靠近屏幕上边缘
BOOL CTest360Dlg::NearUpBorder()
{
CRect rc;
GetWindowRect(rc);
if(rc.top<20)
{
return TRUE;
}
return FALSE;
}
//是否靠近右边缘
BOOL CTest360Dlg::NearRightBorder()
{
CRect rc;
GetWindowRect(rc);
int nWidth = GetSystemMetrics(SM_CXSCREEN);
if (rc.left>nWidth - rc.Width())
{
return TRUE;
}
return FALSE;
}
④判断鼠标是否在窗口内。
BOOL CTest360Dlg::MouseInWnd()
{
CRect rc;
GetWindowRect(rc);
POINT pt;
GetCursorPos(&pt);
if (PtInRect(&rc,pt))
{
return TRUE;
}
return FALSE;
}
⑤定义一个定时器,
#define TIMER_MOVE 1
在CTest360Dlg::OnInitDialog()中启动:
BOOL CTest360Dlg::OnInitDialog()
{
CDialog::OnInitDialog();
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
//设置窗口形状
SetRegion(GetDC(),IDB_BITMAP_360RGN,RGB(0,0,0));
//初始时居中
CenterWindow();
//设置定时器,处理悬浮窗的显隐移动
SetTimer(TIMER_MOVE,10,NULL);
return TRUE;
}
处理如下:
void CTest360Dlg::OnTimer(UINT_PTR nIDEvent)
{
if (nIDEvent == TIMER_MOVE)
{
//鼠标按着的,就怎么都不移动
if (GetKeyState(VK_LBUTTON)<0)
{
return;
}
//靠近屏幕上边缘
if (NearUpBorder())
{
//根据鼠标动作进行窗口的移动(鼠标进入区域就向下平移显示,鼠标离开就向上平移隐藏)
MoveUp();
return;
}
//靠近屏幕左边缘
if (NearLeftBorder())
{
//根据鼠标动作进行窗口的移动(鼠标进入区域就向右平移显示,鼠标离开就向左平移隐藏)
MoveLeft();
return;
}
//靠近屏幕右边缘
if (NearRightBorder())
{
//根据鼠标动作进行窗口的移动(鼠标进入区域就向左平移显示,鼠标离开就向右平移隐藏)
MoveRight();
return;
}
}
CDialog::OnTimer(nIDEvent);
}
其中GetKeyState先强行过滤掉鼠标按下,让这种情况不移动。避免刚拖动窗口到屏幕边缘时鼠标还没松开就直接开始移动了。
3个Move函数,是真正按像素移动窗口的地方,包括来回(出屏幕和进屏幕)。原理是一样的,看明白一个就OK了。
void CTest360Dlg::MoveUp()
{
CRect rc;
GetWindowRect(rc);
//鼠标进入则下移,显示出来
if(MouseInWnd())
{
int height = rc.Height();
if (rc.top>=0)
{
rc.top = 0;
}
else
{
rc.top++;
}
rc.bottom = rc.top + height;
MoveWindow(rc);
}
//鼠标在别处,窗口就往上移出屏幕
else
{
int height = rc.Height();
//窗口向上移动一像素,如果快隐藏(露20)就不移了
if (rc.top<= 20 - height)
{
rc.top = 20 - height;
ShowWindow(SW_HIDE);
m_upDlg->m_Test360Dlg = this;
m_upDlg->DoModal();
}
else
{
rc.top--;
}
rc.bottom = rc.top + height;
MoveWindow(rc);
}
}
void CTest360Dlg::MoveLeft()
{
CRect rc;
GetWindowRect(rc);
//鼠标进入则下移,显示出来
if(MouseInWnd())
{
int width = rc.Width();
if (rc.left>=0)
{
rc.left = 0;
}
else
{
rc.left++;
}
rc.right = rc.left + width;
MoveWindow(rc);
}
//鼠标在别处,窗口就往上移出屏幕
else
{
int width = rc.Width();
//窗口向左移动一像素,如果快隐藏(留20像素)就不移了
if (rc.left<= 20 - width)
{
rc.left = 20 - width;
}
else
{
rc.left--;
}
rc.right = rc.left + width;
MoveWindow(rc);
}
}
void CTest360Dlg::MoveRight()
{
CRect rc;
GetWindowRect(rc);
int sysWidth = GetSystemMetrics(SM_CXSCREEN);
//鼠标在窗口内则窗口左移,显示出来
if(MouseInWnd())
{
int width = rc.Width();
if (rc.left<= sysWidth - width)
{
rc.left = sysWidth - width;
}
else
{
rc.left--;
}
rc.right = rc.left + width;
MoveWindow(rc);
}
//鼠标没在窗口上,窗口就往右移出屏幕
else
{
int width = rc.Width();
//窗口向右移动一像素,如果快隐藏了(还留20像素)就不移了
if (rc.left>= sysWidth - 20)
{
rc.left = sysWidth - 20;
}
else
{
rc.left++;
}
rc.right = rc.left + width;
MoveWindow(rc);
}
}
对MoveUp做说明:
当Timer中判断到窗口在屏幕上边缘时,进入MoveUp,如果此时鼠标进入窗口内,窗口就往下方移动直到完全显示;如果鼠标离开窗口,那么窗口会立即往上隐藏,直到留下一小截。 360官方软件现在是换了个半圆形的窗口“趴”在屏幕边上。这里主要是模拟触发移动的效果。
4,效果
几张截图
左侧:
上侧:
右侧:
5,源码
MFC模拟360悬浮窗加速球Test360_VS2008工程.rar