目录
一, 概述
二, 效果展示
三, demo 制作
1, 设计窗体
2, 双击放大
四, 拖拽
一, 概述
用 Qt 进行开发界面时, 既想要实现友好的用户交互又想界面漂亮, 那么自定义界面就必不可少. 其中有一个操作就是是我们每一个 Qter 开发者都要会的, 而且是经常进行的.
Qt::FramelessWindowHint 这个属性想必大家都使用过, 有些同学可能对这个属性很了解, 也用的是炉火纯青, 今天我们也来说说这个属性.
关于这个无边框属性网上也有一些文章, 有些谈论的是 bug, 当然了这是针对不同 os 而言, 也有些是跟其他第三方库混合使用时的问题. 可是问题归问题, 想要实现自定义的优秀界面这个属性也是必不可少的.
今天我们就来实现一个无边框窗体最大化时, 支持拖拽标题栏进行还原的功能.
无边框窗体支持缩放, 移动这些不属于本篇文章的内容, 本篇文章主要讲解怎么实现最大化时拖拽标题栏进行还原窗体, 本篇文章的代码依赖于博主之前封装的一个拖拽代理类.
二, 效果展示
如效果图所示, 做了一个简单的事例, 双击标题栏窗体最大化, 这个时候如果进行标题栏拖拽, 当鼠标按下并移动一段距离时窗体恢复 normal 状态.
恢复 normal 状态下的窗体仍然支持放大和缩小, 有接口可以设置.
三, demo 制作
demo 的制作过程还是比较简单的, 分为如下几步
1, 设计窗体
通过 desinger 设计器我们拖拽了一个大致窗体内容, 为了更好的展示效果, 标题栏加上了 icon 和背景色
2, 双击放大
鼠标双击标题栏放大这个功能实现起来方法也比较多, 这里博主选择了代码量最少并且实现起来最简单的方式, 直接把标题栏的事件循环安装到了主窗体上.
ui.widget->installEventFilter(this);
接下来我们就需要重写主窗口的 eventFilter 函数即可
- bool DragWidget::eventFilter(QObject * watched, QEvent * event)
- {
- if (watched == ui.widget)
- {
- if (event->type() == QEvent::MouseButtonDblClick)
- {
- if (isMaximized())
- {
- showNormal();
- m_handler.setWidgetResizable(true);
- m_handler.setWidgetMovable(true);
- }
- else
- {
- showMaximized();
- m_handler.setWidgetResizable(false);
- m_handler.setWidgetMovable(false);
- }
- }
- }
- return QWidget::eventFilter(watched, event);
- }
细心的同学就会发现代码里有一个 m_handler 变量, 这个类就是博主之前自己封装的一个拖拽代理, 通过接口可以设置被代理的窗体, 并设置需要代理哪些行为.
本篇文章中所演示的事例代码, 我们代理了主窗口上标题栏部分的移动事件和整个窗体的缩放事件, 设置代码如下所示
- m_handler.activateOn(this);
- m_handler.useLocalMoveabled(true);
- m_handler.addLocalWidget(ui.widget);
- m_handler.setMaximumMove(true, true);
拖拽代理类内容比较多, 本篇文章暂不讲解.
四, 拖拽
为了更好的理解本篇文章, 这里需要把拖拽代理类的头文件放出来, 这样更有利于大家理解.
接口都比较简单, 代码中也有注释, 大家自行阅读.
- class WidgetResizeHandler : public QObject
- {
- public:
- explicit WidgetResizeHandler(QObject* parent = 0);
- ~WidgetResizeHandler();
- public:
- void activateOn(QWidget * topLevelWidget);// 添加 topLevelWidget 事件代理
- void removeFrom(QWidget * topLevelWidget);// 移除 topLevelWidget 事件代理
- Qt::CursorShape CursorShape(QWidget * widget);
- // 窗口移动 default:true
- void setWidgetMovable(bool movable);
- bool isWidgetMovable();
- // 大小可变 default:true
- void setWidgetResizable(bool resizable);
- bool isWidgetResizable();
- // 橡胶式窗口移动 default:false
- void useRubberBandOnMove(bool use);
- bool isUsingRubberBandOnMove();
- // 橡胶式修改大小 default:false
- void useRubberBandOnResize(bool use);
- bool isUsingRubberBandOnResisze();
- void setBorderWidth(int newBorderWidth);
- int borderWidth();
- // 局部可移动
- void useLocalMoveabled(bool use);
- void addLocalWidget(QWidget *);
- // 最大化时支持拖拽 参数 2 表示是否可放大缩小
- void setMaximumMove(bool move, bool resize = false);
- protected:
- virtual bool eventFilter(QObject * obj, QEvent * event) Q_DECL_OVERRIDE;
- private:
- WidgetResizeHandlerImpl * d_ptr;
- };
值得注意的是最后一个 setMaximumMove 接口, 他就是我们今天的猪脚 - 是否支持最大化时拖拽. 当我们设置了这个接口后, 窗体最大化时也就能进行拖拽, 并还原到之前的 normal 状态.
文章第三小节讲解 demo 时, 说过主窗体已经被代理拖拽类进行了事件代理, 那么主窗体的所有事件首先都会传递给这个代理类, 这里我们需要重点关注下鼠标按下时移动事件.
- void WidgetData::handleMouseMoveEvent(QMouseEvent* event)
- {
- if (mLeftButtonPressed)
- {
- if (d_ptr->mWidgetResizable && mPressedMousePos.onEdges)
- {
- resizeWidget(event->globalPos());
- }
- else if (d_ptr->mWidgetMovable)
- {
- moveWidget(event->globalPos());
- }
- else if (d_ptr->mMaxMovable)
- {
- if (mWidget->isMaximized() && TryMoveWidget(event))
- {
- d_ptr->mWidgetMovable = true;
- //d_ptr->mWidgetResizable = true;
- }
- }
- }
- else if (d_ptr->mWidgetResizable)
- {
- updateCursorShape(event->globalPos());
- }
- }
这段代码包含有其他缩放窗体和正常移动的逻辑, 最大化时支持移动的逻辑应该不难找木九十 TryMoveWidget 这个函数, 该函数中我们进行了充分的逻辑判断, 一旦触发了窗体移动, 那么我们把 mWidgetMovable 变量置为 true, 下一次鼠标按下移动事件就会触发正常的拖拽逻辑.
仔细思考上边一段话, 其中有 2 个关键信息
触发窗体移动, 并还原到之前的 normal 状态
进行了第一步后, 需要把 mWidgetMovable 变量置为 true, 之后走正常的窗体移动流程
窗体移动
尝试移动窗体, 当鼠标当前位置距离鼠标按下时的距离大于 20px 时, 进行窗体还原操作, 并返回 true, 代表窗体已经被重置到 normal 态.
- bool WidgetData::TryMoveWidget(QMouseEvent* event)
- {
- QPoint distance = event->globalPos() - mDragPos;
- int length = distance.manhattanLength();
- if (length> 20)
- {
- QRect rect = mWidget->normalGeometry();
- int desX = mDragPos.x() * rect.width() / mWidget->geometry().width();
- int desY = mDragPos.y();
- rect.moveTopLeft(event->globalPos() - QPoint(desX, desY));
- mWidget->showNormal();
- mWidget->setGeometry(rect);
- mDragPos = QPoint(desX, desY);
- mIsMaxMove = true;
- return true;
- }
- return false;
- }
上述代码中的 mIsMaxMove 标识是为了在一次窗体还原操作后, 释放鼠标时可以正常的设置缩放标识而设.
有了上述代码之后, 窗体就能还原到最大化之前的大小, 并且为之也移动到了鼠标相应的位置, 关于这个新位置的计算这里需要说明下.
x 坐标
x 轴坐标使用了比例计算方式. 窗体全屏时鼠标按下的位置在窗体上的位置在窗体还原后依然保持不变, 这样计算比较简单而且不会出错, 保证窗体还原后, 鼠标会一直在标题栏内.
如果需要优化 x 轴坐标的计算方法, 只需要重新计算上述代码中的 desX 值即可.
y 坐标
y 轴坐标这里没有做特殊处理. 因为窗体还原时, 标题栏的高度是没有发生变化的, 因此这里不需要做特殊处理.
讲到这里, 本篇文章的主要内容基本完成, 关于代理拖拽类, 不属于本篇文章内容, 因此就不做过多解释.
来源: https://www.cnblogs.com/swarmbees/p/11415829.html