在用Qt框架开发程序时,面对复杂的UI设计Qt提供的组件已无法实现相应的效果,这时就需要我们根据UI效果图自定义控件来满足需求。本文主要记录了实现自定义控件的两个思路,一个是利用Qt已有的UI组件在Designer中设计自定义控件,另一个是利用QPainer绘制自定义控件。
上图是一个将自定义卡片组件的多个实例添加到QTableWidget列表上的效果,下面以该卡片组件为例,利用Designer来完成组件的设计。
QLabel#lb_titleIcon
{
border-image: url(:/images/gengduo.png);
}
QLabel#lb_img
{
border-radius: 10px;
border-image: url(:/images/img_bg.jpg);
}
QToolButton
{
border: none;
}
组件布局好之后,接下来为组件添加一些接口来动态设置组件中各部分的图片和文本,接口代码如下:
#ifndef ZCUSTOMCARDWIDGET_H
#define ZCUSTOMCARDWIDGET_H
#include
namespace Ui {
class ZCustomCardWidget;
}
class ZCustomCardWidget : public QWidget
{
Q_OBJECT
public:
enum ZButtonFlag
{
ZButtonFlagLeft,
ZButtonFlagRight
};
explicit ZCustomCardWidget(QWidget *parent = nullptr);
~ZCustomCardWidget();
void setTitleIcon(const QString &iconPath);
void setTitle(const QString &title);
void setImage(const QString &imagePath);
void setDescription(const QString &desc);
void setupLeftBtn(const QString &title, const QString &iconPath);
void setupRightBtn(const QString &title, const QString &iconPath);
void setRightBtnVisible(bool visible);
signals:
void sign_toolButtonClicked(ZButtonFlag flag);
private slots:
void slot_toolButtonClicked();
private:
Ui::ZCustomCardWidget *ui;
};
#endif // ZCUSTOMCARDWIDGET_H
以上我们用Designer完成了自定义控件的设计,至于控件如何添加到QTableWidget中使用Q,这里就不再赘述了。
当用Designer不能直接设计所需的控件时,我们可以使用QPainter来绘制。以上图的圆环控件为例,新建一个QWidget的子类,命名为ZAnnularChart。
首先我们要分析控件的组成,可以看到该控件中心是一个白色的圆形,外层是一个不同颜色的圆环,这个圆环可以看做由一个饼图和上层一个比饼图半径小一些的圆形组成,每个颜色所占的角度大小跟数据相关,一圈是360度,数值越大占比就越大,。分析完控件的组成,我们再来分析如何利用QPainter提供的功能来实现控件的绘制,前提是我们熟悉QPainter提供的方法,如果不熟悉的话,这一步就很难进行,根据上面对控件的分析,可以用drawPie和drawEllipse接口来绘制。drawPie接口按照起始角度(startAngle)和角度大小(spanAngle)在给定的矩形内绘制饼图,drawEllipse接口能根据给定的圆心和半径绘制圆形。
下面是具体的代码:
#ifndef ZANNULARCHART_H
#define ZANNULARCHART_H
#include
#include
#include
class ZAnnularChart : public QWidget
{
Q_OBJECT
public:
explicit ZAnnularChart(QWidget *parent = nullptr);
void addData(int val, const QColor &color);
protected:
virtual void paintEvent(QPaintEvent *event);
virtual void mouseMoveEvent(QMouseEvent *event);
private:
QList<QColor> m_colorList;
QList<int> m_dataList;
int m_total;
int m_mouseAtDataIndex;
double m_step;
};
#endif // ZANNULARCHART_H
#include "zannularchart.h"
#include
#include
ZAnnularChart::ZAnnularChart(QWidget *parent)
: QWidget(parent)
, m_total(0)
, m_mouseAtDataIndex(-1)
{
setMouseTracking(true);
}
void ZAnnularChart::addData(int val, const QColor &color)
{
m_colorList.append(color);
m_dataList.append(val);
m_total += val;
m_step = 360.0 / m_total;
update();
}
void ZAnnularChart::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setPen(QColor(255,0,0));
painter.drawRect(rect());
#if 1
int width = rect().width();
int height = rect().height();
int radiusA = 90;
int radiusB = 95;
QRectF rectangle(width/2-radiusA, height/2-radiusA, 2 * radiusA, 2 * radiusA);
QRectF rectangle2(width/2-radiusB, height/2-radiusB, 2 * radiusB, 2 * radiusB);
int startAngle = 0;
int spanAngle = 0;
for (int i = 0; i < m_colorList.size(); ++i)
{
painter.setBrush(m_colorList[i]);
spanAngle = m_step * m_dataList[i] * 16;
if(i == m_mouseAtDataIndex)
{
painter.drawPie(rectangle2, startAngle, spanAngle);
}
else
{
painter.drawPie(rectangle, startAngle, spanAngle);
}
startAngle += spanAngle;
}
painter.setBrush(QColor(255,255,255));
painter.drawEllipse(QPoint(width/2,height/2), 50, 50);
double a = sin(30*M_PI/180);
qDebug() <<"sin(30/) : " << a;
#else
int startAngle = 0;
int spanAngle = 0;
int radiusA = 50;
int radiusB = 90;
int width = rect().width();
int height = rect().height();
QRectF rectA(width/2-radiusA, height/2-radiusA, 2 * radiusA, 2 * radiusA);
QRectF rectB(width/2-radiusB, height/2-radiusB, 2 * radiusB, 2 * radiusB);
for (int i = 0; i < m_dataList.size(); ++i) {
spanAngle = m_step * m_dataList[i];
QPainterPath pathA;
pathA.moveTo(width/2,height/2);
pathA.lineTo(width/2 + radiusA * cos(startAngle * M_PI/180), height/2 - radiusA * sin(startAngle * M_PI/180));
pathA.arcTo(rectA, startAngle, spanAngle);
pathA.closeSubpath();
QPainterPath pathB;
pathB.moveTo(width/2,height/2);
pathB.lineTo(width/2 + radiusB * cos(startAngle * M_PI/180), height/2 - radiusB * sin(startAngle * M_PI/180));
pathB.arcTo(rectB, startAngle, spanAngle);
pathB.closeSubpath();
QPainterPath path = pathB.subtracted(pathA);
painter.setPen(m_colorList[i]);
painter.drawPath(path);
startAngle += spanAngle;
}
#endif
}
void ZAnnularChart::mouseMoveEvent(QMouseEvent *event)
{
int startAngle = 0;
int spanAngle = 0;
int radiusA = 50;
int radiusB = 90;
int width = rect().width();
int height = rect().height();
QRectF rectA(width/2-radiusA, height/2-radiusA, 2 * radiusA, 2 * radiusA);
QRectF rectB(width/2-radiusB, height/2-radiusB, 2 * radiusB, 2 * radiusB);
m_mouseAtDataIndex = -1;
for (int i = 0; i < m_dataList.size(); ++i) {
spanAngle = m_step * m_dataList[i];
QPainterPath pathA;
pathA.moveTo(width/2,height/2);
pathA.lineTo(width/2 + radiusA * cos(startAngle * M_PI/180), height/2 - radiusA * sin(startAngle * M_PI/180));
pathA.arcTo(rectA, startAngle, spanAngle);
pathA.closeSubpath();
QPainterPath pathB;
pathB.moveTo(width/2,height/2);
pathB.lineTo(width/2 + radiusB * cos(startAngle * M_PI/180), height/2 - radiusB * sin(startAngle * M_PI/180));
pathB.arcTo(rectB, startAngle, spanAngle);
pathB.closeSubpath();
QPainterPath path = pathB.subtracted(pathA);
if(path.contains(event->pos()))
{
m_mouseAtDataIndex = i;
break;
}
startAngle += spanAngle;
}
update();
}
在上面的代码中,实现了QWidget的两个虚函数paintEvent,mouseMoveEvent。其中paintEvent绘制控件,mouseMoveEvent处理鼠标事件,为了体验更好,当鼠标在某个颜色的圆环停住时,对应的饼图半径增大一圈。
最终效果如下图所示
在开发中,UI页面的样式一般写在样式表中,方便统一管理,而自定义控件默认是不支持样式表的,在样式表中通过自定义控件的类名来修改控件的各种样式属性没有效果。若要让自定义控件支持样式表,需要再paintEvent函数中添加几行代码。
void ZAnnularChart::paintEvent(QPaintEvent *event)
{
....
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
有了上面几行代码我们就可以在样式表中,设置自定义控件的样式了。