Qt 无边框无非客户区窗口拖动、缩放改变大小、鼠标变形,windows同款,完美实现

简单点说,是在windows下实现窗口拖动和改变大小,一种方法是重载mousePressEvent系列全家福函数,然后在mouseMoveEvent中实现边移鼠标窗口就一边重绘。这种方法网上资料很多,见下:

https://www.cnblogs.com/findumars/p/5518590.html

这种方法缺点是移动的时候窗口闪烁厉害。摒弃。但该方法给了一个很好的思路。

另一种方法:

看到MFC中有调用NCHitTest方法的,这个方法是当鼠标触碰到非客户区时触发,然后调用windows api,实现窗口移动,参照下:

https://bbs.csdn.net/topics/350134227

在这篇帖子的最下方,有MFC改变窗口的方法。

于是我也想到了用Qt调windows api,反正我也不想做跨平台的软件,咳咳。然后发现了Qt有nativeEvent函数,捕获本地所有事件,重载之,专门处理鼠标移动信号。ok,实现了,但绝不完美!为何,继续看。

重载nativeEvent的方法网上查查,有第三个参数Long* result,result=HTLEFT时,鼠标就会变成横向双箭头图案,其他就参照MFC实现吧,后面会给出代码。

然后我们来看哪里不完美。Qt没有非客户区的概念,但类之间的信号传递很明显,当顶层窗口的最外层布局这样设置:

m_layoutMain->setMargin(0);

那么鼠标移到边缘时,鼠标信号的捕获是被最上层的子控件捕获了,实际上就触发不了顶层窗口的nativeEvent了。退而求其次,以为把边界设成5就可以了么?是的,不美观点,设成5确实就可以了,但如果想触发拖动事件的范围也是5,就会发现,鼠标在边界的5范围内,是横向双箭头图案,继续往里移,OK,进入子控件了,鼠标移动事件不触发了,鼠标一直是横向双箭头图案,何况,5也是接受不了的,我要0,不能忍,继续想办法。

然后就想到了过滤器,接着发现了Qt5.7的一个bug。过滤器,在顶层窗口实现nativeEventFilter方法,接着在qApp安装过滤器。

过滤器其实一点都不复杂,选个类A,在类A中实现nativeEventFilter方法后,建个对象a,只要在其他类用installNativeEventFilter(&a)方法安装过滤器,那个这个被安装过滤器的类,接收到了事件后,就会优先发给a来处理,而在qApp中安装过滤器,qApp是第一个接收到消息的,会全部先丢给a处理,a吃剩的残羹冷炙还给qApp,qApp再拿去分给其他改领盒饭的类。怎么算吃剩呢,在过滤器中return false就是吃剩不要了。然后上代码了

这是Class A中的方法,判断鼠标是否在规定区域中,PADDING自己定一个,可以在四个角加大范围,方便触发,我这里+2。

#include 
#include 
#define PADDING 5

enum Direction{
	UP = 0,
	DOWN = 1,
	LEFT,
	RIGHT,
	LEFTTOP,
	LEFTBOTTOM,
	RIGHTBOTTOM,
	RIGHTTOP,
	NONE
};

class A: public QDialog,public QAbstractNativeEventFilter
{
	Q_OBJECT

public:
	A(QWidget *parent=0);
	~A();

private:
	void mousePressEvent(QMouseEvent* event);
	void mouseDoubleClickEvent(QMouseEvent *event);
	//bool nativeEvent(const QByteArray &eventType, void *message, long *result);
	void region(const QPoint &cursorPoint,bool &activeFlag);
	bool nativeEventFilter(const QByteArray &eventType, void *message, long *result);
}


void A::region(const QPoint &cursorGlobalPoint,bool &activeFlag)
{
	QRect rect = this->rect();
	QPoint tl = mapToGlobal(rect.topLeft());
	QPoint rb = mapToGlobal(rect.bottomRight());
	int x = cursorGlobalPoint.x();
	int y = cursorGlobalPoint.y();
	activeFlag = true;
	if (tl.x() + PADDING+2 >= x && tl.x() <= x && tl.y() + PADDING+2 >= y && tl.y() <= y) {
		// 左上角
		dir = LEFTTOP;
		this->setCursor(QCursor(Qt::SizeFDiagCursor));
	}
	else if (x >= rb.x() - PADDING-2 && x <= rb.x() && y >= rb.y() - PADDING-2 && y <= rb.y()) {
		// 右下角
		dir = RIGHTBOTTOM;
		this->setCursor(QCursor(Qt::SizeFDiagCursor));
	}
	else if (x <= tl.x() + PADDING+2 && x >= tl.x() && y >= rb.y() - PADDING-2 && y <= rb.y()) {
		//左下角
		dir = LEFTBOTTOM;
		this->setCursor(QCursor(Qt::SizeBDiagCursor));
	}
	else if (x <= rb.x() && x >= rb.x() - PADDING-2 && y >= tl.y() && y <= tl.y() + PADDING+2) {
		// 右上角
		dir = RIGHTTOP;
		this->setCursor(QCursor(Qt::SizeBDiagCursor));
	}
	else if (x <= tl.x() + PADDING && x >= tl.x()) {
		// 左边
		dir = LEFT;
		this->setCursor(QCursor(Qt::SizeHorCursor));
	}
	else if (x <= rb.x() && x >= rb.x() - PADDING) {
		// 右边
		dir = RIGHT;
		this->setCursor(QCursor(Qt::SizeHorCursor));
	}
	else if (y >= tl.y() && y <= tl.y() + PADDING){
		// 上边
		dir = UP;
		this->setCursor(QCursor(Qt::SizeVerCursor));
	}
	else if (y <= rb.y() && y >= rb.y() - PADDING) {
		// 下边
		dir = DOWN;
		this->setCursor(QCursor(Qt::SizeVerCursor));
	}
	else {
		// 默认
		dir = NONE;
		this->setCursor(QCursor(Qt::ArrowCursor));
		activeFlag = false;
	}
}

