QT保持窗口宽高比

需求

由于窗口功能特殊,需要保持窗口的宽高比不变。即在调整宽度的时候同时自动调整宽度,同理在调整宽度的时候同时自动调整高度。

资料收集

  1. resizeEvent()
    在QWidget中当窗口大小改变之后会触发resizeEvent事件。
  2. setHeightForWidth()
    非顶级窗口可用,设置此属性后,在窗口的宽度发生更改之后会自动调用heightForWidth函数来获取新的高度。
  3. WM_SIZING
    MFC中的一个消息,指示窗口的大小即将改变。

分析整理

首先,resizeEvent()事件是在窗口大小发生改变之后才触发的,而且在代码中使用resize()函数来设置窗口的大小同样会触发此事件。
因此如果resizeEvent()触发的慢的话应该可以看到窗口跳动,而稍微处理不好的话很有可能导致循环。
其次,setHeightForWidth()据网上的资料说此方法只能用于非顶级窗口。所以没法用。
那么就只剩下WM_SIZING这条思路了。
因为QT和MFC是可以同时使用的,也就是说QT和MFC兼容性还可以。以前使用MFC的方式给QT窗口发送过消息,那么QT中是否有和WM_SIZING相对应的方法或事件?

开始行动

  1. 查文档
    首先在已经实现的部分事件相应函数中没有找到。而在QEvent::Type的说明中也没有找到相应的事件类型。
  2. 试一试
    在文档中没有明确找到的话。就只能试了。重载bool event(QEvent *event);函数然后调试看看在窗口尺寸改变之前我们会收到哪些消息。

首先,重载event()函数

bool XPianoKeyboard::event(QEvent * event)
{
	qDebug() << "got event type = " << event->type();
	return false;
}

来看看输出结果:

got event type =  QEvent::NonClientAreaMouseMove
got event type =  QEvent::NonClientAreaMouseButtonPress
got event type =  QEvent::Resize
got event type =  QEvent::Paint
got event type =  QEvent::NonClientAreaMouseButtonRelease
got event type =  QEvent::NonClientAreaMouseMove
got event type =  QEvent::NonClientAreaMouseMove

从输出结果中可以看出,当鼠标按下之后就只有resize事件了。所以说,貌似此路不通。

曲线救国

既然没有类似WM_SIZING的消息,那就只能进行模拟了。即禁用窗口的调整大小功能,然后自己去实现这个功能。

初步思路

  1. 禁用窗口的调整大小功能。
    通过setFixedSize()函数来设置窗口的固定大小,这时系统不再提供窗口的调整大小功能。
  2. QEvent::MouseMove消息中修改鼠标样式,让窗口看起来还具有调整大小功能。
  3. 在收到QEvent::MouseButtonPress消息后捕获鼠标。
    这样无论鼠标移动到哪里窗口都能收到鼠标移动的消息(QEvent::MouseMove)。
  4. 在收到QEvent::MouseMove时计算并调整窗口大小。
  5. 在收到QEvent::MouseButtonRelease,消息时释放鼠标。

关键代码

根据以上思路关键代码如下

/*********消息处理函数**********/

bool XAspectRatioCtl::eventFilter(QObject * watched, QEvent * event)
{
	if (parent() != watched)
	{
		return false;
	}
	QWidget* pw = static_cast(watched);
	if (nullptr == pw)
	{
		return false;
	}
	switch (event->type())
	{
	case QEvent::MouseButtonPress:
	{
		//鼠标点击,进入调整大小模式
		QMouseEvent* me = static_cast(event);
		if (m_RT != RT_Unknow)
		{
			//记录一些信息
			m_oldSize = pw->size();
			m_oldPos = pw->frameGeometry().topLeft();
			pw->grabMouse();
			//pw->setFixedSize(m_oldSize);
			QMouseEvent* me = static_cast(event);
			m_oldPoint = me->globalPos();
			//捕获鼠标
			m_bSizing = true;
		}
	}
	case QEvent::HoverMove:
		//QMainWindow 类型的窗口收到的是这个消息,而不是MouseMove
		//但是这个消息只有在鼠标未点击的时候能收到
		if (!m_bSizing)
		{
			QHoverEvent* me = static_cast(event);
			//是否是要横向调整大小
			bool bhor = me->pos().x() > m_oldSize.width() - 5 || me->pos().x() < 5;
			//是否是要纵向调整大小
			bool bver = me->pos().y() > m_oldSize.height() - 5;

			if (bhor && bver)
				if (me->pos().x() < 5)
				{
					pw->setCursor(Qt::SizeBDiagCursor);
					m_RT = RT_BottomLeft;
				}
				else
				{
					pw->setCursor(Qt::SizeFDiagCursor);
					m_RT = RT_BottomRight;
				}
			else if (bhor)
			{
				pw->setCursor(Qt::SizeHorCursor);
				if (me->pos().x() < 5)
					m_RT = RT_Left;
				else
					m_RT = RT_Right;
			}
			else if (bver)
			{
				pw->setCursor(Qt::SizeVerCursor);
				m_RT = RT_Bottom;
			}
			else
			{
				//鼠标在窗口内移动,重置状态,还原鼠标样式
				m_RT = RT_Unknow;
				pw->unsetCursor();
			}
		}
		break;
	case QEvent::MouseMove:
	{
		//捕获鼠标之后收到的消息,在这里调整窗口大小
		if (m_bSizing)
		{
			//QRect r = frameGeometry();
			QMouseEvent* me = static_cast(event);
			QPoint p = me->globalPos();
			ChangeSize(pw,p);
		}
		else
		{
			QMouseEvent* me = static_cast(event);
			//是否是要横向调整大小
			bool bhor = me->pos().x() > m_oldSize.width() - 5 || me->pos().x() < 5;
			//是否是要纵向调整大小
			bool bver = me->pos().y() > m_oldSize.height() - 5;

			if (bhor && bver)
				if (me->pos().x() < 5)
				{
					pw->setCursor(Qt::SizeBDiagCursor);
					m_RT = RT_BottomLeft;
				}
				else
				{
					pw->setCursor(Qt::SizeFDiagCursor);
					m_RT = RT_BottomRight;
				}
			else if (bhor)
			{
				pw->setCursor(Qt::SizeHorCursor);
				if (me->pos().x() < 5)
					m_RT = RT_Left;
				else
					m_RT = RT_Right;
			}
			else if (bver)
			{
				pw->setCursor(Qt::SizeVerCursor);
				m_RT = RT_Bottom;
			}
			else
			{
				//鼠标在窗口内移动,重置状态,还原鼠标样式
				m_RT = RT_Unknow;
				pw->unsetCursor();
			}
		}
	}
	break;
	case QEvent::MouseButtonRelease:
	{
		//退出鼠标调整模式
		m_oldSize = pw->size();
		pw->releaseMouse();
		m_bSizing = false;
		//pw->setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
	}
	break;
	case QEvent::Show:
		//如果窗口在实例创建之后才显示出来,
		//在此处获取窗口尺寸和最小尺寸
		m_oldSize = pw->frameGeometry().size();
		m_minSize = pw->minimumSizeHint();
		//设置固定大小,这样窗口不再可以调整大小
		pw->setFixedSize(m_oldSize);
		m_AspectRatio = 1.0 * m_oldSize.width() / m_oldSize.height();
		break;
	default:
		qDebug() << event->type();
		break;
	}
	return false;
}

