00-1010最近连续推荐了很多开源项目。不知道你感觉如何?我准备从今天开始写一个Qt组件库,里面包含各种自定义控件,各种实用的小功能窗口,最后甚至还有小游戏。这是我现在的想法。其实我一直想做这样的东西,只是还没开始做。所以,让我们从今天开始,最后,我们等着看我们能做些什么。如果各位大神有什么想法或者发现什么问题,请给我们提意见。
00-1010既然要做界面程序,就必须有windows。主界面窗口相当于程序的门面,所以一定要美观大方。但我相信很多人和我一样没有艺术基础。要避免这种尴尬,这个时候最简单有效的方法就是贴图。用图片做背景后,考虑到大家的兴趣,审美,个性化,我觉得换肤功能是必要的。
解决了窗口背景的问题,我们再来看窗口边框的问题。现在的主流软件大多采用无边框的平面界面。与传统的有边框的界面相比,无边框的窗口更现代,窗口看起来也更清爽。我们知道默认情况下Qt窗口是有边框的。当然,移除边框很容易,只需在程序中添加以下句子:
this-setWindowFlags(Qt :3360 frameleswindowhint);但是去掉边框后,我们发现窗口的标题栏不见了,导致窗口无法放大、缩小和关闭。而且,边框没了之后,窗口连最基本的动作都做不了。我的解决方案是在主窗口顶部添加一个自定义标题栏窗口,在窗口中添加一个程序图标、一个程序名标签、几个按钮,实现窗口的设置、最大化、最小化、关闭等功能,重写窗口移动事件、鼠标点击事件、鼠标释放事件,根据鼠标和窗口的不同状态实现窗口的拉伸、缩放、移动等功能。
以上功能全部实现后,运行程序发现窗口边缘不明显,因为窗口没有边框。如果恰好与其他颜色相近的应用程序或桌面重合,就很难分辨出来。因此,我们需要给窗口添加阴影边框,这在Qt中也很容易。您可以使用QGraphicsDropShadowEffect类和QFrame类来实现它。
00-1010确定了无边框窗口的边框之后,那么下面我们来实现一下。首先,让我们删除窗口框架,设置背景透明度,并启用鼠标跟踪:
this-resize(1000,700);//初始化窗口大小this-setwindowflags(Qt :3360 frameleswindowhint);//设置窗体无边框,允许任务栏右键菜单,允许最小化和最大化this-set属性(Qt:3360WA _半透明背景);//设置背景透明度this-setmousettracking(true);//启用鼠标跟踪。即使没有按下按钮,控件也会接收鼠标移动事件。这个程序主要用来缩放窗口。然后我们实现了自定义标题栏:
//初始化自定义标题栏void widget :3360 inittitlebarwidget(){ titlebar _ widget=newq widget();titleBar _ Widget-set object name(\' titleBar _ Widget \');titleBar _ Widget-set mouse tracking(true);标题栏_ Widget-set cursor(Qt : arrow cursor);//程序图标q Label * logo _ Label=new q Label();logo_Label-setFixedSize(18,18);logo _ Label-setScaledContents(true);logo _ Label-setPixmap(qpix map(\' :/J _ component . ico \'));title name _ Label=new q Label(tr(\' J _ Component \');titleName_Label-setFont(QFont(\'微软雅黑\',10,q font : bold));QPalette调色板;palette . set color(qpalette :3360 window text,QColor(0,0,0));titleName_Label-setPalette(调色板);//自定义关闭、最大化、最小化按钮设置_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_OBJECTpublic: 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哈哈~