bool A::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
	if (eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG")
	{
		MSG* msg = (MSG*)message;

		if (msg->message == WM_MOUSEMOVE)
		{
			QPoint pt = cursor().pos();
			bool activeFlag;
			region(pt, activeFlag);
			if (activeFlag)
			{
				switch (dir)
				{
				case UP:
				case DOWN:
					SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_SIZENS)));
					break;
				case LEFT:
				case RIGHT:
					SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_SIZEWE)));
					break;
				case LEFTTOP:
				case RIGHTBOTTOM:
					SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_SIZENWSE)));
					break;
				case RIGHTTOP:
				case LEFTBOTTOM:
					SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_SIZENESW)));
					break;
				case NONE:
				default:
					SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW)));
					return false;
					break;
				}
				return true;
			}
		}

		if (msg->message == WM_LBUTTONDOWN)
		{
			QPoint pt = cursor().pos();
			bool activeFlag;
			region(pt, activeFlag);
			if (activeFlag)
			{
				if (ReleaseCapture())
				{
					switch (dir)
					{
					case UP:
						SendMessage((HWND)this->winId(), WM_SYSCOMMAND, SC_SIZE | WMSZ_TOP, 0);
						break;
					case DOWN:
						SendMessage((HWND)this->winId(), WM_SYSCOMMAND, SC_SIZE | WMSZ_BOTTOM, 0);
						break;
					case LEFT:
						SendMessage((HWND)this->winId(), WM_SYSCOMMAND, SC_SIZE | WMSZ_LEFT, 0);
						break;
					case RIGHT:
						SendMessage((HWND)this->winId(), WM_SYSCOMMAND, SC_SIZE | WMSZ_RIGHT, 0);
						break;
					case LEFTTOP:
						SendMessage((HWND)this->winId(), WM_SYSCOMMAND, SC_SIZE | WMSZ_TOPLEFT, 0);
						break;
					case RIGHTTOP:
						SendMessage((HWND)this->winId(), WM_SYSCOMMAND, SC_SIZE | WMSZ_TOPRIGHT, 0);
						break;
					case LEFTBOTTOM:
						SendMessage((HWND)this->winId(), WM_SYSCOMMAND, SC_SIZE | WMSZ_BOTTOMLEFT, 0);
						break;
					case RIGHTBOTTOM:
						SendMessage((HWND)this->winId(), WM_SYSCOMMAND, SC_SIZE | WMSZ_BOTTOMRIGHT, 0);
						break;
					case NONE:return false;
						break;
					default:
						break;
					}
					return true;
				}

			}
		}
	}

	return false;
}


ReleaseCapture是干嘛的?windows 的api,官方说是可以捕捉到鼠标的一举一动,呵呵,我也觉得怎么就实现了呢?但真的实现了啊。

然后接下来安装过滤器:

int main(int argc, char *argv[])
{
	QApplication a(argc, argv);
	A w;
	w.show();
	a.installNativeEventFilter(&w);
	return a.exec();
}

好了,这样,不管顶层窗口w里面有什么控件,都先给w处理了,不在判定区域内,就在nativeEventFilter中丢出false,给子窗口处理,拖动效果和windows是一样的,先拖出一个虚线框,鼠标松开,重绘窗口,到此完美实现!没有连续拖动时候的闪烁问题。

接下来说说bug。

参照本帖中第二个参考贴中的例子,最开始设置鼠标图案的时候,并没有用

SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_SIZEWE)));

而是采用了 *result=HTLEFT这样,用HTLEFT后,就不需要ReleaseCapture,而可以直接调SendMessage,改一下最后一个参数,改成这样,就是最后一个参数直接传入坐标,和第二贴中一样。

SendMessage( WM_SYSCOMMAND, SC_SIZE | WMSZ_TOP, MAKELPARAM(point.x, point.y));

然后发现,qApp中传过来的result,居然为空,气炸,于是换成了SetCursor,测试通过。

顺带,还有按住标题移动,见代码

void A::mousePressEvent(QMouseEvent* event)
{
	QPoint ptMouse = event->globalPos();
	m_pointRelative = ptMouse - mapToGlobal(QPoint(0, 0));
	//点住标题则拖拽
	if (m_pointRelative.ry() < 150 )
	{
		if (ReleaseCapture())
			SendMessage((HWND)this->winId(), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
	}

	QDialog::mousePressEvent(event);
}
void A::mouseDoubleClickEvent(QMouseEvent *event)
{
        QPoint ptMouse = event->globalPos();
	m_pointRelative = ptMouse - mapToGlobal(QPoint(0, 0));
	//双击最大化
	if (m_pointRelative.ry() < 150)
	{
		if (this->windowState() == Qt::MaximumSize)
		{
			showNormal();
		}
		else
		{
			showMaximized();
		}
	}

	QDialog::mouseDoubleClickEvent(event);

}
这里也用了windows 的api,先拖出一个虚线框。心满意足,继续干活。

就是虚线框有点细,如果有人知道怎么改变虚线框粗细,请一定要告诉我!我是windows api小白。



你可能感兴趣的:(Qt 无边框无非客户区窗口拖动、缩放改变大小、鼠标变形,windows同款,完美实现)