赞
踩
说QSS之前,先了解下CSS,一般接触过前端开发的都知道CSS是什么,CSS(层叠式样式表)一般用来表现HTML或XML等文件的计算机语言,CSS能够静态修饰网页,也能够结合脚本对网页各元素进行动态格式化。
而QSS,说白了,就是适用于Qt的“层叠式样式表”,由影响窗口部件绘制的样式规则组成,在概念、术语、语法上等很大程度上受到了CSS的影响,QSS的功能比CSS弱很多,主要是体现在选择的类型较少,可用于Qt的属性也比较少,并且,并不是所有的属性都可以在Qt的控件上使用。
1、直接调用QWidget::setStyleSheet()方法
QWidget* pWidget = new QWidget();
pWidget->setStyleSheet("background-color:#ff0000");
2、调用QApplication::setStyleSheet()方法,该方法可以设置整个程序的样式表
qApp->setStyleSheet("background-color:#ff0000");
3、独立使用qss文件,在代码中加载文件并使用样式表
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Style w;
QFile qss(QCoreApplication::applicationDirPath() + "../../style.qss");
if (qss.open(QFile::ReadOnly))
{
QString style = QLatin1String(qss.readAll());
a.setStyleSheet(style);
qss.close();
}
w.show();
return a.exec();
}
4、在QtDesigner设计器中使用,该方法最直接,写完样式表应用之后就能够直接在看到样式表的效果。使用该方法在写样式表的过程中也在实时检测样式表的有效性,可以避免样式表无效。同样,使用该方法,假设你不知道该怎样写样式表,可以直接同过傻瓜式的选择确定好属性之后,再根据样式表语法加入选择器,就能够形成完整的样式表。
说样式表,避免不了要了解下盒子模型,如下图:
margin: 控件的外边距
border: 控件的边框
padding: 控件填充
content: 控件内容
任何控件都能够适配盒子模型,我们可以将盒子模型看做是4个同心的矩形,在没有设置样式表之前,4个矩形是等大的,并且每个矩形背景是透明的,我们通过一个样式表来了解下盒子模型:
QWidget#wBoxModel{
background:red url(:/image/background.png); /*背景色为红色,背景图片为background.png */
background-repeat: repeat-y; /*在y轴方向重复图片*/
background-position: left; /*图片位于控件的做测*/
border: 10px solid #ffff00; /*绘制10像素宽度的黄色实线边框线*/
margin:10px; /*控件的边距为10像素,也可以看做是外边距*/
padding:10px; /*部件填充距离为10像素*/
background-clip:margin; /*背景色填充整个边距矩形*/
background-origin:margin; /*背景图片的原点位于边界矩形*/
outline:11px dashed green; /*绘制一个11像素宽的绿色虚线轮廓线*/
font-size:14pt; /*控件的字体大小为14pt*/
}
QSS的语法规则和CSS的基本相同,由两部分组成,一部分指定了受影响的控件,另一部分指定了影响的属性。例如:
selector{attribute: value;}
QPushButton{color:red;}
QPushButton指定了选择器,表明所有的QPushButton的实例及其的子类会受到影响,后面的 color:red;是规则的定义,表明期前景色受到影响为红色。
注意:QSS通常不区分大小写(即:color、Color、COLOR、cOloR指同一属性),唯一例外就是类名(ClassName)、对象名(ObjectName)、属性名(propertyName)区分大小写。
同样的,假设有多个选择器具有相同的样式表属性,可以并列,中间用(,)隔开,比如:
QPushButton, QCheckBox, QLabel{color:red;}
该语句相当于下面三个规则序列:
QPushButton{color:red;}
QCheckBox{color:red;}
QLabel{color:red;}
如果申明的属性的个数大于1个,则属性与属性之间用(;)隔开,例如:
QPushButton{
color:red;
font-size:14pt;
}
选择器名称 | 选择器 | 说明 |
---|---|---|
通配选择器 | * | 匹配所有的控件 |
类型选择器 | QPushButton | 匹配所有QPushButton和其子类的实例 |
属性选择器 | QPushButton[flat=“false”] | 匹配所有flat属性是false的QPushButton实例,注意该属性可以是自定义的属性,不一定非要是类本身具有的属性 |
类选择器 | .QPushButton | 匹配所有QPushButton的实例,但是并不匹配其子类。这是与CSS中的类选择器不一样的地方,注意前面有一个点号 |
ID选择器 | #myButton | 匹配所有id为myButton的控件实例,这里的id实际上就是objectName指定的值 |
后代选择器 | QDialog QPushButton | 所有QDialog容器中包含的QPushButton,不管是直接的还是间接的 |
子选择器 | QDialog > QPushButton | 所有QDialog容器下面的QPushButton,其中要求QPushButton的直接父容器是QDialog |
相对于一些比较复杂的控件,比如QCheckBox,我们可以将他看做是由一个QPushButton和一个QLabel组成(实际上并不是这样的,只是为了方便理解子控件),我们就可以单独设置前面那个QPushButton的样式。比如:
QCheckBox {
spacing: 10px;
font-size:14pt;
}
上面这个样式表设置QCheckBox整体,包括前后两部分,假设我们要单纯的设置前面的大小,则可以:
QCheckBox::indicator {
width: 26px;
height: 26px;
}
上面的规则指定了QCheckBox左边方框的样式,由(::)来指定控件的子控件,Qt的子控件总是相对于另一个参考元素,这个参考元素可能是小部件或者是其他子控件,比如QSpinbox右边有两个按钮。其中的一个可以看做是另一个的参考元素。
由此可看,我们能够设置复杂样式表的部分样式,这边有一点需要注意:给子控件设置一个图片时,会隐式的设置其大小。
实际使用中,我们可能需要按照一个控件不同的状态来显示不同的效果,比如我们需要给按钮设置样式表来模拟按钮被按下的过程,我们可以设置:
QPushButton#btnDialog{ /*设置按钮在正常状态下的样式*/
background-color:blue;
}
QPushButton#btnDialog:hover{ /*设置按钮在鼠标悬浮状态下的样式*/
background-color:green;
}
QPushButton#btnDialog:pressed{ /*设置按钮在被按下时的样式*/
background-color:red;
}
上面可以看到,控件的伪状态用(:)来指定,意味着限制控件状态的应用程序规则。
伪状态可以用(!)进行否定。
QPushButton#btnDialog:!hover{
background-color:green;
}
伪状态可以连接使用,这个时候使用了隐式的规则与,例如鼠标悬浮在一个被选中的按钮:
QPushButton#btnDialog:hover:checked{ /*第一个状态限制了鼠标悬浮,第二个状态限制了按钮被选中*/
background-color:green;
}
QPushButton#btnProperty[style = "save"]{
color:red;
}
QPushButton#btnProperty[style = "cancel"]{
color:green;
}
然后在代码中设置该按钮的属性。
ui->btnProperty->setProperty("style", "save");
//如果初始按钮是在QtDesigner中拖过来的,也可以在Designer中直接添加样式表
需要注意的一点:属性的value不能用tr(“save”),因为这样使用了Qt的翻译工具,如果开发的软件是多语言版本,则在切换语言的过程中可能会造成比较难以发现的问题。尤其是在QtDesigner编辑器中直接添加属性时,更要注意,因为在QtDesigner中,添加的属性默认是支持翻译的,需要在添加时去掉可翻译的勾选。
这样,按钮就会按照上面第一条规则显示样式,然后我们在其他地方动态的改变属性的值。
void Style:: on_btnPropertyClicked()
{
ui->btnProperty->setProperty("style", "cancel");
}
修改之后我们运行程序,发现样式表并没有按照我们预先设置好的规则来显示。这是因为,如果样式表在属性设置之前定义,修改属性后,样式表并不能生效,我们需要手动加载。将上面的函数修改为下面的这段就能够正常的应用规则显示样式。
void Style:: on_btnPropertyClicked()
{
ui->btnProperty->setProperty("style", "cancel");
this->style()->unpolish(ui->btnProperty);
this->style()->polish(ui->btnProperty);
}
产生上述效果的原因是,如果样式表在属性改变之前已经设定,则不能按照正常的属性来显示样式,需要动态的加载。
class Q_WIDGETS_EXPORT QLabel : public QFrame { Q_OBJECT Q_PROPERTY(QString text READ text WRITE setText) Q_PROPERTY(Qt::TextFormat textFormat READ textFormat WRITE setTextFormat) Q_PROPERTY(QPixmap pixmap READ pixmap WRITE setPixmap) Q_PROPERTY(bool scaledContents READ hasScaledContents WRITE setScaledContents) Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment) Q_PROPERTY(bool wordWrap READ wordWrap WRITE setWordWrap) Q_PROPERTY(int margin READ margin WRITE setMargin) Q_PROPERTY(int indent READ indent WRITE setIndent) Q_PROPERTY(bool openExternalLinks READ openExternalLinks WRITE setOpenExternalLinks) Q_PROPERTY(Qt::TextInteractionFlags textInteractionFlags READ textInteractionFlags WRITE setTextInteractionFlags) Q_PROPERTY(bool hasSelectedText READ hasSelectedText) Q_PROPERTY(QString selectedText READ selectedText) }
上面这部分是QLabel声明的部分代码,可以看到这个里面都有Q_PROPERTY宏定义,这个宏定义用来定义一些属性,具体看下这个宏定义。
Q_PROPERTY(QString text READ text WRITE setText)
QString 表明该属性的类型
text 属性名称
READ text 声明了一个读取该属性的方法 实现的时候必须加 const修饰
WRITE setText 声明了一个写该属性的方法
通过上面的分解,是不是好像很熟悉的样子,对不对,我们在获取一个QLabel的字的时候是不是用ui->lbProperty->text(),设置字体的时候用setText方法,因此可以看出,一些属性能够方便我们的操作,当然在QSS中,这种方便也是存在的,比如:
QLabel{
qproperty-minimumSize: 309px 191px; /*设置lable的最小大小*/
qproperty-maximumSize: 309px 191px;
qproperty-pixmap: url(:/image/background.png);
color:red;
}
QGroupBox QPushButton {
qproperty-text: "Click Me"; /*设置按钮的显示字*/
qproperty-icon: url(:/image/ic_advanced setting.png);
qproperty-iconSize: 40px 40px;
}
通过这些原本的属性,我们不需要额外的增加任何C++代码,就能够直接写出完美的样式。
首先,我们需要派生一个类:StyleWidget 继承自QWidget。
class StyleWidget : public QWidget { Q_OBJECT Q_PROPERTY(QColor normalColor READ normalColor WRITE setNormalColor DESIGNABLE true) Q_PROPERTY(QColor errorColor READ ErrorColor WRITE setErrorColor DESIGNABLE true) Q_PROPERTY(QColor infoColor READ infoColor WRITE setInfoColor DESIGNABLE true) public: explicit StyleWidget(QWidget *parent = 0); ~StyleWidget(); QColor normalColor() const; /*记得要用const修饰*/ void setNormalColor(QColor color); QColor errorColor() const; void setErrorColor(QColor color); QColor infoColor() const; void setInfoColor(QColor color); /*定义的属性值*/ private: QColor m_cMormalColor; QColor m_cErrorColor; QColor m_cInfoColor; };
成员函数的实现比较简单,设置和获取相应属性的值。在使用的时候,就能够直接按照上面原有属性的方法去实现。qss文件中定义:
StyleWidget {
qproperty-normalColor: green;
qproperty-infoColor: rgb(0, 160, 230);
qproperty-errorColor: red;
}
按照调用方式已经完成了,难点是怎么调用这些属性,下面以table的model模型来举例说明一下:
class TableModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit TableModel(QObject *parent = 0);
~TableModel();
/*TableModel继承自QAbstractTableModel,由于下面的函数是纯虚函数,因此在TableModel中需要实现*/
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
virtual int rowCount(const QModelIndex &parent) const;
virtual int columnCount(const QModelIndex &parent) const;
private:
StyleWidget m_dStyle; //定义一个我们自定义的派生的属性类。
};
下面看下TableModel的实现:
TableModel::TableModel(QObject *parent) : QAbstractTableModel(parent) { //构造和析构函数就不再多言 } TableModel::~TableModel() { } int TableModel::rowCount(const QModelIndex &parent) const { return 5; //这边为了方便,我指定了行数和列数 } int TableModel::columnCount(const QModelIndex &parent) const { return 3; }
//实现下面的函数,纯粹是为了给table增加表头
QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const { if(Qt::DisplayRole != role) { return QVariant(); } if(orientation == Qt::Horizontal) { return section + 1; //两个方向的表头都是从1开始自增的数字 } else if(orientation == Qt::Vertical) { return section + 1; } return QVariant(); }
下面函数的目的是实现我们的样式表,该函数能够根据现实规则,对数据、样式等应用我们指定的规则
QVariant TableModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); switch (role) { case Qt::TextColorRole: { if (index.column() == FILE_NAME_COLUMN) return m_dStyle.normalColor(); if (index.column() == FILE_SIZE_COLUMN) return m_dStyle.infoColor(); if (index.column() == FILE_NOTE_COLUMN) return m_dStyle.errorColor(); } case Qt::TextAlignmentRole: { return int(Qt::AlignHCenter | Qt::AlignVCenter); } case Qt::DisplayRole: { return 1; //表格中的元素全部显示为 1 } } return QVariant(); }
调用该类也比较简单:
QTableView* pTable = new QTableView(this);
TableModel* pModel = new TableModel(pTable);
pTable->setModel(pModel);
运行显示效果:
自定义控件是我们在Qt开发中经常遇到的问题,这类控件的样式表和Qt原生控件的样式表一样,写作:
我们首先自定义一个控件:
class myLabel : public QLabel
{
Q_OBJECT
public:
explicit myLabel(QWidget *parent = 0);
~myLabel(){}
};
样式表:
myLabel{color: red;}
这类控件在实际应用中我们并不是很常见,但也不能保证会一直不用,下面先看下这类控件的自定义方式:
namespace myNameSpace{
class myLabel2 : public QLabel
{
Q_OBJECT
public:
explicit myLabel2(QWidget *parent = 0);
~myLabel2(){}
};
}
针对这类控件,有一点比较特殊的地方,我们通过打印该类控件的名称会发现:
myLabel2* PLabel = new myLabel2(this);
qDebug() << "label calssName: " << PLabel->metaObject()->className();
打印结果显示: label calssName: myNameSpace::myLabel2
根据上面的结果显示,名称中作用域的限制,这样假设我们使用QSS的基本语法 myNameSpace::myLabel2{color: red;};会跟QSS子控件的语法出现冲突,此时我们需要将 “::”等价替换为 “–”;注意是两个 “-”;QSS写作:
myNameSpace--myLabel2{color: red;}
假设我们有一个QPushButton,样式表写作如下:
QPushButton#btnProperty{
color:red;
}
QPushButton{
color:blue;
}
这样对按钮btnProperty来说,有两条样式表,运行过程中按钮始终显示字体为红色,对应一条显示规则:选择器越具体越优先,因此我们看到的按钮不会显示为蓝色字体。
接下来:
QPushButton#btnDialog:hover{
background-color:green;
}
QPushButton#btnDialog:Enable{
background-color:red;
}
上面的按钮选择器有相同的特殊性,按照正常逻辑理解为,鼠标在按钮btnDialog悬浮时按钮背景色显示为绿色,其他情况下背景色显示为红色,但是实际运行过程中发现,按钮的背景色一直显示为红色,并没有出现绿色,这就产生了一个规则,样式表中出现相同的规则特殊性时,写在后面的样式表会覆盖掉掐面的样式表,上面的样式表我们可以写作:
QPushButton#btnDialog:Enable{
background-color:red;
}
QPushButton#btnDialog:hover{
background-color:green;
}
对于CSS中,假设一个控件没有设置字体颜色等属性时,控件会自动继承父控件的属性来显示,但在QSS中,控件不会自动继承父控件的样式表。
qApp->setStyleSheet("QPushButton{color:red;}");
pWidget->setStyleSheet("QPushButton{font-size:14pt;}");
pBtn->setStyleSheet("QPushButton{background-color:blue;}");
上面的显示效果最终会显示为:按钮背景为蓝色,字体颜色为红色,字体大小为14pt;因为QSS可以在QApplication、父部件、子部件中设置,任意控件的有效样式表通过合并控件的祖先(父亲、祖父等)以及任何QApplication上设置的样式表而来,因此按钮才会显示为上面的效果。特殊的一点:假设合并的样式表中发生冲突时,不论冲突的特殊性,控件自身的样式表优先
注意这是控件样式表的级联效应,不是继承。
上面已经说过,QSS中,控件不会主动继承父控件的样式表,比如:
QGroup{ color: red;}
这样的样式表只会改变QGroup自己的字体颜色,假设他有控件QLabel,QLabel的字体显示不会为红色;如果需要给一个QGroup及其下属控件的字体设置为红色,则可以:
QGroup, QGroup *{ color: red;}
至此,QSS的基本知识已经说完了,后面还有一些比较特别的点,比如:border-image 和 background-image 等后续再更新。
文中的测试例子
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。