Qt样式表

1、什么是QSS

说QSS之前,先了解下CSS,一般接触过前端开发的都知道CSS是什么,CSS(层叠式样式表)一般用来表现HTML或XML等文件的计算机语言,CSS能够静态修饰网页,也能够结合脚本对网页各元素进行动态格式化。
而QSS,说白了,就是适用于Qt的“层叠式样式表”,由影响窗口部件绘制的样式规则组成,在概念、术语、语法上等很大程度上受到了CSS的影响,QSS的功能比CSS弱很多,主要是体现在选择的类型较少,可用于Qt的属性也比较少,并且,并不是所有的属性都可以在Qt的控件上使用。

2、QSS的使用

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设计器中使用,该方法最直接,写完样式表应用之后就能够直接在看到样式表的效果。使用该方法在写样式表的过程中也在实时检测样式表的有效性,可以避免样式表无效。同样,使用该方法,假设你不知道该怎样写样式表,可以直接同过傻瓜式的选择确定好属性之后,再根据样式表语法加入选择器,就能够形成完整的样式表。

3、盒子模型

说样式表,避免不了要了解下盒子模型,如下图:
在这里插入图片描述

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*/
}

4、QSS语法规则

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;
	}

5、选择器类型

选择器名称选择器说明
通配选择器*匹配所有的控件
类型选择器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

6、子控件

相对于一些比较复杂的控件,比如QCheckBox,我们可以将他看做是由一个QPushButton和一个QLabel组成(实际上并不是这样的,只是为了方便理解子控件),我们就可以单独设置前面那个QPushButton的样式。比如:

	QCheckBox {
		spacing: 10px;
		font-size:14pt;
	} 

上面这个样式表设置QCheckBox整体,包括前后两部分,假设我们要单纯的设置前面的大小,则可以:

	QCheckBox::indicator {
	    width: 26px;
	    height: 26px;
	}

上面的规则指定了QCheckBox左边方框的样式,由(::)来指定控件的子控件,Qt的子控件总是相对于另一个参考元素,这个参考元素可能是小部件或者是其他子控件,比如QSpinbox右边有两个按钮。其中的一个可以看做是另一个的参考元素。
由此可看,我们能够设置复杂样式表的部分样式,这边有一点需要注意:给子控件设置一个图片时,会隐式的设置其大小。

7、伪状态

实际使用中,我们可能需要按照一个控件不同的状态来显示不同的效果,比如我们需要给按钮设置样式表来模拟按钮被按下的过程,我们可以设置:

	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;
    }

8、动态属性

  • 首先先说一下我们很常用的一种方法,就是对一个空间设置不同的属性,然后根据属性设置不同的样式表,来动态设置一个控件的样式。
    首先我们给一个按钮设置样式表:
	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);
	}

产生上述效果的原因是,如果样式表在属性改变之前已经设定,则不能按照正常的属性来显示样式,需要动态的加载。

  • 其次,每一个控件都有 被 Q_PROPERTY qproperty- 定义的原始属性,Q_PROPERTY是一个宏,我们以QLabel为例说明:
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++代码,就能够直接写出完美的样式。

  • 最后,我们看下自定义属性,看完了前面的原有属性,那么我们是不是可以想一想,会不会我们也能够按照这样的方式增加属性呢?增加的属性能够干什么?其实,自定义的属性也能够有很多的用法,比如我们给QTreeWidget的item自定义一个属性,那么我们每次获取相应的值就会非常简单,这里我们说一下怎么用自定义属性来实现样式表。

首先,我们需要派生一个类: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);

运行显示效果:
在这里插入图片描述

9、自定义控件

自定义控件是我们在Qt开发中经常遇到的问题,这类控件的样式表和Qt原生控件的样式表一样,写作:
我们首先自定义一个控件:

class myLabel : public QLabel
{
    Q_OBJECT
public:
    explicit myLabel(QWidget *parent = 0);
    ~myLabel(){}
};

样式表:

myLabel{color: red;}

10、命名空间内的自定义控件

这类控件在实际应用中我们并不是很常见,但也不能保证会一直不用,下面先看下这类控件的自定义方式:

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;}

11、解决冲突

假设我们有一个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;
}

12、级联效应

对于CSS中,假设一个控件没有设置字体颜色等属性时,控件会自动继承父控件的属性来显示,但在QSS中,控件不会自动继承父控件的样式表

qApp->setStyleSheet("QPushButton{color:red;}");

pWidget->setStyleSheet("QPushButton{font-size:14pt;}");

pBtn->setStyleSheet("QPushButton{background-color:blue;}");

上面的显示效果最终会显示为:按钮背景为蓝色,字体颜色为红色,字体大小为14pt;因为QSS可以在QApplication、父部件、子部件中设置,任意控件的有效样式表通过合并控件的祖先(父亲、祖父等)以及任何QApplication上设置的样式表而来,因此按钮才会显示为上面的效果。特殊的一点:假设合并的样式表中发生冲突时,不论冲突的特殊性,控件自身的样式表优先

注意这是控件样式表的级联效应,不是继承。

13、继承

上面已经说过,QSS中,控件不会主动继承父控件的样式表,比如:

QGroup{ color: red;}

这样的样式表只会改变QGroup自己的字体颜色,假设他有控件QLabel,QLabel的字体显示不会为红色;如果需要给一个QGroup及其下属控件的字体设置为红色,则可以:

QGroup, QGroup *{ color: red;}

至此,QSS的基本知识已经说完了,后面还有一些比较特别的点,比如:border-image 和 background-image 等后续再更新。

14、测试代码

文中的测试例子

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值