现在的位置: 首页 > 综合 > 正文

Qt控制无边框窗体的拖动与缩放

2014年09月05日 ⁄ 综合 ⁄ 共 6884字 ⁄ 字号 评论关闭

部门启动了一个项目,用Qt做界面,美工毫无意外地又把界面设计得花里胡哨。由于对QML不熟,只好再次祭出QSS,并辅以QPainter的绘制。这里碰到一个问题,要做得和效果图一样,必须去掉边框(Qt::FramelessWindowHint属性),于是只好自己处理窗体的鼠标事件,控制拖动和缩放。之前做过类似的事情,但是实现的不好,因为要把大量的代码塞到各种鼠标事件函数里,和类里其他的事务代码掺杂在一起,很难看。这次想了想,分离出一个代理类,利用installEventFilter把窗体事件挂到这个代理类中实现,大大解耦了。


先简单讲下原理。拖动很简单,此处略过。至于缩放,我的做法是,把窗体区域划分成一个九宫格(如下图所示),鼠标在区域1、区域5拖拽改变窗体高度,在区域3、区域7拖拽改变宽度,2、4、6、8同时改变宽高,这个也不用多说了,还是看代码吧。

DragProxy.h

#ifndef DRAGPROXY_H
#define DRAGPROXY_H

#include <QObject>
#include <QWidget>
#include <QEvent>
#include <QRect>
#include <QPoint>

class DragProxy : public QObject
{
	Q_OBJECT

public:
	DragProxy(QWidget *parent);
	~DragProxy();

protected:
	enum WidgetRegion
	{
		Top = 0,
		TopRight,
		Right,
		RightBottom,
		Bottom,
		LeftBottom,
		Left,
		LeftTop,
		Inner,
		Unknown
	};

public:
	// 设置四周边框宽度
	void SetBorderWidth(int top, int right, int bottom, int left);

protected:
	virtual bool eventFilter(QObject* obj, QEvent* event);

	void MakeRegions();
	WidgetRegion HitTest(const QPoint& pos);
	void UpdateGeometry(int x, int y, int w, int h);

	// 鼠标从边框快速移到窗体内子控件上,可能会造成鼠标样式未改变,这里使用计时器监控
	void StartCursorTimer();
	void StopCursorTimer();

private:
	QWidget* m_proxyWidget;		// 代理的窗体
	int m_top, m_right, m_bottom, m_left;	// 四周宽度
	QRect m_regions[9];		// 九宫格,对应9个区域

	QPoint m_originPosGlobal;	// 拖拽前鼠标位置
	QRect m_originGeo;			// 拖拽前窗体位置和大小

	bool m_mousePressed;		// 鼠标是否按下
	WidgetRegion m_regionPressed;	// 记录鼠标按下时所点击的区域

	int m_cursorTimerId;
};

#endif // DRAGPROXY_H

DragProxy.cpp

#include "DragProxy.h"
#include <QMouseEvent>
#include <QTimerEvent>
#include <QCursor>

DragProxy::DragProxy(QWidget *parent)
	: QObject((QObject*)parent)
{
	m_proxyWidget = parent;
	m_top = m_right = m_bottom = m_left = 0;

	m_proxyWidget->setMouseTracking(true);
	m_proxyWidget->installEventFilter(this);	// 代理窗体事件

	m_mousePressed = false;
	m_regionPressed = Unknown;

	m_cursorTimerId = 0;
}

DragProxy::~DragProxy()
{
}

void DragProxy::SetBorderWidth(int top, int right, int bottom, int left)
{
	m_top = top;
	m_right = right;
	m_bottom = bottom;
	m_left = left;

	MakeRegions();
}

void DragProxy::UpdateGeometry(int x, int y, int w, int h)
{
	int minWidth = m_proxyWidget->minimumWidth();
	int minHeight = m_proxyWidget->minimumHeight();
	int maxWidth = m_proxyWidget->maximumWidth();
	int maxHeight = m_proxyWidget->maximumHeight();

	if (w < minWidth || w > maxWidth || h < minHeight || h > maxHeight)
	{
		return;
	}

	m_proxyWidget->setGeometry(x, y, w, h);
}

