目录
第一章 项目特点
功能:自定义标题栏、阴影、透明、边框、全局背景、拖拽、缩放、最大化、最小化、还原、关闭、记住窗体状态。
网上已经有很多类似的教程,但都有一些不足。本篇文章几乎完全实现了和window默认窗体的所有功能(缺少最小化的动画和分屏)。
我是业余的,水平不高,这是我能想到最完美的解决方案了。
第二章 大致思路
2.1 阴影
Qt中的QGraphicsDropShadowEffect类,可以实现阴影效果。
而QWidget提供了void QWidget::setGraphicsEffect(QGraphicsEffect *effect)
通过这个函数,就能为窗体设置阴影。但有一些细节需要知晓:
- 窗体范围:一个应用程序的范围就是最外层窗体的geometry(),任何超出这个范围的效果都没法显示。
而阴影便是超出了所在窗体的范围,所以为了实现阴影效果,需要再外层窗体内嵌套一个窗体,为内层窗体设置阴影。这样阴影就不会超出应用程序的范围了。
2.2 标题栏
自定义窗体最重要的就是能自定义标题栏,比如在标题栏上加上其他的东西,像这样:
窗体的标题栏属于操作系统控制,肯定是没法修改的,想要实现上面的效果,肯定要隐藏窗体的默认标题栏,然后自己做一个标题栏。
2.3 拖拽
当鼠标拖拽标题栏时,可以移动整个窗体。这里可以重写QWidget的鼠标按下、鼠标移动、鼠标释放三个事件来实现。
- 首先鼠标按下时,记录鼠标坐标、鼠标状态、窗体坐标;
- 鼠标移动时,计算鼠标移动的向量,让窗体原来的坐标加上这个向量;
- 鼠标释放时,更新鼠标状态。
2.4 缩放
当鼠标移动到窗体边缘时,鼠标样式自动改变,并且拖拽鼠标实现改变窗体的大小。
这里我在窗体的四周放置了8个QLabel,分别放在绿色和蓝色区域,利用他们定义了“窗体四周”。
网上其他例子都是在鼠标移动事件里判断鼠标坐标,进而改变鼠标样式,实现相应的功能,逻辑很复杂,而且要为窗体里所有元素设置鼠标跟踪:setMouseTracking(true)。
这里利用8个标签,省去了该表鼠标样式的麻烦,简化了判断鼠标位置的逻辑,并且非常稳定,不会出现各种各样的bug。
2.5 最小化、最大化、还原、关闭
在内层窗体里加上相应的QToolButton,连接信号槽即可。
比较复杂的时最大化/还原按钮,当窗体最大化时,应当隐藏阴影、8个标签、改变按钮图标。
第三章 具体实现
环境:Qt Creator 5.9
- 新建QWidget项目,选中ui设计界面。这个界面就是窗体的主界面,按钮、文本框之类的元素放在这个界面上。
- 添加BaseWindow类,继承自QWidget类,在这个类中实现阴影,拖拽等功能。
- 添加一些必要的图标资源。
- 在main函数里设置启动窗体为BaseWindow。
最外面的虚线框为BaseWindow,内层实线框为Widget。蓝绿色为8个标签,放置在BaseWindow里。
3.1 设置布局
在BaseWindow头文件中定义变量:
private:
QSettings *m_Settings;
int m_ShadowWidth; //阴影宽度,也是m_BaseLayout的margin
int m_ResizeWidth = 3; //窗体四周调整尺寸的宽度,也是m_LblLeftTop等的宽度或高度
int m_TitleHeight; //标题栏高度
int m_TempShadowWidth;
//记录窗体状态
bool m_IsMaximized;
QRect m_Rect;
int m_BorderWidth;
//用于调整窗体大小的label
QLabel *m_LblLeftTop;
QLabel *m_LblTop;
QLabel *m_LblRightTop;
QLabel *m_LblLeft;
QLabel *m_LblRight;
QLabel *m_LblLeftBottom;
QLabel *m_LblBottom;
QLabel *m_LblRightBottom;
QFrame *m_BaseFrame; //主窗体,产生阴影,容纳m_ContentWindow等
QGridLayout *m_BaseLayout; //主窗体的网格布局
Widget *m_ContentWindow; //用户实际的界面,指向构造函数传入的ContentWindow
QGridLayout *m_ContentLayout; //m_ContentWindow的网格布局
定义私有成员函数void initializeUi();
在这个函数内实现界面布局,具体实现如下:
//初始化窗体
void BaseWindow::initializeUi()
{
//读取ini配置文件
m_Settings = new QSettings(QApplication::applicationDirPath()+"/config.ini", QSettings::IniFormat, this);
//设置背景窗体透明,为了实现阴影效果
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinimizeButtonHint);
this->setAttribute(Qt::WA_TranslucentBackground);
//BaseWindow显示阴影的布局
m_ShadowWidth = m_Settings->value("Shadow/shadowWidth").toInt();
m_BaseLayout = new QGridLayout();
m_BaseLayout->setMargin(m_ShadowWidth);
m_BaseLayout->setSpacing(0);
this->setLayout(m_BaseLayout);
//主窗体显示
m_BaseFrame = new QFrame();
m_BaseFrame->setObjectName("m_BaseFrame");
QString borderStyle = m_Settings->value("BaseWindow/border").toString();
m_BaseFrame->setStyleSheet("QFrame#m_BaseFrame{border:"+borderStyle+"}");
m_BorderWidth = borderStyle.mid(0, borderStyle.indexOf("px")).toInt();
m_BaseLayout->addWidget(m_BaseFrame, 0, 0, 3, 3);
//主窗体的布局
m_ContentLayout = new QGridLayout();
m_ContentLayout->setMargin(0);
m_ContentLayout->setSpacing(0);
m_BaseFrame->setLayout(m_ContentLayout);
//加入主窗体
m_ContentWindow = new Widget();
m_ContentLayout->addWidget(m_ContentWindow, 0, 0);
//设置最大化按钮的图标
m_ContentWindow->setToolResizeIcon(m_IsMaximized);
//设置调整窗体大小的Label
{
m_LblLeftTop = new QLabel();
m_LblLeftTop->setFixedSize(m_ResizeWidth, m_ResizeWidth);
m_LblLeftTop->setCursor(Qt::SizeFDiagCursor