QT任何控件,实现监听全局事件/windows事件

一、以监听键盘ctrl键按下为例

1.必须要控件有焦点时才触发就能满足业务需求的,方法1即可
2.只要程序有焦点时才触发就能满足业务需求的,方法2即可
3.只要程序跑起来,不管什么情况都要满足业务需求的,用方法3钩子

方法1、QT一般的做法的就是继承这个控件,重写该控件的键盘回调函数,当该回调函数被触发时,就是有键盘按键被按下。

//键盘按下事件
virtual void keyPressEvent(QKeyEvent * event) override;
//键盘抬起事件
virtual void keyReleaseEvent(QKeyEvent * event) override;

keyPressEvent和keyReleaseEvent
两个函数就是我们需要重写的两个按钮回调函数,函数的实现比较简单。比如我要监听ctrl是否按下了。可以如下写法。

注意:如果写在QWidget::keyPressEvent上面,就代表触发这个按键之前执行这个动作,写在下面需要等这个事件循环走完,然后再执行代码。用该方法可以来实现一些极少情况两个控件先后顺序的执行。

void TestWidget::keyPressEvent(QKeyEvent * event)
{
    
    
	if (event->key() == Qt::Key_Control)
	{
    
    
		 m_mutilSelection = true;
	}
	//要让事件继续传递,否则事件循环在这里就终止了
	QWidget::keyPressEvent(event);
}

void TestWidget::keyReleaseEvent(QKeyEvent * event)
{
    
    
	if (event->key() == Qt::Key_Control)
	{
    
    
		 m_mutilSelection = false;
	}
	//要让事件继续传递,否则事件循环在这里就终止了
	QWidget::keyReleaseEvent(event);
}

但是这种做法有一个缺陷,必须这个控件要获得焦点,才能触发这两个函数。为什么会出现这个情况呢,因为其他有焦点的控件有优先处理该键盘事件,并且人家也把事件处理了,那么Qt的事件循环就会被中断掉,该控件自然就收不到消息了。

方法2、把控件主动安装全局事件过滤器,监听全部消息

该方法只要程序有获得焦点,也就是程序是激活状态的,那么就会生效

1.首先在控件所在的界面,重写

    bool eventFilter(QObject* watched, QEvent* event);

2.在控件所在的界面,构造函数中
这样你的这个界面都会收到qt时间循环的所有消息

  QApplication::instance()->installEventFilter(this);

3.在eventFilter筛选我们想要的事件,实现功能
我这里还是监听按下和抬起Ctrl键,然后置一个标识位

  if (event->type() == QEvent::KeyPress)
    {
    
    
        QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);

        if (keyEvent->key() == Qt::Key_Control)
        {
    
    
            m_mutilSelection = true;
        }
    }
    else if (event->type() == QEvent::KeyRelease)
    {
    
    
        QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);

        if (keyEvent->key() == Qt::Key_Control)
        {
    
    
            m_mutilSelection = false;
        }
    }
	//同理,必须让事件循环继续,否则在这里就中断了
    return QWidget::eventFilter(watched, event);

方法3、使用windows钩子,只要程序在跑,都能触发相应功能

举个最容易理解的例子,我们最常见到的游戏辅助工具,F1实现透视,F2实现XXX等,那么你如果要你的程序获取焦点再去响应,那游戏窗口的焦点就失去了。这时候你游戏就操作不了。所以我们要通过windows的消息循环来实现

使用钩子时,有一个规范,那就是我们调用完钩子处理函数后,需要调用CallNextHookEx函数让事件循环继续下去,这和上面那两个方法道理也是一样的。

这边博主实现了一个钩子的示例,还是监听Ctrl键的示例

class WinKeyHook
{
    
    
public:
	WinKeyHook();	
	~WinKeyHook();
public:
	//钩子函数,处理键盘事件
	static LRESULT CALLBACK keyHookEvent(int nCode, WPARAM wParam,
	 LPARAM lParam);
	 
	//设置回调函数,可以从外部设置,这样keyHookEvent处理的时候可以执行这个回调
	void SetKeyPressCallBack( std::function<void(int)> func);
	void SetKeyReleaseCallBack( std::function<void(int)> func);
private:
	static HHOOK keyborard_hook_;//hook对象
	static std::function<void(int)> m_pressFunc;//按下的回调,int代表键码
	static std::function<void(int)> m_releaseFunc;//抬起的回调,int代表键码
};

cpp

//静态变量类外初始化
HHOOK CustomTreeKeyHook:: keyborard_hook_ = NULL;
std::function<void(int)> CustomTreeKeyHook::m_pressFunc = NULL;
std::function<void(int)> CustomTreeKeyHook::m_releaseFunc = NULL;
WinKeyHook::WinKeyHook()
{
    
    
	 Q_ASSERT(!keyborard_hook_);
	 //构造函数里把keyHookEvent函数的地址当钩子写入到windows消息循环
	 keyborard_hook_ = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)keyHookEvent, GetModuleHandle(NULL), 0);
}
	
WinKeyHook::~WinKeyHook()
{
    
    
	//析构函数中释放这个钩子
	if (nullptr != keyborard_hook_) 
	{
    
    
	UnhookWindowsHookEx(keyborard_hook_);
	}
}

void WinKeyHook::SetKeyPressCallBack(const std::function<void(int)> func)
{
    
    
	m_pressFunc=func;
}

void WinKeyHook::SetKeyReleaseCallBack(const std::function<void(int)> func)
{
    
    
	m_releaseFunc=func;
}

LRESULT CALLBACK WinKeyHook::keyHookEvent(int code, WPARAM wParam, LPARAM lParam)
{
    
    
	if (code < 0)
		return CallNextHookEx(keyborard_hook_, code, wParam, lParam);
	if (wParam == WM_KEYDOWN)
	{
    
    
		//用户按下了键盘按键
		m_pressFunc(((KBDLLHOOKSTRUCT *)lParam)->vkCode);
	}else if(wParam == WM_KEYUP)
	{
    
    
		//用户抬起了键盘按键
		m_releaseFunc(((KBDLLHOOKSTRUCT *)lParam)->vkCode);
	}
	return CallNextHookEx(keyborard_hook_, code, wParam, lParam);
}

然后我们可以这样使用,然后就在CtrlEvent里处理就好,判断键码是否为ctrl或者其他的键,去进行一些操作,键码已经当参数传出来了

 WinKeyHook* ctrl=new WinKeyHook;
//TestWidget指的就是上面我们两种方法的控件界面哈
ctrl.SetKeyPressCallBack(std::bind(&TestWidget::CtrlPressEvent, this, std::placeholders::_1));
ctrl.SetKeyReleaseCallBack(std::bind(&TestWidget::CtrlReleaseEvent, this, std::placeholders::_1));

void TestWidget::CtrlPressEvent(int keyCode)
{
    
    
	if(keyCode==VK_CONTROL)//VK_CONTROL是windows的CTRL键码
	{
    
    
		m_mutilSelection = true;
	}
}

void TestWidget::CtrlReleaseEvent(int keyCode)
{
    
    
	if(keyCode==VK_CONTROL)//VK_CONTROL是windows的CTRL键码
	{
    
    
		m_mutilSelection = false;
	}
}

钩子的办法就比较通用了。只要是C++写的界面都可以参照这套。读者自己拓展哈

猜你喜欢

转载自blog.csdn.net/qq_40861091/article/details/120349159
今日推荐