需求
由于窗口功能特殊,需要保持窗口的宽高比不变。即在调整宽度的时候同时自动调整宽度,同理在调整宽度的时候同时自动调整高度。
资料收集
resizeEvent()
在QWidget中当窗口大小改变之后会触发resizeEvent
事件。setHeightForWidth()
非顶级窗口可用,设置此属性后,在窗口的宽度发生更改之后会自动调用heightForWidth
函数来获取新的高度。WM_SIZING
MFC中的一个消息,指示窗口的大小即将改变。
分析整理
首先,resizeEvent()
事件是在窗口大小发生改变之后才触发的,而且在代码中使用resize()
函数来设置窗口的大小同样会触发此事件。
因此如果resizeEvent()
触发的慢的话应该可以看到窗口跳动,而稍微处理不好的话很有可能导致循环。
其次,setHeightForWidth()
据网上的资料说此方法只能用于非顶级窗口。所以没法用。
那么就只剩下WM_SIZING
这条思路了。
因为QT和MFC是可以同时使用的,也就是说QT和MFC兼容性还可以。以前使用MFC的方式给QT窗口发送过消息,那么QT中是否有和WM_SIZING
相对应的方法或事件?
开始行动
- 查文档
首先在已经实现的部分事件相应函数中没有找到。而在QEvent::Type
的说明中也没有找到相应的事件类型。 - 试一试
在文档中没有明确找到的话。就只能试了。重载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
的消息,那就只能进行模拟了。即禁用窗口的调整大小功能,然后自己去实现这个功能。
初步思路
- 禁用窗口的调整大小功能。
通过setFixedSize()
函数来设置窗口的固定大小,这时系统不再提供窗口的调整大小功能。 - 在
QEvent::MouseMove
消息中修改鼠标样式,让窗口看起来还具有调整大小功能。 - 在收到
QEvent::MouseButtonPress
消息后捕获鼠标。
这样无论鼠标移动到哪里窗口都能收到鼠标移动的消息(QEvent::MouseMove
)。 - 在收到
QEvent::MouseMove
时计算并调整窗口大小。 - 在收到
QEvent::MouseButtonRelease
,消息时释放鼠标。
关键代码
根据以上思路关键代码如下
/*********消息处理函数**********/
bool XAspectRatioCtl::eventFilter(QObject * watched, QEvent * event)
{
if (parent() != watched)
{
return false;
}
QWidget* pw = static_cast<QWidget*>(watched);
if (nullptr == pw)
{
return false;
}
switch (event->type())
{
case QEvent::MouseButtonPress:
{
//鼠标点击,进入调整大小模式
QMouseEvent* me = static_cast<QMouseEvent*>(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<QMouseEvent*>(event);
m_oldPoint = me->globalPos();
//捕获鼠标
m_bSizing = true;
}
}
case QEvent::HoverMove:
//QMainWindow 类型的窗口收到的是这个消息,而不是MouseMove
//但是这个消息只有在鼠标未点击的时候能收到
if (!m_bSizing)
{
QHoverEvent* me = static_cast<QHoverEvent*>(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<QMouseEvent*>(event);
QPoint p = me->globalPos();
ChangeSize(pw,p);
}
else
{
QMouseEvent* me = static_cast<QMouseEvent*>(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
在初始化该类实例的时候传入QWidget
或QMainWindow
指针,则被监控的窗口在初次显示后即可保持其宽高比。
具体代码见附件:https://download.csdn.net/download/u014410266/12470183。
环境
vs2017 + QT 5.12.5