简述

Qt中关于样式的使用很常见,为了降低耦合性(与逻辑代码分离),我们通常会定义一个QSS文件,然后编写各种部件(例如:QLable、QLineEdit、QPushButton)的样式,最后使用QApplication进行样式加载,这样,就可以让整个应用程序就共享同一个样式。

新建QSS文件

首先,新建一个后缀名为qss的文件,例如:style.qss,将其加入资源文件(qrc)中。
提示:也可以使用绝对路径或相对路径。

编写QSS

在style.qss文件中编写自己的样式代码,例如:

  1. QToolTip {
  2. border: 1px solid rgb(45, 45, 45);
  3. background: white;
  4. color: black;
  5. }

加载QSS

为了方便以后调用,可以写一个静态加载样式的函数:

  1. #include <QFile>
  2. #include <QApplication>
  3. class CommonHelper
  4. {
  5. public:
  6. static void setStyle(const QString &style) {
  7. QFile qss(style);
  8. qss.open(QFile::ReadOnly);
  9. qApp->setStyleSheet(qss.readAll());
  10. qss.close();
  11. }
  12. };

然后,在主函数里进行加载:

  1. int main(int argc, char *argv[])
  2. {
  3. QApplication a(argc, argv);
  4. // 加载QSS样式
  5. CommonHelper::setStyle("style.qss");
  6. MainWindow window;
  7. window.show();
  8. return a.exec();
  9. }

实现原理

很容易发现,原来qApp是QCoreApplication的一个单例,然后,将其转换为QApplication。

  1. #if defined(qApp)
  2. #undef qApp
  3. #endif
  4. #define qApp (static_cast<QApplication *>(QCoreApplication::instance()))

那么,QApplication调用setStyleSheet()以后为何所有的部件样式都改变了呢?
通过逐层分析,我们发现其主要是调用了setStyle():

  1. void QApplication::setStyle(QStyle *style)
  2. {
  3. if (!style || style == QApplicationPrivate::app_style)
  4. return;
  5. QWidgetList all = allWidgets();
  6. // clean up the old style
  7. if (QApplicationPrivate::app_style) {
  8. if (QApplicationPrivate::is_app_running && !QApplicationPrivate::is_app_closing) {
  9. for (QWidgetList::ConstIterator it = all.constBegin(), cend = all.constEnd(); it != cend; ++it) {
  10. QWidget *w = *it;
  11. if (!(w->windowType() == Qt::Desktop) && // except desktop
  12. w->testAttribute(Qt::WA_WState_Polished)) { // has been polished
  13. QApplicationPrivate::app_style->unpolish(w);
  14. }
  15. }
  16. }
  17. QApplicationPrivate::app_style->unpolish(qApp);
  18. }
  19. QStyle *old = QApplicationPrivate::app_style; // save
  20. QApplicationPrivate::overrides_native_style =
  21. nativeStyleClassName() == QByteArray(style->metaObject()->className());
  22. #ifndef QT_NO_STYLE_STYLESHEET
  23. if (!QApplicationPrivate::styleSheet.isEmpty() && !qobject_cast<QStyleSheetStyle *>(style)) {
  24. // we have a stylesheet already and a new style is being set
  25. QStyleSheetStyle *newProxy = new QStyleSheetStyle(style);
  26. style->setParent(newProxy);
  27. QApplicationPrivate::app_style = newProxy;
  28. } else
  29. #endif // QT_NO_STYLE_STYLESHEET
  30. QApplicationPrivate::app_style = style;
  31. QApplicationPrivate::app_style->setParent(qApp); // take ownership
  32. // take care of possible palette requirements of certain gui
  33. // styles. Do it before polishing the application since the style
  34. // might call QApplication::setPalette() itself
  35. if (QApplicationPrivate::set_pal) {
  36. QApplication::setPalette(*QApplicationPrivate::set_pal);
  37. } else if (QApplicationPrivate::sys_pal) {
  38. clearSystemPalette();
  39. initSystemPalette();
  40. QApplicationPrivate::initializeWidgetPaletteHash();
  41. QApplicationPrivate::initializeWidgetFontHash();
  42. QApplicationPrivate::setPalette_helper(*QApplicationPrivate::sys_pal, /*className=*/0, /*clearWidgetPaletteHash=*/false);
  43. } else if (!QApplicationPrivate::sys_pal) {
  44. // Initialize the sys_pal if it hasn't happened yet...
  45. QApplicationPrivate::setSystemPalette(QApplicationPrivate::app_style->standardPalette());
  46. }
  47. // initialize the application with the new style
  48. QApplicationPrivate::app_style->polish(qApp);
  49. // re-polish existing widgets if necessary
  50. if (QApplicationPrivate::is_app_running && !QApplicationPrivate::is_app_closing) {
  51. for (QWidgetList::ConstIterator it = all.constBegin(), cend = all.constEnd(); it != cend; ++it) {
  52. QWidget *w = *it;
  53. if (w->windowType() != Qt::Desktop && w->testAttribute(Qt::WA_WState_Polished)) {
  54. if (w->style() == QApplicationPrivate::app_style)
  55. QApplicationPrivate::app_style->polish(w); // repolish
  56. #ifndef QT_NO_STYLE_STYLESHEET
  57. else
  58. w->setStyleSheet(w->styleSheet()); // touch
  59. #endif
  60. }
  61. }
  62. for (QWidgetList::ConstIterator it = all.constBegin(), cend = all.constEnd(); it != cend; ++it) {
  63. QWidget *w = *it;
  64. if (w->windowType() != Qt::Desktop && !w->testAttribute(Qt::WA_SetStyle)) {
  65. QEvent e(QEvent::StyleChange);
  66. QApplication::sendEvent(w, &e);
  67. w->update();
  68. }
  69. }
  70. }
  71. #ifndef QT_NO_STYLE_STYLESHEET
  72. if (QStyleSheetStyle *oldProxy = qobject_cast<QStyleSheetStyle *>(old)) {
  73. oldProxy->deref();
  74. } else
  75. #endif
  76. if (old && old->parent() == qApp) {
  77. delete old;
  78. }
  79. if (QApplicationPrivate::focus_widget) {
  80. QFocusEvent in(QEvent::FocusIn, Qt::OtherFocusReason);
  81. QApplication::sendEvent(QApplicationPrivate::focus_widget->style(), &in);
  82. QApplicationPrivate::focus_widget->update();
  83. }
  84. }

主要分为4步:

  1. 清理旧样式 - unpolish()
  2. 初始化新样式 - polish()
  3. 加载新样式 - polish() + sendEvent()、update()
  4. 删除旧样式 - delete

通过调用QWidgetList all = allWidgets()获取了所有控件的集合,然后,利用迭代器QWidgetList::ConstIterator对每一个控件进行处理,最后,通过QApplication::sendEvent()来发送QEvent::StyleChange事件,达到全局样式更改。