qt 多窗口布局 框架(从头开始编写QT组件库)

qt 多窗口布局  框架(从头开始编写QT组件库)(1)

决定

最近连续推荐了好多开源项目,不知道大家感觉怎么样?我打算从今天开始编写一个Qt组件库,其中包含各种自定义控件,多种实用小功能窗口甚至最后还会加入小游戏,这是我现在的设想。其实一直想要做这样的一个东西,但是一直没有开始,那么就从今天开始,最后具体能做成什么样,大家拭目以待,如果各位大神有什么想法或者发现了什么问题也请多多指教。

无边框窗口框架

既然要做一个界面程序,那么窗口肯定少不了,其中主界面窗口就相当于程序的门面,一定要美观大方。但是相信很多人都和我一样没有美术基础,为了避免这种尴尬,此时最简单有效的方法就是贴图。确定使用图片作为背景后,考虑到大家的兴趣爱好、审美以及个性化,我觉得换肤功能是必要的。

解决了窗口背景的问题,我们再来看看窗口边框的问题。现在的主流软件大都是采用无边框扁平化的界面,相较于传统的带边框的界面,无边框窗口显得更现代化,窗口看上去更加清爽。我们知道Qt窗口默认都是带边框的,当然想要将边框去掉也很容易,只需要在程序中添加下面这句话即可:

this->setWindowFlags(Qt::FramelessWindowHint);

但是去掉边框以后,我们发现,窗口标题栏没了,导致窗口不能放大、缩小和关闭,而且边框没了以后,窗口连最基础的移动都做不到了。我的解决方式是在主窗口顶部添加自定义标题栏窗口,并在窗口中添加程序图标、程序名称标签,以及几个按钮来实现窗口设置、最大化、最小化和关闭功能,并重写窗口移动事件、鼠标点击事件和鼠标释放事件,根据鼠标和窗口的不同状态来实现窗口的拉伸缩放和移动功能。

上面的功能都实现以后,运行程序发现因为窗口没有边框,所以窗口边缘很不明显,如果刚好和颜色相近的其他应用或桌面重合,很难分得清,所以我们需要给窗口加一个阴影边框,这在Qt中也比较容易,可以使用QGraphicsDropShadowEffect类和QFrame类搭配实现。

实现

确定了无边框窗口的框架后,那么下面我们就来实现,首先我们先去掉窗口边框、设置背景透明和启用鼠标跟踪:

this->resize(1000, 700);//初始化窗口大小 this->setWindowFlags(Qt::FramelessWindowHint);//设置窗体无边框、允许任务栏右键菜单、允许最小化、最大化 this->setAttribute(Qt::WA_TranslucentBackground);//设置背景透明 this->setMouseTracking(true);//启用鼠标跟踪,即使没有按下按钮,控件也会收到鼠标的移动事件,本程序中主要为了缩放窗口使用

接着我们实现自定义标题栏:

//初始化自定义标题栏 void Widget::initTitleBarWidget() { titleBar_Widget = new QWidget(); titleBar_Widget->setObjectName("titleBar_Widget"); titleBar_Widget->setMouseTracking(true); titleBar_Widget->setCursor(Qt::ArrowCursor); //程序图标 QLabel *logo_Label = new QLabel(); logo_Label->setFixedSize(18, 18); logo_Label->setScaledContents(true); logo_Label->setPixmap(QPixmap(":/J_Component.ico")); titleName_Label = new QLabel(tr(" J_Component")); titleName_Label->setFont(QFont("Microsoft YaHei", 10, QFont::Bold)); QPalette palette; palette.setColor(QPalette::WindowText, QColor(0, 0, 0)); titleName_Label->setPalette(palette); //自定义关闭、最大化、最小化按钮 setting_Button = new QToolButton(); setting_Button->setObjectName("setting_Button"); setting_Button->setCursor(Qt::PointingHandCursor);//设置光标样式 close_Button = new QToolButton(); close_Button->setObjectName("close_Button"); close_Button->setCursor(Qt::PointingHandCursor);//设置光标样式 max_Button = new QToolButton(); max_Button->setObjectName("max_Button"); max_Button->setCursor(Qt::PointingHandCursor);//设置光标样式 min_Button = new QToolButton(); min_Button->setObjectName("min_Button"); min_Button->setCursor(Qt::PointingHandCursor);//设置光标样式 //获取按钮图标 close_Pixmap = style()->standardPixmap(QStyle::SP_TitleBarCloseButton); max_Pixmap = style()->standardPixmap(QStyle::SP_TitleBarMaxButton); min_Pixmap = style()->standardPixmap(QStyle::SP_TitleBarMinButton); normal_Pixmap = style()->standardPixmap(QStyle::SP_TitleBarNormalButton); //设置按钮图标 setting_Button->setIcon(QPixmap(":/Images/Settings.png")); close_Button->setIcon(close_Pixmap); max_Button->setIcon(max_Pixmap); min_Button->setIcon(min_Pixmap); //设置提示信息 setting_Button->setToolTip(tr("Setting")); close_Button->setToolTip(tr("Close")); max_Button->setToolTip(tr("Maximize")); min_Button->setToolTip(tr("Minimize")); //自定义按钮布局 QHBoxLayout *titleBar_HLayout = new QHBoxLayout(); titleBar_HLayout->addWidget(logo_Label); titleBar_HLayout->addWidget(titleName_Label); titleBar_HLayout->addStretch(); titleBar_HLayout->addWidget(setting_Button); titleBar_HLayout->addWidget(min_Button); titleBar_HLayout->addWidget(max_Button); titleBar_HLayout->addWidget(close_Button); titleBar_HLayout->setSpacing(1); titleBar_HLayout->setContentsMargins(5, 0, 0, 0); titleBar_Widget->setLayout(titleBar_HLayout); //关联信号与槽函数 connect(setting_Button, SIGNAL(clicked(bool)), this, SLOT(showSettingMenu())); connect(close_Button, SIGNAL(clicked(bool)), this, SLOT(close())); connect(max_Button, SIGNAL(clicked(bool)), this, SLOT(myShowMaximized())); connect(min_Button, SIGNAL(clicked(bool)), this, SLOT(showMinimized())); }

然后实现窗口移动和缩放功能,窗口移动主要使用九宫格法,及四边、四角和中间区域,具体实现方法,参考如下代码,注释里写的也比较清楚:

//计算当前鼠标光标在自定义九宫格中的行号 int Widget::row(QPointF pos) { if(pos.y() < 10)//如果鼠标当前坐标的纵坐标偏移窗口左上角坐标的纵坐标 < 5(认为鼠标在上边框上) { return 10; } else if(pos.y() > height() - 10)//鼠标当前坐标的纵坐标偏移窗口左上角坐标的纵坐标 > 窗口的高度-5(认为鼠标在下边框上) { return 30; } else//如果 5 < 鼠标当前坐标的纵坐标偏移窗口左上角坐标的纵坐标 < 窗口的高度-5(认为鼠标在窗体内) { return 20; } } //计算当前鼠标光标在自定义九宫格中的列号 int Widget::col(QPointF pos) { if(pos.x() < 10)//(认为鼠标在左边框上) { return 1; } else if(pos.x() > width() - 10)//(认为鼠标在右边框上) { return 3; } else//(认为鼠标在窗体内) { return 2; } } //计算当前鼠标光标在自定义九宫格中的编号 int Widget::calcPositionNum(QPointF pos) { return row(pos) col(pos); } //设置鼠标光标样式 void Widget::setMouseStyle(int nMousePositionNum) { switch(nMousePositionNum) { case 11: setCursor(Qt::SizeFDiagCursor); break; case 12: setCursor(Qt::SizeVerCursor); break; case 13: setCursor(Qt::SizeBDiagCursor); break; case 21: setCursor(Qt::SizeHorCursor); break; case 22: if(m_bMousePress) { setCursor(Qt::ClosedHandCursor); } else { setCursor(Qt::ArrowCursor); } break; case 23: setCursor(Qt::SizeHorCursor); break; case 31: setCursor(Qt::SizeBDiagCursor); break; case 32: setCursor(Qt::SizeVerCursor); break; case 33: setCursor(Qt::SizeFDiagCursor); break; default: setCursor(Qt::WaitCursor); break; } } //鼠标按下响应 void Widget::mousePressEvent(QMouseEvent *event) { if(event->button() == Qt::LeftButton) { m_bMousePress = true; m_bMouseMove = true; m_qLastRect = geometry(); m_qMousePosition = event->globalPos();//获取鼠标当前位置距离屏幕左上角的坐标 } } //鼠标移动响应 void Widget::mouseMoveEvent(QMouseEvent *event) { if(!m_bMousePress)//如果鼠标没按下 { m_nMousePositionNum = calcPositionNum(event->pos()); setMouseStyle(m_nMousePositionNum); } if(m_bMousePress)//如果鼠标按下 { if(m_bMouseMove) { m_bMouseMove = false; m_nMousePositionNum = calcPositionNum(event->pos()); } setMouseStyle(m_nMousePositionNum); QPoint tempPos = event->globalPos() - m_qMousePosition;//得到鼠标移动的距离 if(m_nMousePositionNum == 22) { if(event->globalPos().y() <= 3) { m_bIsMax = false; myShowMaximized(); } else if(m_bIsMax && event->globalPos().y()>3) { m_bIsMax = true; myShowMaximized(); } else { move(pos() tempPos);//当前窗口左上角距离屏幕左上角的距离 鼠标移动的距离 = 窗口左上角的新位置 m_qMousePosition = event->globalPos(); } } else { switch(m_nMousePositionNum) { case 11://左上角 m_qLastRect.setTopLeft(m_qLastRect.topLeft() tempPos); break; case 13://右上角 m_qLastRect.setTopRight(m_qLastRect.topRight() tempPos); break; case 31://左下角 m_qLastRect.setBottomLeft(m_qLastRect.bottomLeft() tempPos); break; case 33://右下角 m_qLastRect.setBottomRight(m_qLastRect.bottomRight() tempPos); break; case 12://上 m_qLastRect.setTop(m_qLastRect.top() tempPos.y()); break; case 21://左 m_qLastRect.setLeft(m_qLastRect.left() tempPos.x()); break; case 23://右 m_qLastRect.setRight(m_qLastRect.right() tempPos.x()); break; case 32://下 m_qLastRect.setBottom(m_qLastRect.bottom() tempPos.y()); break; default: break; } this->setGeometry(m_qLastRect); m_qMousePosition = event->globalPos(); } } } //鼠标释放响应 void Widget::mouseReleaseEvent(QMouseEvent *event) { Q_UNUSED(event); m_bMousePress = false; m_bMouseMove = false; setCursor(Qt::ArrowCursor); } //窗口大小改变事件 void Widget::resizeEvent(QResizeEvent *event) { Q_UNUSED(event); if(m_bIsMax)//如果是最大化 { max_Button->setIcon(normal_Pixmap); max_Button->setToolTip(tr("Restore Down")); } else { max_Button->setIcon(max_Pixmap); } } //窗口鼠标双击响应事件 void Widget::mouseDoubleClickEvent(QMouseEvent *event) { if(event->button() == Qt::LeftButton) { if(event->pos().y() < 25) { myShowMaximized(); } } }

最后就是实现换肤功能,这里我自己封装了一个换肤类,添加了9张图片作为默认壁纸,还添加了一个自定义按钮用来从本地选择背景图片,其中大家可以注意一下QSignalMapper的用法:

#ifndef SKINWIDGET_H #define SKINWIDGET_H #include <QWidget> //换肤窗口类 class SkinWidget : public QWidget { Q_OBJECT public: explicit SkinWidget(QString picName, QWidget *parent = 0); private: QString bkPicName; //背景图片名称 signals: void changeSkin(QString); //换肤信号 private slots: void setSkin(QString); //换肤响应 void showCustomWidget(); //显示选择自定义背景图片窗口 protected: void paintEvent(QPaintEvent *); }; #endif // SKINWIDGET_H

