qt 多窗口布局 框架(从头开始编写QT组件库)
决定
最近连续推荐了好多开源项目,不知道大家感觉怎么样?我打算从今天开始编写一个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,导致我删了好多帧,下次要分开录了...)
其实现在实现的这个换肤功能加上空空的主界面,完全可以作为一个简单的图片浏览器,感觉优化一下显示速度,应该完全能满足要求。
由于时间关系,实现原理讲得比较粗略,不过相关代码已经贴出来了,大家感兴趣的话可以研究一下,如果有什么问题,也欢迎在评论里留言,大家一起探讨,共同进步。
最后,为大家附上几张程序中的背景图,我觉得挺好看的,O(∩_∩)O哈哈~
,
免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com