bool DragProxy::eventFilter(QObject* obj, QEvent* event)
{
	QEvent::Type eventType = event->type();
	if (eventType == QEvent::MouseMove)
	{
		QMouseEvent* mouseEvent = (QMouseEvent*)event;
		QPoint curPosLocal = mouseEvent->pos();
		DragProxy::WidgetRegion regionType = HitTest(curPosLocal);

		QPoint curPosGlobal = m_proxyWidget->mapToGlobal(curPosLocal);

		if (!m_mousePressed)	// 鼠标未按下
		{
			switch (regionType)
			{
			case Top:
			case Bottom:
				m_proxyWidget->setCursor(Qt::SizeVerCursor);
				break;
			case TopRight:
			case LeftBottom:
				m_proxyWidget->setCursor(Qt::SizeBDiagCursor);
				break;
			case Right:
			case Left:
				m_proxyWidget->setCursor(Qt::SizeHorCursor);
				break;
			case RightBottom:
			case LeftTop:
				m_proxyWidget->setCursor(Qt::SizeFDiagCursor);
				break;
			default:
				m_proxyWidget->setCursor(Qt::ArrowCursor);
				break;
			}

			StartCursorTimer();
		}
		else	// 鼠标已按下
		{
			QRect geo = m_proxyWidget->geometry();

			if (m_regionPressed == Inner)	
			{
				m_proxyWidget->move(m_originGeo.topLeft() + curPosGlobal - m_originPosGlobal);
			}
			else if (m_regionPressed == Top)
			{
				int dY = curPosGlobal.y() - m_originPosGlobal.y();
				UpdateGeometry(m_originGeo.x(), m_originGeo.y() + dY, m_originGeo.width(), m_originGeo.height() - dY);
			}
			else if (m_regionPressed == TopRight)
			{
				QPoint dXY = curPosGlobal - m_originPosGlobal;
				UpdateGeometry(m_originGeo.x(), m_originGeo.y() + dXY.y(), m_originGeo.width() + dXY.x(), m_originGeo.height() - dXY.y());
			}
			else if (m_regionPressed == Right)
			{
				int dX = curPosGlobal.x() - m_originPosGlobal.x();
				UpdateGeometry(m_originGeo.x(), m_originGeo.y(), m_originGeo.width() + dX, m_originGeo.height());
			}
			else if (m_regionPressed == RightBottom)
			{
				QPoint dXY = curPosGlobal - m_originPosGlobal;
				UpdateGeometry(m_originGeo.x(), m_originGeo.y(), m_originGeo.width() + dXY.x(), m_originGeo.height() + dXY.y());
			}
			else if (m_regionPressed == Bottom)
			{
				int dY = curPosGlobal.y() - m_originPosGlobal.y();
				UpdateGeometry(m_originGeo.x(), m_originGeo.y(), m_originGeo.width(), m_originGeo.height() + dY);
			}
			else if (m_regionPressed == LeftBottom)
			{
				QPoint dXY = curPosGlobal - m_originPosGlobal;
				UpdateGeometry(m_originGeo.x() + dXY.x(), m_originGeo.y(), m_originGeo.width() - dXY.x(), m_originGeo.height() + dXY.y());
			}
			else if (m_regionPressed == Left)
			{
				int dX = curPosGlobal.x() - m_originPosGlobal.x();
				UpdateGeometry(m_originGeo.x() + dX, m_originGeo.y(), m_originGeo.width() - dX, m_originGeo.height());
			}
			else if (m_regionPressed == LeftTop)
			{
				QPoint dXY = curPosGlobal - m_originPosGlobal;
				UpdateGeometry(m_originGeo.x() + dXY.x(), m_originGeo.y() + dXY.y(), m_originGeo.width() - dXY.x(), m_originGeo.height() - dXY.y());
			}
		}
	}
	else if (eventType == QEvent::MouseButtonPress)
	{
		QMouseEvent* mouseEvent = (QMouseEvent*)event;
		if (mouseEvent->button() == Qt::LeftButton)
		{
			m_mousePressed = true;

			QPoint curPos = mouseEvent->pos();
			m_regionPressed = HitTest(curPos);

			m_originPosGlobal = m_proxyWidget->mapToGlobal(curPos);
			m_originGeo = m_proxyWidget->geometry();

			StopCursorTimer();
		}
	}
	else if (eventType == QEvent::MouseButtonRelease)
	{
		m_mousePressed = false;
		m_regionPressed = Unknown;

		m_proxyWidget->setCursor(Qt::ArrowCursor);
	}
	else if (eventType == QEvent::Resize)
	{
		MakeRegions();
	}
	else if (eventType == QEvent::Leave)
	{
		m_proxyWidget->setCursor(Qt::ArrowCursor);
		StopCursorTimer();
	}
	else if (eventType == QEvent::Timer)
	{
		QTimerEvent* timerEvent = (QTimerEvent*)event;
		if (timerEvent->timerId() == m_cursorTimerId)
		{
			if (m_regions[Inner].contains(m_proxyWidget->mapFromGlobal(QCursor::pos())))
			{
				m_proxyWidget->setCursor(Qt::ArrowCursor);
				StopCursorTimer();
			}
		}
	}

	return QObject::eventFilter(obj, event);
}