/*********尺寸计算函数*********/

void XAspectRatioCtl::ChangeSize(QWidget * pw, QPoint np)
{
	QPoint cp = np - m_oldPoint;
	QSize newSize;
	QPoint newPos = {0,0};		//当从左边调整窗口大小时还需要移动窗口

	switch (m_RT)
	{
	case XAspectRatioCtl::RT_Left:
		//从左边调整大小,直接设置宽度值,然后根据宽度值计算高度值
		newSize.setWidth(m_oldSize.width() - cp.x());
		newSize.setHeight(newSize.width() / m_AspectRatio);
		newPos.setY(m_oldPos.y());
		newPos.setX(np.x());
		break;
	case XAspectRatioCtl::RT_Right:
		//从右边调整大小,直接设置宽度值,然后根据宽度值计算高度值
		newSize.setWidth(m_oldSize.width() + cp.x());
		newSize.setHeight(newSize.width() / m_AspectRatio);
		break;
	case XAspectRatioCtl::RT_Bottom:
		//从下方调整大小,直接设置高度值,然后根据高度值计算宽度值
		newSize.setHeight(m_oldSize.height() + cp.y());
		newSize.setWidth(newSize.height() * m_AspectRatio);
		break;
	case XAspectRatioCtl::RT_BottomLeft:
	{
		//先计算宽度和高度值,
		int nh = m_oldSize.height() + cp.y();
		int nw = m_oldSize.width() - cp.x();
		//取较小尺寸值,然后计算确切尺寸
		if (nw > nh * m_AspectRatio)
		{
			newSize.setHeight(nh);
			newSize.setWidth(nh*m_AspectRatio);
		}
		else
		{
			newSize.setWidth(nw);
			newSize.setHeight(nw / m_AspectRatio);
		}
		newPos.setY(m_oldPos.y());
		//根据调整后的尺寸和调整前的尺寸来计算x轴移动位置
		newPos.setX(m_oldPos.x() - (newSize.width() - m_oldSize.width()));
	}
		break;
	case XAspectRatioCtl::RT_BottomRight:
	{
		int nh = m_oldSize.height() + cp.y();
		int nw = m_oldSize.width() + cp.x();
		if (nw > nh * m_AspectRatio)
		{
			newSize.setHeight(nh);
			newSize.setWidth(nh*m_AspectRatio);
		}
		else
		{
			newSize.setWidth(nw);
			newSize.setHeight(nw / m_AspectRatio);
		}
	}
		break;
	default:
		break;
	}
	//如果调整后的大小小于最小允许的大小,则不进行调整
	if (newSize.height() < m_minSize.height() || newSize.width() < m_minSize.width())
	{
		return;
	}
	pw->setFixedSize(newSize);
	if (newPos != QPoint(0,0))
	{
		pw->move(newPos);
	}
}

成品

最后根据以上思路。写了一个控制类XAspectRatioCtl在初始化该类实例的时候传入QWidgetQMainWindow指针,则被监控的窗口在初次显示后即可保持其宽高比。
具体代码见附件:https://download.csdn.net/download/u014410266/12470183。

环境

vs2017 + QT 5.12.5

你可能感兴趣的:(QT)