QComboBox文字居中的一种解决办法
本文会尽可能解释一些内容,所以会显得有点长。
关于QComboBox文字的居中,网上提供的主流方案是,通过 QComboBox::setEditable 设置为可编辑状态,再获取 QComboBox::lineEdit 设置为只读并居中文字。
该方案的缺点在于改变了不可编辑状态下QComboBox的交互行为,例如,点击文本区域不会弹出列表,原因是QComboBox的一些交互行为依赖于 editable 属性,虽然可以通过自定义鼠标事件等方式完善,但不同平台上的交互又有细微的区别。
对此,这里提出另一种解决方案,尽可能避免影响QComboBox原生的交互,有点复杂,从思路上,也许能解决更多的类似问题。
一些基本原理
Qt虽然提供了 style sheet 方式设置控件样式,但Qt并没有将对应的QStyleSheetStyle公开。Qt的基础控件都是使用了QStyle接口绘制的,在使用 style sheet 时,大部分样式,如颜色、字体、边框等,在绘制时会从样式表中读取并覆盖QStyleOption,用户代码无法感知到该过程,特别是当设置了 hover、pressed 动作下的样式时。所以绘制的过程只能交给QStyle去绘制控件。
不过,QStyle接口并不关心QPainter参数的绘制目标,例如可以通过 QWidget::render 来截取控件内容。Qt也没有限制在A控件上绘制B控件。
另外,尽管Qt文档里说明了不同控件的style sheet应用范围,但实际样式定义了就会存在,即使样式对某些控件本身无效。
解决方案
所幸,QComboBox和QPushButton的绘制事件比较简单,不涉及私有接口,可以借用一些源码里的绘制逻辑,所以重写 QComboBox::paintEvent
// 该代码是Qt默认实现,直接拷贝一份
QStylePainter painter(this);
painter.setPen(palette().color(QPalette::Text));
// draw the combobox frame, focusrect and selected etc.
QStyleOptionComboBox opt;
initStyleOption(&opt);
painter.drawComplexControl(QStyle::CC_ComboBox, opt);
if (currentIndex() < 0)
opt.palette.setBrush(QPalette::ButtonText, opt.palette.brush(QPalette::ButtonText).color().lighter());
// draw the icon and text
//painter.drawControl(QStyle::CE_ComboBoxLabel, opt); //不需要再绘制文本内容
// 以上代码是Qt默认实现
painter.end();
// 在文本区域绘制按钮文本
QPainter painter2(this);
QStyleOptionButton buttonOpt;
buttonOpt.initFrom(this); // 主要是一些窗口状态,hover、enable等
QRect editRect = this->style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this);
buttonOpt.rect = editRect; // 设置按钮区域为QComboBox的文本区域
buttonOpt.text = opt.currentText; // 设置文本
// 绘制QStyle::CE_PushButtonLabel
this->style()->drawControl(QStyle::CE_PushButtonLabel, &buttonOpt, &painter2, this);
需要注意的是:
- 最后通过 QStyle::drawControl 仅绘制 QStyle::CE_PushButtonLabel ,是由于 QStyle::CE_PushButton 会绘制边框等样式,如果QComboBox设置了边框,会重复画一遍。
- QStyle::drawControl 最后一个参数必须传QComboBox对象,样式需要通过窗口来获取。
- 可以在QComboBox的样式中设置 text-align 来控制文本的对齐,虽然对QComboBox本身无效,但对QPushButton有效
- 通过设置按钮文本绘制区域来控制位置,但可能会存在某些特殊的样式没有计算在内。
结果截图
hover状态下的样式同样能够保留,由于未增加其他子控件,交互逻辑也没有变化。
解决过程
一开始考虑使用组合的方式,在QComboBox上覆盖QLabel来实现居中,但QStyle在应用的过程中,针对QComboBox子控件的样式做了过滤,样式并不会生效。Qt有个私有的属性 Qt::WA_StyleSheet 可以跳过过滤,但在重设 style sheet 时可能引起异常。
还有一种比较麻烦的方案是,默认实现里,painter.drawControl(QStyle::CE_ComboBoxLabel, opt) 这段执行前,通过字体信息计算当前文本宽度,修改opt的rect属性,使默认的文本绘制刚好显示在中间。