一、简介
工具:VS2019社区版
项目:按住界面上的按钮,拖动窗体
二、步骤
(一)创建"MFC应用"项目
(具体细节不再阐述)
(二)拖一个按钮到界面上
(等会儿就是通过这个按钮去拖动界面的)
(三)代码
因为MFC的按钮本身点击相关的事件只有单击和双击,并且当鼠标在控件上方时,窗体本身的"MouseMove"事件不响应,所以不能像上一章”拖动没有标题栏的窗体“一样,通过”MouseMove“事件来拖动窗体。
我们需要“PreTranslateMessage”来拦截一下鼠标的消息,主要是截获鼠标的按下和抬起事件。
(1)”XXXXDlg.h“
public:
BOOL PreTranslateMessage(MSG* pMsg); //拦截鼠标“按下”的消息
void OnTimer(UINT_PTR nIDEvent); //计时器动作
void MoveTheWindow(CPoint PrePoint, CPoint CurPoint);
见下图红色框中的部分(其他是系统自动生成的代码,不用管):
(2)“XXXXDlg.cpp”
思路整理:
a. 首先我们需要把 按钮原本的Click 和 按钮的长按之后松开 区分开来,本质上可以这么看
按钮原本的Click:极短时间内按钮按下,抬起(经测试,点击事件中,“按下"约耗时2~5ms)
按钮的长按之后松开:这里我人为把 按下的时间>5ms 定义为长按
b. 移动窗体的本质上可以当成 点到点的移动,即当鼠标前一刻点的位置与当前点的位置不同时,
就要移动点到当前位置。所以,截获鼠标按下的动作时,还要不断记录此刻鼠标的坐标,与前一刻的坐标对比。
c. 按住按钮,鼠标不断移动的过程中,只会截获到一次鼠标按下事件,但是在鼠标松开之前,我们还需要不断去获取鼠标的位置,从而能够移动窗体。所以,这里我的设计是,鼠标按下时,单独开一个线程去不断获取鼠标当前的位置。
d. 为了区分按钮点击事件和按钮拖动窗体事件,我们可以在按钮的点击事件中区分开来。5ms以内算点击事件。
代码实现
首先在开头加上一些必要的全局变量的定义和函数的申明:
int iDownTime = 0; //计数鼠标按下的事件
BOOL bMouseDown = false; //鼠标是否按下
CPoint PrePoint; //记录前一刻点的位置
void ThreadMoveWin();//移动窗体的线程
函数的具体实现:
/// <summary>
/// 拦截消息
/// </summary>
/// <param name="pMsg"></param>
/// <returns></returns>
BOOL CMoveFrmByBtnDemoDlg::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_LBUTTONDOWN)//拦截鼠标左键按下消息
{
if (pMsg->hwnd == GetDlgItem(IDC_BUTTON1)->m_hWnd )//判断按下的位置是否为目标button
{
iDownTime = 0;
bMouseDown = true;
GetCursorPos(&PrePoint);
SetTimer(1, 1, NULL); //计时器1,每隔1ms触发一次”OnTimer“事件
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadMoveWin, NULL, 0, 0); //单独线程拖动窗口
}
}
if (pMsg->message == WM_LBUTTONUP)//拦截鼠标左键按下消息
{
if (pMsg->hwnd == GetDlgItem(IDC_BUTTON1)->m_hWnd)//判断按下的位置是否为目标button
{
KillTimer(1); //关掉计时器1
bMouseDown = false;
}
}
return CDialog::PreTranslateMessage(pMsg); //这个一定要有
}
/// <summary>
/// 响应定时器
/// </summary>
/// <param name="nIDEvent"></param>
void CMoveFrmByBtnDemoDlg::OnTimer(UINT_PTR nIDEvent)
{
iDownTime = iDownTime + 1;
CDialogEx::OnTimer(nIDEvent);
}
/// <summary>
/// 移动窗体
/// </summary>
/// <param name="PrePoint"></param>
/// <param name="CurPoint"></param>
/// <returns></returns>
void CMoveFrmByBtnDemoDlg::MoveTheWindow(CPoint PrePoint, CPoint CurPoint)
{
CPoint ptTemp = CurPoint - PrePoint;
CRect rcWindow;
GetWindowRect(&rcWindow);
rcWindow.OffsetRect(ptTemp.x, ptTemp.y);
MoveWindow(&rcWindow);
}
/// <summary>
/// 移动窗体的线程
/// </summary>
void ThreadMoveWin()
{
CPoint CurPoint;
while (bMouseDown)
{
GetCursorPos(&CurPoint);
if (CurPoint != PrePoint)
{
((CMoveFrmByBtnDemoDlg*)theApp.GetMainWnd())->MoveTheWindow(PrePoint, CurPoint);
}
PrePoint = CurPoint;
}
}
/// <summary>
/// 按钮点击事件
/// </summary>
void CMoveFrmByBtnDemoDlg::OnBnClickedButton1()
{
if (iDownTime<5)
{
AfxMessageBox(_T("点击按钮事件"));
}
}
另外,因为里面涉及到了定时器,需要用到OnTimer(),所以我们需要在类向导里添加”WM_TIMER“消息。
注意,要看"XXXXDlg.cpp"中消息响应有没有添加上,有两处消息响应,不要弄混了。
注意看,上图中两个"BEGIN_MESSAGE_MAP" - "END_MESSAGE_MAP" 是不一样的。类向导中,添加消息响应的类默认是"CAboutDlg",要改成我们的项目名对应的Dlg,不然OnTimer会跟SetTimer对应不上,函数进不去。
我们要用到的是下面那个BEGIN_MESSAGE_MAP,所以,类向导中的类一定要选对。也可以不通过类向导,自己手动在“BEGIN_MESSAGE_MAP”中添加。
三、结果
以上代码就可以实现通过按钮拖动窗体的功能,亲测有效哦。
(PS: 本来想放一小段视频展示效果的,但是惭愧,捣鼓半天没找到在哪添加,路过哪位友人知道怎么插视频的麻烦指导一下)
四、拓展
以上可以拓展到,通过控件拖动无界面窗体。