Qt自定义控件的两个思路

目录

      • 前言
      • 1、用Designer设计
      • 2、 用QPainter绘制
      • 3、支持样式表

前言

在用Qt框架开发程序时,面对复杂的UI设计Qt提供的组件已无法实现相应的效果,这时就需要我们根据UI效果图自定义控件来满足需求。本文主要记录了实现自定义控件的两个思路,一个是利用Qt已有的UI组件在Designer中设计自定义控件,另一个是利用QPainer绘制自定义控件。

1、用Designer设计

Qt自定义控件的两个思路_第1张图片

上图是一个将自定义卡片组件的多个实例添加到QTableWidget列表上的效果,下面以该卡片组件为例,利用Designer来完成组件的设计。

  • 1)创建一个设计师界面类,类名根据实际需求取即可,这里命名为:ZCustomCardWidget
  • 2)分析组件的布局结构,从效果图上能够看到这是一个上下结构的组件,具体的布局划分见下图
    Qt自定义控件的两个思路_第2张图片
  • 3)在Designer中打开zcustomcardwidget.ui,按照上图的结构拖拽控件到画布上,这里最上面是两个QLabel,一个用来表示图标,一个用来表示标题;中间部分是一个QLabel,表示圆角图片;再往下是一个QLabel,表示描述文本;最底部是两个QToolButton,按钮,按钮设置为图标在上,文本在下的显示样式。最终得到如下图所示的组件效果,为了清楚地查看组件的整体样式,这里在ui中添加了一些qss样式代码。
    Qt自定义控件的两个思路_第3张图片
    样式代码如下:
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,这里就不再赘述了。

2、 用QPainter绘制

Qt自定义控件的两个思路_第4张图片
当用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处理鼠标事件,为了体验更好,当鼠标在某个颜色的圆环停住时,对应的饼图半径增大一圈。
最终效果如下图所示

3、支持样式表

在开发中,UI页面的样式一般写在样式表中,方便统一管理,而自定义控件默认是不支持样式表的,在样式表中通过自定义控件的类名来修改控件的各种样式属性没有效果。若要让自定义控件支持样式表,需要再paintEvent函数中添加几行代码。

void ZAnnularChart::paintEvent(QPaintEvent *event)
  {
  	   ....
      QStyleOption opt;
      opt.init(this);
      QPainter p(this);
      style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
  }

有了上面几行代码我们就可以在样式表中,设置自定义控件的样式了。

你可能感兴趣的:(C++,qt,ui,开发语言)