在某些环境中,我们可能会想改变一下QT内置部件的外观。可能我们只想做一点微小的改变,或者将它完全实现为另一种风格,给我们的应用一种独特的,与众不同的外观。这里有三种不同的方法来重定义Qt内置控件的外观。
我们可以子类化单独的控件,并重新实现它们的绘图和鼠标事件。使用这种方法你可以完全控制它并实现你的想法,但是你需要做很多工作。
也可以子类化QStyle或者预定义一个类如QWindowsStyle.此方法非常强大。它通常被用来根据不同的平台提供此平台本地化的外观。
从Qt4.2开始,Qt style sheet成为了我们的另一种选择。Qt style sheets灵感来源于HTML(Cascading
Style Sheets)。我们并不需要掌握任何的编程知识就可以掌控Qt style
sheets,因为它只是一种在运行时被解释的普通文本。
我们已经在第5,7章讨论过第一种技术,尽管我们的重点放在了创建自定义部件。在这章,我们将回顾一下最后两种方法。我们将呈现两种自定义的风格:Candy风格,由style sheet指定。Bronze 风格,通过子类化QStyle实现(如 19.1所示)。为保证对话框大小可控,所有的控件都是经过小心挑选的。
使用Qt Style Sheets
Qt style sheets灵感来源于CSS,但它适用于控件。一个style sheets由style规则组成,style规则影响控件的渲染。所有的style规则都是普通文本。由于style sheets在是运行时解析的,通过Qt Designer’s Style editor或者开发时内嵌一个QTextEdit,使用-stylesheet file.css命令行选择为一个应用指定一个style sheet,我们可以很容易地使用不同的设计做实验。
Style sheets被应用于当前活动QStyle的最顶层(如,QWindowsVistaStyle或QPlastiqueStyle)。由于创建style sheets不牵涉任何子类化,所以它们是对存在控件微小自定义的最佳选择。假设我们想用黄色作为QLineEdit的背景色,可以使用下面的style sheet:
QLineEdit {
background-color: yellow;
}
对于CSS来说,QLineEdit被称作(选择器)selector, background-color被称为属性(attribute),yellow为值(value)。
对于这类型的自定义来说,使用style sheet往往能产生比摆弄控件的调色板更可靠的效果。因为QPalette入口(Base, Button, Highlight等等)因不同的风格使用各异。比如,QWindowsStyle使用Base调色板入口填充一个只读combobox背景,然而QPlastiqueStyle使用Button入口。再者,传入整个调色板,特定风格使用硬编码来渲染特定元素。而style sheets恰恰相反,它保证不管哪个QStyle是活动的,指定的颜色会被恰当使用。
QApplication::setStyleSheet()为整个应用设置一个style sheet:
qApp->setStyleSheet(“QLineEdit {background-color: yellow; }”);
我们也可以对一个控件(widget)设置style sheet,它的子控件使用QWidget::setStyleSheet()。如
dialog->setStyleSheet(“QLineEdit { background-color: yellow; }”)
如果我们对QLineEdit直接设置style sheet,我们可以忽略QLineEdit选择器(selector)和花括号:
lineEdit->setStyleSheet(“background-color: yellow;”);
目前为止,我们只为单一控件类设置了单个属性。在实际应用中,经常多种Style rules结合使用。如,下面的Style rules为六个控件类及它们的子类设置了前景色和背景色:
QCheckBox,
QCombBox,
QLineEdit,
QListView,
QRadioButton,
QSpinBox {
color: #050505;
background-color: yellow;
}
颜色也可以由名字指定,由HTML风格的字符串#RRGGBB格式,或者RGB或RGBA值:
QLineEdit {
color: rgb(0, 88, 120);
background-color: rgba(99%, 78% 12% 59%);
}
使用名字时,我们可以使用任何QColor::setNamedColor()能识别的颜色名。对于RGB来说,我们必须指定red, green, blue部分,每部分为0到255或0%到100%。RGBA允许我们指定额外的alpha部分,alpha对应于颜色的透明度。我们也可以指定一个调色板入口或者梯度(gradient)。
QLineEdit {
color: palette(Base);
background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 1,
stop: 0 white, stop: 0.4 gray, stop 1: green)
}
使用background-image属性,我们可以为背景指定图片。
背景色默认从控件左上角开始展开(不包括任何margin),在横向,纵向重复以填满整个部件(widget).此行为可以使用background-position和background-repeat属性配置. 如:
QLineEdit {
background-image: url(:/images/yellow-bg.png);
background-position: top right;
background-repeat: repeat-y;
}
如果同时指定背景图和背景色,背景色会透过背景图的半透明部分。
目前为止,我们讨论过的所有的选择器(selector)都是类名。我们还可以使用其它的选择器(selector);如,如果我们想为OK和Cancel按钮指定前景色,可以这样写:
QPushButton[text="OK"] {
color: green;
}
QPushButton[text="Cancel"] {
color: red;
}
此种选择器语法适用于任何Qt属性,尽管我们必须记住Style sheet并不知道什么时候一个属性被修改。选择器也可以以多种方式结合。如选择所有名为”okButon”,且x, y属性为0,名为”frame”的QFrame的直属子控件的QPushButton。我们可以这样写:
QFrame#frame > QPushButton[x="0"][y="0"]#okButton {
...
}
即使Qt没有定义任何mandatoryFiled属性,我们仍然可以很容易地创建一个:QObject:setProperty().从Qt4.2开始,动态设置一个不存在的属性值会导致此属性被创建。如:
nameLineEdit->setProperty(“mandatoryFiled”, true);
genderComboBox->setProperty(“mandatoryFiled”, true);
ageSpinBox->setProperty(“mandatoryFiled”, true);
对于一个有着许多line editors和comboboxes的表格应用来说,将mandatory fileds的背景色设为黄色是非常普遍的。我们假设将此约定应用到我们的应用,我们以下列开始我们的style sheet:
*[mandatoryFile="true"] {
background-color: yellow;
}
Style sheet不仅仅在控制颜色方面有用,它也可以用来调整部分的大小和位置。例如,以下的规则用来增加checkbox的大小,radio按钮的指示器(indicator)到20px,并确保指示器(indicator)和文本之间的间隙和8pixels。
QCheckBox::indicator, QRadioButton::indicator {
width: 20px;
height: 20px;
}
QCheckBox, QRadioButton {
spacing: 8px;
}
注意第一条rule的选择器(selector)语法。我们写的是QCheckBox而不是QCheckBox::indicator,我们本应该指定整个部件的大小而不是它们的指示器(indicator),第一条规则于图19.3中解释。
除了辅助控制,Style sheet也可以引用部件(widget)的状态。例如,当鼠标悬停于checkbox之上时,我们想将checkbox的文本颜色指定为白色:
QCheckBox:hover {
color: white;
}
状态由一个:指示,而subcontrol由::指示。我们可以依次指定多个状态,每个状态由:隔开。此种情况适用于所有状态的满足。例如,以下规则只适用于鼠标悬停于一个checked checkbox:
QCheckBox:checked:hover {
color: white;
}
如果我们想将之适用于任一状态,,应该使用多个选择器(selector),并使用,隔开:
QCheckBox:hover, QCheckBox:checked {
color: white;
}
逻辑非用!表示:
QCheckBox: !checked {
color: blue;
}
状态可与子控制相结合:
QComboBox::drop-down:hover {
image: url(:/images/downarrow_bright.png);
}
Style sheet可与其它技术相结合来表达更复杂的自定义。例如,假如我们想放置一个小“erase”按钮到一个QLineEdit框中,于文本的左边。这涉及到创建一个EraseButton类,将之放于QLineEdit之上(如,使用Layout),并且为输入文本预留空间,防止文本与按钮冲突。用子类化QStyle来实现很不方便,这意味着我们还必须子类化此应用使用的每种风格(QWindowsVistaStyle, QPlastiqueStyle, etc).使用style sheet只需以下几行文本就可以实现:
QLineEdit {
padding: 0px 15px 0px 0px;
}
padding属性让我们指定部件的top, right, bottom, lef填充。此填充被插入在QLineEdit的文本的框架之间。CSS也定义了padding-top, padding-right, padding-bottom和padding-left,以便我们想单独设定某一值。如:
QLineEdit {
padding-right: 16px;
}
如可以使用Style sheet自定义的Qt widget一样, QLineEdit支持box模型(box model)如图19.6所示. 此模型指定了四个影响部件布局和渲染的矩形。
- contents rectangle是最内层的矩形。这是实际内容绘画的地方。
- padding rectangle包围contents rectangle。它将padding属性指定的任何填充都计算在内。
- border rectangle又包围着padding rectangle。它为边框预留空间。margin rectangle是最外层的矩形。它包围着border rectangle并将任何指定的margin计算在内。
- 对于一个没有padding, 没有border,没有margin的一个普通部件来说,the four rectangle coincide exactly.
我们将呈现一个使用Style sheet自定义的风格–Candy. 图19.7为显示了Candy风格的部分部件。使用图19.6的box模型,Candy自定义了QLineEdits,QListViews, QPushButtons。接下来我们将一一呈现Style sheet的实现。整个的Style sheet的实现可以在本书提供的例子中找到,qss/Candy.qss位于Candy例子目录中。
对话框内的部分如图19.1所示。对话框本身的背景图使用如下规则设置:
QDialog { [R1]
background-image: url(:/images/background.png);
}
以下规则设置QLabels的color和font属性:
QLabel { [R2]
font: 9pt;
color: rgb(0, 0, 127);
}
以下规则定义了对话框部件类QLineEdit和QListView的外观:
QLineEdit,
QListView { [R3]
color: rgb(127, 0, 64);
background-color: rgb(255, 255, 241);
selection-color: white;
selection-background-color: rgb(191, 31, 127);
border: 2px groove gray;
border-radius: 10px;
padding: 2px 4px;
}
为了使QLineEdit和QListView更出彩,我们对常规和选中的文本都定义了前景色和背景色。除此之外我们为border属性定义了一个gray 2-pixel-wid “grooved” border。我们还可以单独指定border-width, border-style, border-color. 当然,还可以通过指定border-radius为边框倒角,我们已经使用过以10pixel为半径的border-radius了。图19.8提供了一个图表,它表示了边框和填充的改变。为保证文本不与圆角”碰撞”,我们指定了2pixel纵向的填充,4pixel的横向填充。对于QListViews来说,纵向的填充看起来不太对,所以我们使用以下规则将之覆盖:
QListView { [R4]
padding: 5px 4px;
}
当同一部件的同一属性被多次设置,只有最后一条规则起作用。
对QPushButtons来说,我们可以使用一个完全不同的方法。我们可以使用一个图片作为背景,而不是使用style sheet来画。为了了按钮更容易扩展,按钮背景使用CSS边框图片机制。
不像由background-image指定的背景图片,边框图片被切割成3x3的格子。如图19.9所示。当填充部件背景时,四个角A,C,G,I不变,其它5个部分被拉伸,平铺来填满可用空间。
边框图片由border-image属性指定,它要求我们输入图片文件名和”四刀”来定义9个格子。切割被定义为距离上,右,下,左的相素数。如下:
border-image: url(border.png) 4, 8, 12, 16;
使用边框图片的时候,我们必须显式指定border-width。正常情况下,border-width对应于切割的地方。除非四个角落将被拉伸或者压缩来适应border-width.以border.png为例,我们应该这样指定边框宽度:
border-width: 4px 8px 12px 16px;
现在我们知道了边框图片的工作机制,我们现在来看看它怎么用在风格化Candy QPushButtons。下面定义了按钮正常状态下是怎么被定义的:
QPushButton { [R5]
color: white;
font: bold 10pt;
border-image: url(:/images/button.png) 16;
border-width: 16;
padding: -16px 0px;
min-heigth: 32px;
min-width: 60px;
}
在以上style sheet中,四次切割距离34x34像素的边框图片边缘16像素,如图19.10所示。因为四个切割都是一致的,所以我们只需为四个切割写一个”16”,为边框宽度写“16px;
在QPushButton的例子中如图19.10,边框图片中的D,E,F区域被丢弃,因为按钮的高度不够而容不下。B,H被横向拉伸来填充额外的区域。
边框图片的标准用法是提供一个边框,将部件围绕其中。但是我们已经破坏了此标准用法,并使用它来创建了部件的背景。结果造成E区被丢弃,padding rectangle的高度为0.为了给按钮的文本创造空间,我们指定了一个为-16pixel的纵向padding。图19.11解释了此情形。如果我们用边框图片(border image)来创建一个实际的边框,文本与边框冲撞可能不会是我们想要的结果。但是我们用它来创建一个可扩展的(scalable)背景,我们可能更想让文本在之上而还是在它里面。
我们使用min-width和min-height来设置按钮内容的最小值。选定值保证为边框图片角预留了足够的空间,OK按钮一般要比刚好需要的值宽一些,好让它与Cancel按钮看起来更自然一些。
先前的QPushButton规则适用于所有的按钮。现在我们定义一些只适用于特定状态的按钮规则。
QPushButton:hover { [R6]
border-image: url(:/images/button-hover.png) 16;
}
当鼠标悬停于一个按钮之上,:hover状态为真,此时的规则将会覆盖之间的规则。我们使用此项技术指定一个更明亮的边框图片,来获得一个更漂亮的悬停效果。先前指定的其它按钮属性规则同样适合,只是border-image这个属性改变了。
QPushButton:pressed { [R7]
color: lightgray;
border-image: url(:/images/button-pressed.png) 16;
padding-top: -15px;
padding-bottom: -17px;
}
当用户按下按钮,我们前景色变为亮灰色,用一个更加暗边框图片,并通过把padding往下移动制造出按钮文本往下沉的效果。
我们最后的风格规则(style rules)将自定义QComboBox的外观。为了显示style sheets的操控性和精确性,我们将可编辑comboboxes与不可编辑comboboxes区别开来,如图19.12.只读combobox被渲染成一个按钮加上一个位于右边的向下箭头。然而,可编辑comboboxes由一个类似QLineEdit的部件和一个小圆角按钮组成。这意味着我们可以使用大部分定义好的QLineEdit,QListView和QPushButtons的规则。
用来定义QLineEdit部件的规则可以用来风格化可编辑comboboxes:
QComboBox:editable,
QLineEdit,
QListView { [R3A]
color: rgb(127, 0, 63);
background-color: rgb(255, 255, 241);
selection-color: white;
selection-background-color: rgb(191, 31, 127);
border: 2px groove gray;
padding: 2px 4px;
}
为QPushButton正常状态定义的规则也适用于只读comboboxes:
QComboBox: !editable,
QPushButton { [R5A]
color:white;
font: bold 10pt;
border-image: url(:/images/button.png) 16;
border-width: 16px;
padding: -16px 0px;
min-height: 32px;
min-width: 60px;
}
当悬停于一个只读combobox或悬停于一个可编辑comboboxes的下拉按钮的时候应该改变背景图片,就像对QPushButton的做法一样:
QComboBox:!editable:hover,
QComboBox:drop-down:editable:hover,
QPushButton:hover { [R6A]
border-image: url(:/images/button-hover.png) 16;
}
按一个只读combobox就像按一个QPushButton:
QComboBox:!editable,
QPushButton:pressed { [R7A]
color: lightgray;
border-image: url(:/images/button-pressed.png) 16;
padding-top: -15px;
padding-top: -17px;
}
重用R3, R5, R6, R7节省很多时间同时也使我们的风格保持一致。但我们还没有定义画下拉按钮的规则,定义如下:
QComboBox::down-arrow {
image: url(:/images/down-arrow.png); [R8]
}
我们自己提供下拉按钮的图片,所以会比标准的箭头大一些:
QComboBox::down-arrow { [R9]
top: 1px;
}
如果combobox是打开的,那么箭头向下移一个像素.
QComboBox * { [R10]
font: 9pt;
}
当用户点击一个combobox的时候,combobox显示一系统条款. R10保证combobox的弹出不会继承规则R5A指定的大字体。
QComboBox::drop-down:!editable { [R11]
subcontrol-origin: padding;
subcontrol-position: center right;
width: 11px;
height: 6px;
background: none;
}
使用subcontrol-origin和subcontrol-position属性,我们将下拉箭头放在只读combobox使用的右方填充矩形的纵向中心。除此之外,我们设置它的大小与文本按钮内容对应,11x6pixel大小的图片,我们禁止它用背景图,因为我们的下拉按钮只由下拉箭头组成。
QComboBox: !editable { [R12]
padding-right: 15px;
}
至于只读combobox,我们指定15pixel的填充来保证文本不会覆盖下拉箭头。图19.13展示了尺寸的作用。
对于可编辑combobox来说,我们需要配置下拉按钮使之看起来像小按钮。
QComboBox::drop-down:editable { [R13]
border-image: url(:/images/button.png) 16;
border-width: 10px;
subcontrol-origin: margin;
subcontrol-position: center right;
width: 7px;
height: 6px;
}
我们指定边框图片为button.png。然而,这些我们指定边框宽度为10pixel而不是16pixel,使图片更小一些。指定内容的固定大小为横向7pixel,纵向6pixel。图19.14为我们做法示意图。
如果combobox展开,我们为下拉箭头使用一个更暗的图片:
QComboBox::drop-down:editable:open { [R14]
border-image: url(:/images/button-pressed.png) 16;
}
为给下拉按钮提供空间,我们为可编辑combobox指定右边缘大小为29pixel.如19.15所示。
QComboBox:editable {
margin-right: 29px;
}
现在我们已经完成了Candy style sheet的创建。此style sheet大约100行,使用了几张自定义图片,我们得到的效果是一个完全不同的对话框。
使用style sheet来创建自己风格会经历很多困难和错误, 尤其对于没有使用过CSS的人来说.