前言
Qt中改变现有的控件的外观有多种方式,最常见的是使用样式表,根据样式表中的各种选择器并对属性、状态和sub-control进行样式的修改,也是比较方便的方式。最近由于要使用cef进行一个多标签浏览器的开发,需要修改tab的外观,这里使用QStyle来进行修改。
QStyle
简单来说就是Qstyle是一个抽象类,封装了不同平台上的GUI的外观。对于QStyle的详细介绍,请参考其他的资料或者Qt的帮助文档,百度一大堆。
QProxyStyle实现了QStyle所有的抽象接口,并且默认保持系统风格。
这里我们继承一个QProxStyle,然后实现其中两个接口便可实现。
第一个是sizeFromContent,返回控件的大小,函数原型如下:
virtual QSize sizeFromContents(ContentsType type, const QStyleOptio *option, const QSize &size, const QWidget *widget) const
第二个是drawControl,用于绘制控件的元素。函数原型如下:
virtual void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = Q_NULLPTR) const
效果图
绘制代码中的思路
比较笨的控制大小,数格子!!
由windows自带的画图数格子,然后用整体的大小确定比例,用比例绘制比较妥。
方式1
很明显了吧,
方式2
其实和方式1一样,只是椭圆不一样。高度比较小的时候,两者看着都差不多,我都试过。
方式3
二次贝塞尔曲线,就很简单。绘制出来可以说和chome基本一样
效果:
代码:
void Widget::drawShape1(QPainter *p, const QRect &drawRect)
{
QRectF r(drawRect);
qreal per = r.height() / 8.0;
qreal topMargin = 4.0;
QPainterPath path;
path.moveTo(r.bottomLeft() - QPointF(per, 0));
path.quadTo(QPointF(r.left(), r.bottom()),
QPointF(r.left(), r.bottom() - per));
path.lineTo(r.left(), r.top() + per + topMargin);
path.quadTo(QPointF(r.left() , r.top() + topMargin),
QPointF(r.left() + per, r.top() + topMargin));
path.lineTo(r.right() - per, r.top() + topMargin);
path.quadTo(QPointF(r.right(), r.top() + topMargin),
QPointF(r.right(), r.top() + per + topMargin));
path.lineTo(r.right(), r.bottom() - per);
path.quadTo(QPointF(r.right(), r.bottom()),
r.bottomRight() + QPointF(per, 0));
p->setRenderHint(QPainter::Antialiasing);
p->setBrush(Qt::green);
p->drawPath(path);
}
void Widget::drawShape2(QPainter *p, const QRect &drawRect)
{
QRectF r(drawRect);
qreal per = r.height() / 8.0;
qreal topMargin = 4.0;
QPainterPath path;
path.moveTo(r.bottomLeft() + QPointF(per, 0));
path.quadTo(QPointF(r.left(), r.bottom()),
QPointF(r.left(), r.bottom() - per));
path.lineTo(r.left(), r.top() + per + topMargin);
path.quadTo(QPointF(r.left(), r.top() + topMargin),
QPointF(r.left() - per, r.top() + topMargin));
path.lineTo(r.right() - per, r.top() + topMargin);
path.quadTo(QPointF(r.right(), r.top() + topMargin),
QPointF(r.right(), r.top() + per + topMargin));
path.lineTo(r.right(), r.bottom() - per);
path.quadTo(QPointF(r.right(), r.bottom()),
r.bottomRight() + QPointF(per, 0 ));
p->setRenderHint(QPainter::Antialiasing);
p->setBrush(Qt::red);
p->drawPath(path);
}
实现代码
控件大小
QSize NewChromeTabStyle::sizeFromContents(QStyle::ContentsType ct, const QStyleOption *opt,
const QSize &contentsSize, const QWidget *w) const
{
// return QProxyStyle::sizeFromContents(ct, opt, contentsSize, w);
QSize s = QProxyStyle::sizeFromContents(ct, opt, contentsSize, w);
if (ct == QStyle::CT_TabBarTab) {
// s.transpose();
s.rwidth() = contentsSize.width();
s.rheight() = 32;
}
return s;
}
绘制样式
void NewChromeTabStyle::drawControl(QStyle::ControlElement element, const QStyleOption *option,
QPainter *painter, const QWidget *w) const
{
painter->save();
painter->setPen(Qt::red);
// painter->drawRect(w->rect());
painter->restore();
switch (element) {
case CE_TabBarTabLabel:
{
const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option);
QRect textRect = subElementRect(QStyle::SE_TabBarTabText, tab, w);
if (tab->state & QStyle::State_Selected) {
painter->save();
painter->setPen(QPen("#000000"));
QTextOption option(Qt::AlignLeft | Qt::AlignVCenter);
option.setWrapMode(QTextOption::NoWrap);
painter->drawText(textRect, tab->text, option);
painter->restore();
}
else if(tab->state & QStyle::State_MouseOver) {
painter->save();
painter->setPen(QPen("#000000"));
QTextOption option(Qt::AlignLeft | Qt::AlignVCenter);
option.setWrapMode(QTextOption::NoWrap);
painter->drawText(textRect, tab->text, option);
painter->restore();
}
else
{
painter->save();
painter->setPen(QPen("#666666"));
QTextOption option(Qt::AlignLeft | Qt::AlignVCenter);
option.setWrapMode(QTextOption::NoWrap);
painter->drawText(textRect, tab->text, option);
painter->restore();
}
}
break;
case CE_TabBarTabShape:
{
const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option);
// Set Anti-aliasing effect
painter->setRenderHint(QPainter::Antialiasing);
QRectF shapeRect = tab->rect;
qreal arcLen = shapeRect.height() / 2;
if (tab->state & QStyle::State_Selected) {
painter->save();
QPen pen(Qt::red);
pen.setWidthF(0.5);
painter->setPen(pen);
// painter->drawRect(shapeRect);
painter->restore();
painter->save();
shapeRect.setX(shapeRect.x() - arcLen);
shapeRect.setWidth(shapeRect.width() + arcLen *3/ 4);
QPainterPath path;
QRectF arcRect1(shapeRect.x(),
shapeRect.y() + shapeRect.height() / 3,
arcLen,
shapeRect.height() * 2 / 3);
path.moveTo(QPointF(arcRect1.x(),
arcRect1.y() + arcRect1.height()));
path.lineTo(QPointF(arcRect1.x() + arcRect1.width() / 2,
arcRect1.y() + arcRect1.height()));
// Starting from 270°, returning to the arc of 90°
path.arcTo(arcRect1, 270.0, 90.0);
path.lineTo(QPointF(arcRect1.x() + arcRect1.width(),
arcRect1.y()));
QRectF arcRect2(shapeRect.x() + arcRect1.width(),
shapeRect.y(),
arcLen,
shapeRect.height() * 2 / 3);
// Starting from 180°, returning to the arc of -90°
path.arcTo(arcRect2, 180.0, -90.0);
path.lineTo(QPointF(arcRect2.x() + shapeRect.width() - 2 * (arcRect2.x() + arcRect2.width()) + arcRect2.width() / 2,
arcRect2.y()));
QRectF arcRect3(shapeRect.x() + shapeRect.width() - 2 * arcRect1.width(),
shapeRect.y(),
arcRect1.width(),
shapeRect.height() * 2 / 3);
path.arcTo(arcRect3, 90.0, -90.0);
path.lineTo(QPointF(arcRect3.x() + arcRect3.width(),
arcRect3.y() + arcRect3.height()));
QRectF arcRect4(arcRect3.x() + arcRect3.width(),
arcRect3.y() + arcRect3.height() / 2,
arcRect1.width(),
shapeRect.height() * 2 / 3);
path.arcTo(arcRect4, 180.0, 90.0);
path.lineTo(QPointF(arcRect1.x(),
arcRect1.y() + arcRect1.height()));
painter->setPen(Qt::NoPen);
painter->setBrush(Qt::white);
QPolygonF polygon = path.toFillPolygon();
painter->drawPolygon(polygon);
painter->restore();
}else if(tab->state & QStyle::State_MouseOver)
{
// return;
painter->save();
QPen pen(Qt::black);
pen.setWidthF(0.5);
painter->setPen(pen);
// painter->drawRect(shapeRect);
painter->restore();
painter->save();
shapeRect.setX(shapeRect.x() - arcLen);
shapeRect.setWidth(shapeRect.width() + arcLen *3/ 4);
QPainterPath path;
QRectF arcRect1(shapeRect.x(),
shapeRect.y() + shapeRect.height() / 3,
arcLen,
shapeRect.height() * 2 / 3);
path.moveTo(QPointF(arcRect1.x(),
arcRect1.y() + arcRect1.height()));
path.lineTo(QPointF(arcRect1.x() + arcRect1.width() / 2,
arcRect1.y() + arcRect1.height()));
// Starting from 270°, returning to the arc of 90°
path.arcTo(arcRect1, 270.0, 90.0);
path.lineTo(QPointF(arcRect1.x() + arcRect1.width(),
arcRect1.y()));
QRectF arcRect2(shapeRect.x() + arcRect1.width(),
shapeRect.y(),
arcLen,
shapeRect.height() * 2 / 3);
// Starting from 180°, returning to the arc of -90°
path.arcTo(arcRect2, 180.0, -90.0);
path.lineTo(QPointF(arcRect2.x() + shapeRect.width() - 2 * (arcRect2.x() + arcRect2.width()) + arcRect2.width() / 2,
arcRect2.y()));
QRectF arcRect3(shapeRect.x() + shapeRect.width() - 2 * arcRect1.width(),
shapeRect.y(),
arcRect1.width(),
shapeRect.height() * 2 / 3);
path.arcTo(arcRect3, 90.0, -90.0);
path.lineTo(QPointF(arcRect3.x() + arcRect3.width(),
arcRect3.y() + arcRect3.height()));
QRectF arcRect4(arcRect3.x() + arcRect3.width(),
arcRect3.y() + arcRect3.height() / 2,
arcRect1.width(),
shapeRect.height() * 2 / 3);
path.arcTo(arcRect4, 180.0, 90.0);
path.lineTo(QPointF(arcRect1.x(),
arcRect1.y() + arcRect1.height()));
painter->setPen(Qt::NoPen);
painter->setBrush(QColor("#FBFCFC"));
QPolygonF polygon = path.toFillPolygon();
painter->drawPolygon(polygon);
painter->restore();
}else
{
painter->save();
shapeRect.setX(shapeRect.x() - arcLen);
shapeRect.setWidth(shapeRect.width() + arcLen / 2);
QPen pen(Qt::black);
pen.setWidthF(0.5);
painter->setPen(pen);
/// Draw a dividing line on the left
painter->drawLine(QPointF(shapeRect.x() + arcLen*2/3 -1, shapeRect.height() / 3),
QPointF(shapeRect.x() + arcLen*2/3 -1, shapeRect.height() * 2 / 3));
/// Draw a dividing line on the right
// painter->drawLine(baseR.x() + baseR.width() - arcLen,
// baseR.height() / 4,
// baseR.x() + baseR.width() - arcLen,
// baseR.height() * 3 / 4);
painter->restore();
}
}
break;
default:
QProxyStyle::drawControl(element, option, painter, w);
break;
}
}
其他
tab的大小
在sizeFromContents函数返回的控件尺寸可以设置成固定的,也可以直接返回默认。
在设置成默认的尺寸以后,每个tab的大小都固定了,这样对于Tab文字比较少的那种,看起来不太友好。仿照嘛,就要仿照得像一点。这里可以结合样式表来动态调整tab的大小。在每次增加和移除tab的时候调用这个函数自动调整tab的大小。
void MainWindow::AdjustTabWidth()
{
int max_tabwidget_width = width() - 150;
tabWidget->tabBar()->setMaximumWidth(max_tabwidget_width);
// get the average with of the tab
int average_width = tabWidget->count() > (max_tabwidget_width / MaxTabWidth) ?
max_tabwidget_width / tabWidget->count() : MaxTabWidth;
tabWidget->setStyleSheet(QString::fromUtf8("BTabWidget::pane{"
"border:none;"
"}"
"BTabWidget::tab-bar {"
"left: 6px;"
"}"
"QTabBar::tab {"
"min-width:78px;"
"width:%1px;"
"}"
).arg(average_width));
}
老版本chrome标签样式
也可以用这个绘制老版本的谷歌浏览器的样式。效果图如下。方式也一样,代码就不贴了,也没绘制好,后面的Demo中有实现。
没画好。。。需要这种效果的再改改吧。
最后
完整的Demo: 源代码.
测试Demo。比较乱,如果有需要就凑合着看吧!!🙂