序
有时候UI设计的风格,并不需要系统默认的标题栏;如QQ、微信电脑版等等。
这时候就需要我们实现对窗口的拖动和拉伸进行实现。
由于拖动比较简单,就此略过。
本文将以Qt做示例介绍我实现的拉伸功能,并封装成类。
当然思路是相同的,其他UI也可按此方法实现。
使用方法
秉持实用至上主义,集成使用尽可能的方便,具体操作如下:
只需要在无边框的窗体添加 CFrameForm 对象,将它叠放在窗体上就行。
例如:
#ifndef FORM_H#define FORM_H#include #include "cframeform.h" // 包含头文件namespace Ui {class Form;}class Form : public QWidget{ Q_OBJECTpublic: explicit Form(QWidget *parent = 0); ~Form();private: Ui::Form *ui; CFrameForm *m_frame; // 创建对象};#endif // FORM_H
类实现如下:
#include "form.h"#include "ui_form.h"Form::Form(QWidget *parent) : QWidget(parent), ui(new Ui::Form){ ui->setupUi(this); setWindowFlags(windowFlags() | Qt::FramelessWindowHint); m_frame = new CFrameForm(this); // 创建对象}Form::~Form(){ delete ui;}
设计思路
- 如图所示,一般鼠标移动到窗口的四个角落(黑色区域)和四个边缘(红色区域)的时候,就可以进行拖拽了;我们只要对这个几个区域进行监听处理,在不同位置时,鼠标移动到上面就处于不同的状态,这样可以给用户更好的体验。
- 当点击鼠标左键后开始,监听鼠标移动消息;
- 然后只要根据窗口和鼠标的位置,进行动态计算,确定窗口不动的角、边与拉动的角、边,便可以计算出拉伸的区域大小。
- 再然后对区域大小进行边框标记,就可以看到大小;在来回拉动的过程中实时刷新下,即可看到橡皮筋的效果了。
- 等到鼠标是否,移到了其他区域如白色区域或者窗口以外的地方,鼠标恢复箭头状态。
拖拽区域
为每个区域创建一个窗口,实现对响应鼠标的点击和释放,以及类型的管理,知道每个窗口显示什么样的光标,以及区域位置;
我们创建一个简单的区域操作类:
class CHandel : public QWidget{ Q_OBJECTpublic: CHandel(int type, CFrameForm*frm):QWidget(frm), m_type(type), m_frm(frm){} int type() {return m_type;}private: void mousePressEvent(QMouseEvent *e) { m_frm->setType(m_type); QWidget::mousePressEvent(e); } void mouseReleaseEvent(QMouseEvent *e) { m_frm->setType(9); QWidget::mouseReleaseEvent(e); }private: int m_type; CFrameForm*m_frm;};
橡皮筋
为每个边框设计一个窗口,实现4条线组成的方框,实现橡皮筋的显示效果。(问:为啥不是用一个窗口,然后绘制四边?)
为了对4条线的管理,我们创建一个简单的区域类:
class CRubberBand{public: explicit CRubberBand(); void setRect(const QRect &rect); QRect rect(); bool isVisible(); void show(); void hide(); void clear();private: QRect m_rect; QListm_lines;};
类的实现部分:
/// class CRubberBandCRubberBand::CRubberBand() // 构造4条线的属性和颜色{ for (int i = 0; i < 4; i++) { QWidget *l = new QWidget; l->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); l->setStyleSheet("background:rgb(133,132,131)"); l->raise(); m_lines.append(l); }}void CRubberBand::setRect(const QRect&r) // 根据区域大小设置线的位置{ m_rect = r; m_lines[0]->setGeometry(QRect(r.topLeft(), r.topRight()+QPoint(0,2))); m_lines[1]->setGeometry(QRect(r.bottomLeft(), r.bottomRight()+QPoint(0,2))); m_lines[2]->setGeometry(QRect(r.topLeft(), r.bottomLeft()+QPoint(2,0))); m_lines[3]->setGeometry(QRect(r.topRight(), r.bottomRight()+QPoint(2,0))); foreach (auto l, m_lines) l->raise();}QRect CRubberBand::rect(){ return m_rect;}bool CRubberBand::isVisible() // 判断是否可见{ return m_lines[0]->isVisible();}void CRubberBand::show() // 显示4条线{ foreach (auto l, m_lines) l->show();}void CRubberBand::hide() // 隐藏4条线{ foreach (auto l, m_lines) l->hide();}void CRubberBand::clear() // 删除四条线{ foreach (auto l, m_lines) l->deleteLater(); m_lines.clear();}
功能代码实现
为了便于通用其他项目,我将功能代码封装成另一个类,派生于QWidget,处理鼠标事件、窗口大小改变事件及对区域与橡皮筋的操作。
代码实现如下:
class CFrameForm : public QWidget{ Q_OBJECTpublic: enum Type { LeftTop=0, Top, RightTop, Right, RightBottom, Bottom, LeftBottom, Left, Other }; explicit CFrameForm(QWidget*parent=0); ~CFrameForm(); void setType(int type) {m_type = (Type)type;} void setCanResize(bool c);private slots: void setHandel(QWidget *handel, CFrameForm::Type type);private: void mousePressEvent(QMouseEvent *); void mouseMoveEvent(QMouseEvent *); void mouseReleaseEvent(QMouseEvent *); void showEvent(QShowEvent *event); void resizeEvent(QResizeEvent *e);private: QPoint m_pos; QRect m_normalRect; QDesktopWidget m_desk; QWidget* m_parent; Type m_type; CRubberBand m_rubber; // 橡皮筋 QListm_handles; // 8块区域};
类功能实现如下:
const int iresize = 8; // 拖拽窗体尺寸像素CFrameForm::CFrameForm(QWidget*parent) :QWidget(parent), m_parent(parent){ m_rubber.hide(); m_type = Other; setWindowFlags(windowFlags() | Qt::FramelessWindowHint); // 设置成无边框 for (int i = Left; i >= LeftTop; i--) // 创建8个区域 { CHandel *w = new CHandel(i, this); m_handles.push_front(w); }}CFrameForm::~CFrameForm(){ m_rubber.clear();}void CFrameForm::setCanResize(bool c) // 设置是否可以进行拖拽拉伸{ for (int i = 0; i < m_handles.size(); i++){ setHandel(m_handles[i], !c ? Other : (Type)m_handles[i]->type()); m_handles[i]->raise(); }}// 根据所属类型,移动到对应区域和设置对应光标void CFrameForm::setHandel(QWidget*handel, CFrameForm::Type type){ switch (type) { case LeftTop: handel->setGeometry(QRect(0,0,10,10)); handel->setCursor(Qt::SizeFDiagCursor); break; case Top: handel->setGeometry(QRect(10,0,width()-20,iresize)); handel->setCursor(Qt::SizeVerCursor); break; case RightTop: handel->setGeometry(QRect(width()-10,0,10,10)); handel->setCursor(Qt::SizeBDiagCursor); break; case Right: handel->setGeometry(QRect(width()-iresize,10,iresize,height()-20)); handel->setCursor(Qt::SizeHorCursor); break; case RightBottom: handel->setGeometry(QRect(width()-10,height()-10,10,10)); handel->setCursor(Qt::SizeFDiagCursor); break; case Bottom: handel->setGeometry(QRect(10,height()-iresize,width()-20,iresize)); handel->setCursor(Qt::SizeVerCursor); break; case LeftBottom: handel->setGeometry(QRect(0,height()-10,10,10)); handel->setCursor(Qt::SizeBDiagCursor); break; case Left: handel->setGeometry(QRect(0,10,iresize,height()-20)); handel->setCursor(Qt::SizeHorCursor); break; case Other: handel->setCursor(Qt::ArrowCursor); break; }}void CFrameForm::mousePressEvent(QMouseEvent *e) // 判断鼠标按键与类型正确与否{ if ((e->button() == Qt::LeftButton) && (m_type != Other)) m_pos = e->pos(); else QWidget::mousePressEvent(e);}void CFrameForm::mouseMoveEvent(QMouseEvent *e) // 动态计算大小拉伸区域大小,并设置显示橡皮筋{ if (!m_pos.isNull()) { QWidget *p = m_parent; if (p && m_type < Other){ QRect r; int w = p->minimumSizeHint().width(); int h = p->minimumSizeHint().height(); if (m_type == Right) { r = QRect(p->pos(), QSize(e->pos().x(), height())); if (r.width() < w) r.setWidth(w); } else if (m_type == Left) { int x = QCursor::pos().x(); r = QRect(x, p->pos().y(), p->pos().x() + width() - x, height()); int dx = r.x() + r.width(); if (r.width() < w) r.setX(dx-w); } else if (m_type == Bottom) { r = QRect(p->pos(), QSize(width(), e->pos().y())); if (r.height() < h) r.setHeight(h); } else if (m_type == Top) { r = QRect(p->pos().x(), QCursor::pos().y(), width(), p->pos().y() + height() - QCursor::pos().y()); int dy = r.y() + r.height(); if (r.height() < h) r.setY(dy-h); } else if (m_type == RightTop) { r = QRect(p->pos().x(), QCursor::pos().y(), e->pos().x(), p->pos().y() + height() - QCursor::pos().y()); if (r.width() < w) r.setWidth(w); int dy = r.y() + r.height(); if (r.height() < h) r.setY(dy-h); } else if (m_type == LeftTop) { r = QRect(QCursor::pos(), QSize(p->pos().x() + width() - QCursor::pos().x(), p->pos().y() + height() - QCursor::pos().y())); int dx = r.x() + r.width(); if (r.width() < w) r.setX(dx-w); int dy = r.y() + r.height(); if (r.height() < h) r.setY(dy-h); } else if (m_type == RightBottom) { r = QRect(p->pos(), QCursor::pos());//QSize(e->pos().x(), e->pos().y()))); if (r.width() < w) r.setWidth(w); if (r.height() < h) r.setHeight(h); } else if (m_type == LeftBottom) { r = QRect(QCursor::pos().x(), p->pos().y(), p->pos().x() + width() - QCursor::pos().x(), e->pos().y()); int dx = r.x() + r.width(); if (r.width() < w) r.setX(dx-w); if (r.height() < h) r.setHeight(h); } else return; m_rubber.show(); m_rubber.setRect(r); } } else QWidget::mouseMoveEvent(e);}void CFrameForm::mouseReleaseEvent(QMouseEvent *e) // 鼠标释放后隐藏橡皮筋并设置界面大小{ if (m_rubber.isVisible()) { m_rubber.hide(); if (m_parent) m_parent->setGeometry(m_rubber.rect()); resize(m_rubber.rect().size()); } else QWidget::mousePressEvent(e); m_type = Other; m_pos = QPoint();}void CFrameForm::showEvent(QShowEvent *event) { if (m_parent) resize(m_parent->size()); }void CFrameForm::resizeEvent(QResizeEvent *e) // 窗体大小变更后,更新8个区域位置{ QWidget::resizeEvent(e); for (int i = 0; i < m_handles.size(); i++) { setHandel(m_handles[i], (Type)m_handles[i]->type()); m_handles[i]->raise(); }}
总结
我的砖就抛到这里,希望对你们有用。
接下来的路就请各位小伙伴们自己走了!