MFC学习记录005——VS2019通过按钮拖动窗体(初学者详细步骤)

一、简介

工具: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: 本来想放一小段视频展示效果的,但是惭愧,捣鼓半天没找到在哪添加,路过哪位友人知道怎么插视频的麻烦指导一下)

四、拓展

以上可以拓展到,通过控件拖动无界面窗体。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值