引言
样式表在开发基于应用程序的项目中经常使用,今天记录一个我在使用中的一次坑。
案例
背景
最近想编写一个插件,基于Qt Designer的扩展插件。对于自定义的控件希望可以通过提供的对外接口能够修改原有的样式。
示例
下面是部分实现的代码。其中主要涉及前期设置样式后,后期通过调用一些可以更改样式的对外接口来更改原有的样式,设置为新的样式的代码。这个过程中遇到了一些坑,下面来看看。
优化
设置样式的字符串最好集中在一起,而不能像QString字符串那样不断的追加。
原来追加的样式设置字符串:
void QCustomWidget::setBorderRadius(const int &radius)
{
m_radius = radius;
QString strBorderRadius = QString("QCustomWidget#MainWidget{border-radius:%1px;}").arg(radius);
strBorderRadius += QString("QWidget#titleWidget{border-radius:%1px;}").arg(radius);
strBorderRadius += QString("QWidget#contextWidget{border-radius:%1px;}").arg(radius);
qDebug()<<"radius: "<<strBorderRadius;
setStyleSheet(strBorderRadius);
}
像上面这样写似乎也是可以的,但尽量像下面这样将字符串集中。
void QCustomWidget::setBorderRadius(const int &radius)
{
m_radius = radius;
QString strBorderRadius = QString("QCustomWidget#MainWidget{border-radius:%1px;}"
"QWidget#titleWidget{border-radius:%2px;}"
"QWidget#contextWidget{border-radius:%3px;}").arg(radius).
arg(radius).arg(radius);
qDebug()<<"radius: "<<strBorderRadius;
setStyleSheet(strBorderRadius);
}
坑1
前期设置样式后,后期通过调用对外的接口改变控件的样式。若是对于单个控件的样式改变,使用this->setStyleSheet(this->styleSheet() +strStyle),设置的样式都不会生效。
下面是代码,像这样:
void QCustomWidget::setTopLeftRadius(const int &radius)
{
m_topLeftRadius = radius;
QString strBorderRadius = QString("QWidget#titleWidget{border-top-left-radius:%1px;}").arg(radius);
this->setStyleSheet(this->styleSheet() + strBorderRadius);
}
void QCustomWidget::setTopRightRadius(const int &radius)
{
m_topRightRadius = radius;
QString strBorderRadius = QString("QWidget#titleWidget{border-top-right-radius:%1px;}").arg(radius);
this->setStyleSheet(this->styleSheet() + strBorderRadius);//Qt的样式表机制会在你改变样式表时自动处理界面的重绘。
}
void QCustomWidget::setBottomLeftRdius(const int &radius)
{
m_bottomLeftRadius = radius;
QString strBorderRadius = QString("QWidget#contextWidget{border-bottom-left-radius:%1px;}").arg(radius);
this->setStyleSheet(this->styleSheet() + strBorderRadius);
}
void QCustomWidget::setBottomRightRadius(const int &radius)
{
m_bottomRightRadius = radius;
QString strBorderRadius = QString("QWidget#contextWidget{border-bottom-right-radius:%1px;}").arg(radius);
this->setStyleSheet(this->styleSheet() + strBorderRadius);
}
void QCustomWidget::setBackGroundColor(const QColor &color)
{
m_backGroundColor = color;
QString strBackColor = QString("QWidget#contextWidget{background-color:%1;}").arg(color.name());
qDebug()<<"background-color: "<<strBackColor;
//当已经使用整个类的setStyleSheet设置了样式后,对于后期单个控件样式的改变,需要使用m_Context->setStyleSheet(m_Context->styleSheet() + strBackColor);
//而不是m_Context->setStyleSheet(this->styleSheet() + strBackColor);若是使用,就会出现只会以控件的最后一次设置样式显示,对于同一控件的多次样式设置,出现覆盖,只显示最后一次的样式
this->setStyleSheet(this->styleSheet() + strBackColor);
}
void QCustomWidget::setTitleColor(const QColor &color)
{
m_titleColor = color;
QString strBackColor = QString("QWidget#titleWidget{background-color:%1;}").arg(color.name());
qDebug()<<"title-color: "<<strBackColor;
this->setStyleSheet(this->styleSheet() + strBackColor);
}
上面的这个函数接口中都使用this来设置样式表。若调用的时候如下。
QCustomWidget *pWidget = new QCustomWidget();
//初始化完成之后,每一个控件的样式只可以在初始化的基础之上,再设置一次样式,若多次重复设置同一个控件的样式就会设置无效
pWidget->setTopRightRadius(5);//在初始化的基础之上可以单独去设置标题和文本窗口的样式一次,但多次就会无效
pWidget->setTopLeftRadius(0);//同一个控件,设置多次,最后一次控件的样式有效,其它被覆盖无效
pWidget->setBottomRightRadius(20);
pWidget->setBottomLeftRdius(5);
pWidget->setBackGroundColor(Qt::green);
pWidget->setTitleColor(Qt::cyan);
pWidget->show();
那么运行结果是上面所有的样式设置都不会生效。还是原来已经设置好的样式,没有实现预期调用接口中的效果。原来的样式如下,运行后和原来的样式一样。
坑2
若前期已经通过setStyleSheet()设置了整个类的样式,后期想通过调用对外提供的接口更改样式。实现如下的调用。
QCustomWidget *pWidget = new QCustomWidget();
//初始化完成之后,每一个控件的样式只可以在初始化的基础之上,再设置一次样式,若多次重复设置同一个控件的样式就会设置无效
pWidget->setTopRightRadius(5);//在初始化的基础之上可以单独去设置标题和文本窗口的样式一次,但多次就会无效
pWidget->setTopLeftRadius(0);//同一个控件,设置多次,最后一次控件的样式有效,其它被覆盖无效
pWidget->setBottomRightRadius(20);
pWidget->setBottomLeftRdius(5);
pWidget->show();
其中setTopRightRadius()和setTopLeftRadius()都是针对控件m_title来设置样式,当函数setTopRightRadius()和setTopLeftRadius()中设置样式的代码像下面这样写时:
void QCustomWidget::setTopLeftRadius(const int &radius)
{
m_topLeftRadius = radius;
QString strBorderRadius = QString("QWidget#titleWidget{border-top-left-radius:%1px;}").arg(radius);
m_title->setStyleSheet(this->styleSheet() + strBorderRadius);
}
void QCustomWidget::setTopRightRadius(const int &radius)
{
m_topRightRadius = radius;
QString strBorderRadius = QString("QWidget#titleWidget{border-top-right-radius:%1px;}").arg(radius);
m_title->setStyleSheet(this->styleSheet() + strBorderRadius);
}
void QCustomWidget::setBottomLeftRdius(const int &radius)
{
m_bottomLeftRadius = radius;
QString strBorderRadius = QString("QWidget#contextWidget{border-bottom-left-radius:%1px;}").arg(radius);
m_Context->setStyleSheet(this->styleSheet() + strBorderRadius);
}
void QCustomWidget::setBottomRightRadius(const int &radius)
{
m_bottomRightRadius = radius;
QString strBorderRadius = QString("QWidget#contextWidget{border-bottom-right-radius:%1px;}").arg(radius);
m_Context->setStyleSheet(this->styleSheet() + strBorderRadius);
}
void QCustomWidget::setBackGroundColor(const QColor &color)
{
m_backGroundColor = color;
QString strBackColor = QString("QWidget#contextWidget{background-color:%1;}").arg(color.name());
qDebug()<<"background-color: "<<strBackColor;
//当已经使用整个类的setStyleSheet设置了样式后,对于后期单个控件样式的改变,需要使用m_Context->setStyleSheet(m_Context->styleSheet() + strBackColor);
//而不是m_Context->setStyleSheet(this->styleSheet() + strBackColor);若是使用,就会出现只会以控件的最后一次设置样式显示,对于同一控件的多次样式设置,出现覆盖,只显示最后一次的样式
m_Context->setStyleSheet(this->styleSheet() + strBackColor);
}
void QCustomWidget::setTitleColor(const QColor &color)
{
m_titleColor = color;
QString strBackColor = QString("QWidget#titleWidget{background-color:%1;}").arg(color.name());
qDebug()<<"title-color: "<<strBackColor;
m_title->setStyleSheet(this->styleSheet() + strBackColor);
}
只会有最后一个pWidget->setTopLeftRadius(0);生效,而pWidget->setTopRightRadius(5);不会生效,因为样式出现了覆盖。同样, pWidget->setBottomRightRadius(20);不会生效,而 pWidget->setBottomLeftRdius(5);会生效。因为setBottomRightRadius(20);和setBottomLeftRdius(5);都是针对控件m_Context的样式设置,后面的样式设置覆盖了前面的样式设置。
其运行效果:
截图中只观察样式调用函数中的四个角的半径即可。可以看到只有左上角和左下角的样式生效。
若上面的调用中,再加上两句调用背景色的函数:
QCustomWidget *pWidget = new QCustomWidget();
pWidget->setTopRightRadius(5);//在初始化的基础之上可以单独去设置标题和文本窗口的样式一次,但多次就会无效
pWidget->setTopLeftRadius(0);//同一个控件,设置多次,最后一次控件的样式有效,其它被覆盖无效
pWidget->setBottomRightRadius(20);
pWidget->setBottomLeftRdius(5);
pWidget->setBackGroundColor(Qt::green);
pWidget->setTitleColor(Qt::cyan);
运行的效果如下:
可以看到四个角的半径的设置都无效,只有背景色生效。
因为setBackGroundColor(Qt::green);是针对于控件m_Context,而setTitleColor(Qt::cyan);是针对与控件m_title。上面的四个角的设置也是分别针对于控件m_title和m_Context,故而出现了覆盖。最后设置的样式才生效,前面的设置都会被覆盖掉。
解决坑
针对上面的这中情况,下面是应对方法。其本意是想在已有样式:
的基础之上,可以通过调用对外提供的接口来设置想要的四角半径及背景色。
那么需要这么编写实现代码才能够实现:
void QCustomWidget::setTopLeftRadius(const int &radius)
{
m_topLeftRadius = radius;
QString strBorderRadius = QString("QWidget#titleWidget{border-top-left-radius:%1px;}").arg(radius);
m_title->setStyleSheet(m_title->styleSheet() + strBorderRadius);
}
void QCustomWidget::setTopRightRadius(const int &radius)
{
m_topRightRadius = radius;
QString strBorderRadius = QString("QWidget#titleWidget{border-top-right-radius:%1px;}").arg(radius);
m_title->setStyleSheet(m_title->styleSheet() + strBorderRadius);//Qt的样式表机制会在你改变样式表时自动处理界面的重绘。
}
void QCustomWidget::setBottomLeftRdius(const int &radius)
{
m_bottomLeftRadius = radius;
QString strBorderRadius = QString("QWidget#contextWidget{border-bottom-left-radius:%1px;}").arg(radius);
m_Context->setStyleSheet(m_Context->styleSheet() + strBorderRadius);
}
void QCustomWidget::setBottomRightRadius(const int &radius)
{
m_bottomRightRadius = radius;
QString strBorderRadius = QString("QWidget#contextWidget{border-bottom-right-radius:%1px;}").arg(radius);
m_Context->setStyleSheet(m_Context->styleSheet() + strBorderRadius);
}
void QCustomWidget::setBackGroundColor(const QColor &color)
{
m_backGroundColor = color;
QString strBackColor = QString("QWidget#contextWidget{background-color:%1;}").arg(color.name());
qDebug()<<"background-color: "<<strBackColor;
//当已经使用整个类的setStyleSheet设置了样式后,对于后期单个控件样式的改变,需要使用m_Context->setStyleSheet(m_Context->styleSheet() + strBackColor);
//而不是m_Context->setStyleSheet(this->styleSheet() + strBackColor);若是使用,就会出现只会以控件的最后一次设置样式显示,对于同一控件的多次样式设置,出现覆盖,只显示最后一次的样式
m_Context->setStyleSheet(m_Context->styleSheet() + strBackColor);
}
void QCustomWidget::setTitleColor(const QColor &color)
{
m_titleColor = color;
QString strBackColor = QString("QWidget#titleWidget{background-color:%1;}").arg(color.name());
qDebug()<<"title-color: "<<strBackColor;
m_title->setStyleSheet(m_title->styleSheet() + strBackColor);
}
调用方法:
QCustomWidget *pWidget = new QCustomWidget();
//初始化完成之后,每一个控件的样式只可以在初始化的基础之上,再设置一次样式,若多次重复设置同一个控件的样式就会设置无效
pWidget->setTopRightRadius(5);//在初始化的基础之上可以单独去设置标题和文本窗口的样式一次,但多次就会无效
pWidget->setTopLeftRadius(0);//同一个控件,设置多次,最后一次控件的样式有效,其它被覆盖无效
pWidget->setBottomRightRadius(20);
pWidget->setBottomLeftRdius(5);
pWidget->setBackGroundColor(Qt::green);
pWidget->setTitleColor(Qt::cyan);
pWidget->setBackGroundColor(Qt::gray);
pWidget->setTitleColor(Qt::red);
运行的效果:
对比代码中想要实现的效果,左上角半径为0,右上角半径为5,左下角半径为5,左下角半径为20,标题栏背景色为红色,文本窗背景色为灰色,达到了想要的效果。
坑3
设置上图中的按钮的圆角半径的时候,发现通过类对外的接口调用怎么设置都无效,而直接使用固定圆角半径设置便会生效。
下面看看我当时可供对外调用的设置按钮圆角半径的接口:
void QCustomWidget::setBtn1Style(const int &radius, const QColor &backColor, const QColor &textColor)
{
QString strBackColor = QString("border-radius:%1px;background-color: %2;color:%3;").arg(radius).arg(backColor.name()).arg(textColor.name());
m_pConfirmBtn->setStyleSheet(strBackColor);
}
函数调用是这样的:
QCustomWidget *pWidget = new QCustomWidget();
//初始化完成之后,每一个控件的样式只可以在初始化的基础之上,再设置一次样式,若多次重复设置同一个控件的样式就会设置无效
pWidget->setTopRightRadius(5);//在初始化的基础之上可以单独去设置标题和文本窗口的样式一次,但多次就会无效
pWidget->setTopLeftRadius(0);//同一个控件,设置多次,最后一次控件的样式有效,其它被覆盖无效
pWidget->setBottomRightRadius(20);
pWidget->setBottomLeftRdius(8);
pWidget->setBackGroundColor(Qt::green);
pWidget->setTitleColor(Qt::cyan);
pWidget->setBackGroundColor(Qt::gray);
pWidget->setTitleColor(Qt::red);
pWidget->setBtnImage("F:/self_study/jobSearchPrepare/Qt/usePlugin/useCustomWidgetPlugin/delete.png");
pWidget->setBtn1Text(QStringLiteral("忽略"));
pWidget->setBtn2Text(QStringLiteral("确定"));
pWidget->setBtn1Style(8,QColor(255, 170, 0),Qt::white);
看着似乎没有什么问题,但是就是样式不生效。
运行结果:
忽略按钮的背景色设置成功了,但是圆角半径没有生效。
考虑样式字符串是否编写错误,是否出现同一控件的样式覆盖,答案是字符串编写正确,没有出现同一控件的样式覆盖。考虑是否是因为字符串的格式化arg()的使用是否在样式表中无法正常解析,但是通过日志打印,样式字符串能够正常输出。是否是父控件的设置导致,排查之后发现没有。
于是考虑通过下面这样测验样式设置:
void QCustomWidget::setBtn1Style(const int &radius, const QColor &backColor, const QColor &textColor)
{
m_pConfirmBtn->setStyleSheet(QString("border-radius:5px;background-color: rgb(255, 170, 0);color:white;"));
}
可以正常显示所设置的圆角半径和背景色。
运行结果:
只是通过字符串传入参数QString().arg()构成一个样式字符串的时候无法正常显示圆角半径,想着是否因为QString().arg()的使用导致的。为什么呢?于是又尝试了另一种写法:
void QCustomWidget::setBtn1Style(const int &radius, const QColor &backColor, const QColor &textColor)
{
m_pConfirmBtn->setStyleSheet(QString("border-radius:"+QString::number(radius)+"px;background-color: rgb(255, 170, 0);color:white;"));
}
将设置样式的字符串改为上面这种字符串拼接的方式,结果还是样式无效。
运行结果:
纠结很久,比对寻思原因,当修改了传入的圆角半径为5px,而不是8px,这时候无论是通过QString().arg()构成的样式字符串,还是通过上面的字符串拼接构成的样式字符串,都能够正常显示所设置的按钮圆角半径。
QCustomWidget *pWidget = new QCustomWidget();
//初始化完成之后,每一个控件的样式只可以在初始化的基础之上,再设置一次样式,若多次重复设置同一个控件的样式就会设置无效
pWidget->setTopRightRadius(5);//在初始化的基础之上可以单独去设置标题和文本窗口的样式一次,但多次就会无效
pWidget->setTopLeftRadius(0);//同一个控件,设置多次,最后一次控件的样式有效,其它被覆盖无效
pWidget->setBottomRightRadius(20);
pWidget->setBottomLeftRdius(8);
pWidget->setBackGroundColor(Qt::green);
pWidget->setTitleColor(Qt::cyan);
pWidget->setBackGroundColor(Qt::gray);
pWidget->setTitleColor(Qt::red);
pWidget->setBtnImage("F:/self_study/jobSearchPrepare/Qt/usePlugin/useCustomWidgetPlugin/delete.png");
pWidget->setBtn1Text(QStringLiteral("忽略"));
pWidget->setBtn2Text(QStringLiteral("确定"));
pWidget->setBtn1Style(5,QColor(255, 170, 0),Qt::white);
运行结果:
为什么呢?
其实,是因为我给按钮一开始设置的高度为38,但是实际上按钮处于一个布局中,同时又使用了样式表,所以按钮的实际高度不是38,而且通过肉眼很明显会发现没有设置按钮样式时按钮的高度要比设置了样式的按钮高度大。也就是按钮受到了布局管理器和样式表的影响,其高度不再是38,而我设置了圆角半径为8px,意味着按钮高度此时要大于16px,但实际上可能没有这么高,所以当调整了按钮的圆角半径为5px,就可以设置按钮的圆角半径。进而我修改按钮的圆角半径分别为6px,7px都可以正常设置按钮的圆角半径。也就是说按钮的实际高度可能就是14px,反正是小于16px的。
提示
本篇文章主要记录样式设置中遇到的问题,重在理解。