实现类似360悬浮窗口这样的效果,当窗口在屏幕边缘时,鼠标移开,就自动向边缘隐藏,鼠标放上去,就又平滑显示出来。
正常状态:
边缘自动隐藏:
首先是实现圆角或椭圆这种不规则形状的窗口,可以参考另一篇文章:
MFC实现不规则窗口
然后需要给没有标题栏的窗口增加拖拽移动的功能,这个就是自己手动发送一个消息,使windows认为鼠标在标题条上
对于窗口的移动显示隐藏,使用了定时器。
其中有一些做判断的函数,如判断在窗口在屏幕某个边缘,判断鼠标是否在窗口内部等。
①新建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); }
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官方软件现在是换了个半圆形的窗口“趴”在屏幕边上。这里主要是模拟触发移动的效果。
几张截图
MFC模拟360悬浮窗加速球Test360_VS2008工程.rar