#include "skinwidget.h" #include <QSignalMapper> #include <QPushButton> #include <QGridLayout> #include <QVBoxLayout> #include <QLabel> #include <QPainter> #include <QFileDialog> SkinWidget::SkinWidget(QString picName, QWidget *parent) :bkPicName(picName), QWidget(parent) { QSignalMapper *signalMapper = new QSignalMapper(this); QStringList bkPicName; bkPicName << ":/Images/1.jpg" << ":/Images/2.jpg" << ":/Images/3.jpg" << ":/Images/4.jpg" << ":/Images/5.jpg" << ":/Images/6.jpg" << ":/Images/7.jpg" << ":/Images/8.jpg" << ":/Images/9.jpg"; QGridLayout *gridLayout = new QGridLayout; gridLayout->setSpacing(0); int row = 0, column = 0;//行和列 for(int i=0; i<9; i ) { QPushButton *btn = new QPushButton; QIcon icon(bkPicName[i]); btn->setIcon(icon); //在这里加样式表是因为这个按钮在qss中的样式有时候不能生效 btn->setStyleSheet("QPushButton{min-width: 105px; max-width: 105px;}"); btn->setIconSize(QSize(97, 62)); connect(btn, SIGNAL(clicked()), signalMapper, SLOT(map())); //bkPicName[i] = bkPicName[i].left(10) ".jpg";//1-small.png --> 1.jpg signalMapper->setMapping(btn,bkPicName[i]); if(i % 3 == 0) { row ; column = 0; } gridLayout->addWidget(btn, row, column ); } connect(signalMapper, SIGNAL(mapped(QString)), this, SIGNAL(changeSkin(QString))); connect(signalMapper, SIGNAL(mapped(QString)), this, SLOT(setSkin(QString))); QPushButton *custom_Button = new QPushButton(tr("Custom Background Image>>>")); custom_Button->setObjectName("custom_Button"); //在这里加样式表是因为这个按钮在qss中的样式有时候不能生效 custom_Button->setStyleSheet("QPushButton{min-width: 318px; max-width: 318px; border-radius: 6px; background: rgba(255, 255, 255, 50%);}"); connect(custom_Button, SIGNAL(clicked(bool)), this, SLOT(showCustomWidget())); QHBoxLayout *hLayout = new QHBoxLayout; hLayout->addStretch(); hLayout->addWidget(custom_Button); QVBoxLayout *vLayout = new QVBoxLayout; vLayout->addLayout(gridLayout); vLayout->addLayout(hLayout); setLayout(vLayout); setWindowFlags(Qt::Popup); } void SkinWidget::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setBrush(QBrush(QPixmap(bkPicName))); painter.setRenderHints(QPainter::Antialiasing, true); painter.setPen(Qt::black); painter.drawRect(rect()); } //更新换肤界面的背景 void SkinWidget::setSkin(QString picName) { bkPicName = picName; update(); } //显示选择自定义背景图片窗口 void SkinWidget::showCustomWidget() { bkPicName = QFileDialog::getOpenFileName(NULL, tr("Select Image"), "C:/", tr("Image (*.jpg *.png *.svg)")); if(bkPicName == "") return; changeSkin(bkPicName); }

最终效果如下面动图所示:(一张图片不能超过20MB,导致我删了好多帧,下次要分开录了...)

qt 多窗口布局  框架(从头开始编写QT组件库)(2)

其实现在实现的这个换肤功能加上空空的主界面,完全可以作为一个简单的图片浏览器,感觉优化一下显示速度,应该完全能满足要求。

由于时间关系,实现原理讲得比较粗略,不过相关代码已经贴出来了,大家感兴趣的话可以研究一下,如果有什么问题,也欢迎在评论里留言,大家一起探讨,共同进步。

最后,为大家附上几张程序中的背景图,我觉得挺好看的,O(∩_∩)O哈哈~

qt 多窗口布局  框架(从头开始编写QT组件库)(3)

qt 多窗口布局  框架(从头开始编写QT组件库)(4)

qt 多窗口布局  框架(从头开始编写QT组件库)(5)

qt 多窗口布局  框架(从头开始编写QT组件库)(6)

qt 多窗口布局  框架(从头开始编写QT组件库)(7)

qt 多窗口布局  框架(从头开始编写QT组件库)(8)

qt 多窗口布局  框架(从头开始编写QT组件库)(9)

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页