void DragProxy::StartCursorTimer()
{
	StopCursorTimer();
	m_cursorTimerId = m_proxyWidget->startTimer(50);
}

void DragProxy::StopCursorTimer()
{
	if (m_cursorTimerId != 0)
	{
		m_proxyWidget->killTimer(m_cursorTimerId);
		m_cursorTimerId = 0;
	}
}

void DragProxy::MakeRegions()
{
	int width = m_proxyWidget->width();
	int height = m_proxyWidget->height();

	m_regions[Top]			= QRect(m_left, 0, width - m_left - m_right, m_top);
	m_regions[TopRight]		= QRect(width - m_right, 0, m_right, m_top);
	m_regions[Right]		= QRect(width - m_right, m_top, m_right, height - m_top - m_bottom);
	m_regions[RightBottom]	= QRect(width - m_right, height - m_bottom, m_right, m_bottom);
	m_regions[Bottom]		= QRect(m_left, height - m_bottom, width - m_left - m_right, m_bottom);
	m_regions[LeftBottom]	= QRect(0, height - m_bottom, m_left, m_bottom);
	m_regions[Left]			= QRect(0, m_top, m_left, height - m_top - m_bottom);
	m_regions[LeftTop]		= QRect(0, 0, m_left, m_top);
	m_regions[Inner]		= QRect(m_left, m_top, width - m_left - m_right, height - m_top - m_bottom);
}

DragProxy::WidgetRegion DragProxy::HitTest(const QPoint& pos)
{
	for (int i = 0; i < 9; i++)
	{
		const QRect rect = m_regions[i];
		if (rect.contains(pos))
		{
			return DragProxy::WidgetRegion(i);
		}
	}

	return Unknown;
}



引入那个计时器,是为了解决一个难缠的鼠标样式问题。我发现,如果从鼠标从窗体边框快速移到窗体边缘的一个子控件上,而此控件又没有处理鼠标样式,鼠标样式不会恢复成默认的箭头。所以我只好很别扭地使用了一个计时器,发现鼠标已不在窗体边缘时,将鼠标样式重置为箭头,同时停止计时器。这种方法显然很生硬,如果有更好方案请赐教。使用代码如下:

FramelessDialog::FramelessDialog(QWidget *parent)
	: QDialog(parent)
{
	ui.setupUi(this);
	setWindowFlags(Qt::FramelessWindowHint);

	setMinimumSize(50, 50);
	setMaximumSize(800, 800);

	DragProxy* dragProxy = new DragProxy(this);
	dragProxy->SetBorderWidth(8, 8, 8, 8);
}

刚录了个gif,可以看动态效果,不知道为什么上传上来就显示成静止图片了,于是作罢,各位有兴趣自己试验下。


抱歉!评论已关闭.