继承QWidget使用QPainter自定义二维图形控件【Qt学习】

通过阅读该文章,将了解本文所说的二维图形控件的基本概念、为何要自定义二维图形控件、如何自定义二维图形控件。

该文章将首先进行一些书面化的描述,然后再通过继承QWidget,使用QPainter自定义折线绘制控件进行演示,控件效果如下图:

(注:该例子仅仅是为了说明如何实现简单的自定义绘制,其实折线绘制我们一般是不会选择自定义实现,完全可以使用更加优秀的第三方库进行实现,如:QCustomPlot)

一、理论部分

1. 二维图形控件基本概念

控件:控件是指对数据和方法的封装,此处所指的“二维图形控件”特指通过Qt-QPainter绘制的以二维图形的形式展示给用户的交互控件

2. 为什么要自定义二维图形控件

Qt中有许多自定义的控件,比如下拉框QComboBox、按钮控件QPushButton、复选框QCheckBox等等,基本能满足我们大多数的界面开发需求,如下图所示:

但是,有时候我们会面临一些新的需求,比如说:”我需要个仪表盘“、”我需要个折线图“、”我需要个进度提示控件“…

然后你就会发现:
a. 现存的Qt控件无法满足需求
b. 没有找到合适的第三方开源控件

这个时候就需要自定义控件啦!!!!

3. 自定义窗口部件的常见思路

我整理了下就我理解的自定义部件的常见思路,如下图所示,主要列出了三种可行的方式:
a. 对现存的窗口部件进行组合,即最常规的方式,比如用Designer拖动现成控件设计对话框,不需要自己写控件的实现方案,只需要处理交互逻辑;
b. 子类化相关窗口部件,就是继承现成的一些控件,修改或是扩展相关的功能以满足需求,如重写QComboBox;
c. 子类化QWidget, 其实和b所说的一致,只是灵活性更高,我们要完全根据我们的需求去定义数据、外观、行为等。

该文章例子演示部分主要说明子类化QWidget如何进行。

二、例子演示

目前我暂时不一步一步的去进行代码说明,我直接将折线绘制的代码附上,现在仅仅是进行简单的实现绘制,后期有时间再实现一些交互。

一般的 ,实现这么一个功能较单一的控件, 将数据和行为都定义在控件内即可,一般步骤是:
a . 根据需要思考我们的数据结构,由于绘制的是折线,我们就可使用QVector m_polylineData来装载折线数据即可【即输入数据】
b. 下一步就是考虑如何将a中的数据绘制到界面,这一步需要我们根据窗体和数据信息,作一些数据计算和映射,最后使用画笔QPainiter在paintEvent中绘制即可
c. 绘制好后,我们需要考虑的就是用户看见这些数据,可能会进行的操作,如拖动、缩放等【TODO该例子暂未实现】

PolylineWidget.h:

/*** 名称:折线绘制控件* 日期:2021.02.08* 作者:zl** 说明:*      该控件仅仅是对如何继承QWidget并根据需求定义外观、行为进行一个简单的说明** 整体布局:* 其中:*     m_viewRect主要用于绘制折线*     PolylineWidget和m_viewRect之间就是边矩,我们将在这个区域绘制标题信息和标尺信息**                  PolylineWidget* ——————————————————————————————————————————————*|                     主标题                 |*|           ----------------------------     |*|          :                          :    |*|          : 折线绘制区域(m_viewRect) :    |*| 垂直标题 :                          :    |*|          :                          :    |*|          :---------------------------     |*|                 水平标题                   |* —————————————————————————————————————————————* */#ifndef POLYLINEWIDGET_H
#define POLYLINEWIDGET_H#include <QWidget>
class PolylineWidget:public QWidget
{Q_OBJECTpublic:explicit PolylineWidget(QWidget *parent = 0);~PolylineWidget();/*** @brief 设置折线图数据* @param polyline 输入折线数据点集*/void setPolylineData(const QVector<QPointF>& polyline);/*** @brief 设置主标题* @param title 输入 主标题*/void setMainTitleText(const QString& title);/*** @brief 设置横坐标显示文字* @param HText 输入 横坐标显示文字*/void setHCoordinateText(const QString& HText);/*** @brief 设置纵坐标显示文字* @param VText 输入 纵坐标显示文字*/void setVCoordinateText(const QString& VText);/*** @brief 设置折线绘制区与窗体边界的间隔* @param left 输入 左间距* @param top 输入 上间距* @param right 输入 右间距* @param bottom 输入 下间距*/void setMargin(int left, int top, int right, int bottom);/*** @brief 【未实现】刷新重绘,用于用户手动刷新*/void refresh();protected:/*** @brief 窗体大小改变,每次改变窗体大小后将重新计算边矩*/virtual void resizeEvent(QResizeEvent* event);/*** @brief 重绘事件,控制主要的绘制流程*/virtual void paintEvent(QPaintEvent *event);/*** @brief 绘制折线* @param p 输入 画笔*/void drawPolyline(QPainter *p);/*** @brief 绘制横坐标标尺* @param p 输入 画笔*/void drawHCoordinate(QPainter *p);/*** @brief 绘制纵坐标标尺* @param p 输入 画笔*/void drawVCoordinate(QPainter *p);/*** @brief 绘制横纵坐标显示文字* @param p 输入 画笔*/void drawCoordinateText(QPainter *p);private:/*** @brief 逻辑数值X转桌面像素坐标X* @param lpx 输入 逻辑坐标X* @return*/int lpxTodpx(double lpx);/*** @brief 桌面像素X坐标转逻辑坐标X* @param dpx 输入 桌面像素坐标* @return*/double dpxTolpx(int dpx);/*** @brief 逻辑坐标Y转桌面坐标* @param lpy 输入 逻辑y值* @return*/int lpyTodpy(double lpy);/*** @brief 桌面y坐标转逻辑y坐标* @param dpy 输入 桌面y坐标* @return*/double dpyTolpy(int dpy);/*** @brief 设置绘制的折线数据后需要计算逻辑数值范围,且对数据进行排序*/void initData();private:///< 折线图数据,即需要绘制的数据QVector<QPointF> m_polylineData;///< 输入数值的最值范围 横X 纵Ydouble m_maxX;double m_minX;double m_maxY;double m_minY;///< 折线图横坐标显示文本QString m_HCoordinateText;///< 折线图纵坐标显示文本QString m_VCoordinateText;///< 主标题,即顶部的标图QString m_mainTitle;///< 绘图区域,用于绘制折线图形的区域,QRect m_viewRect;///< 绘制区域与窗体边界的间距QRect m_marginRect;
};#endif // POLYLINEWIDGET_H

PolylineWidget.cpp:

#include "polylinewidget.h"
#include"qpainter.h"#include <math.h>
#include <QDebug>/*********工具方法**************//// 比较函数,升序排列
bool compare(const QPointF& p1, const QPointF& p2){return p1.x() < p2.x();
}/// 调整步数
double gAdaptStep(double step)
{double exp;exp = 1.0;while(step < 10){step *= 10;exp /= 10;}while(step > 100){step /= 10;exp *= 10;}step = qRound(step/5)*5*exp;return step;
}PolylineWidget::PolylineWidget(QWidget *parent):QWidget(parent)
{// 最值初始化m_maxX = m_maxY = -HUGE_VAL;m_minX = m_minY = HUGE_VAL;// 坐标显示文字m_HCoordinateText = m_VCoordinateText ="";// 默认边距m_marginRect.setLeft(20);m_marginRect.setTop(20);m_marginRect.setRight(20);m_marginRect.setBottom(20);}PolylineWidget::~PolylineWidget()
{}void PolylineWidget::setPolylineData(const QVector<QPointF> &polyline)
{m_polylineData = polyline;initData();
}void PolylineWidget::setMainTitleText(const QString &title)
{m_mainTitle = title;
}void PolylineWidget::setHCoordinateText(const QString &HText)
{m_HCoordinateText = HText;}void PolylineWidget::setVCoordinateText(const QString &VText)
{m_VCoordinateText = VText;
}void PolylineWidget::setMargin(int left, int top, int right, int bottom)
{// 将边矩保存到成员变量m_marginRect.setLeft(left);m_marginRect.setRight(right);m_marginRect.setTop(top);m_marginRect.setBottom(bottom);// 更新绘图区int w = width() - m_marginRect.left() - m_marginRect.right();int h = height() - m_marginRect.top() - m_marginRect.bottom();m_viewRect.setRect(m_marginRect.left(), m_marginRect.top(), w,h);
}void PolylineWidget::refresh()
{}void PolylineWidget::resizeEvent(QResizeEvent *event)
{// 更新绘图区int w = width() - m_marginRect.left() - m_marginRect.right();int h = height() - m_marginRect.top() - m_marginRect.bottom();m_viewRect.setRect(m_marginRect.left(), m_marginRect.top(), w,h);
}void PolylineWidget::paintEvent(QPaintEvent *event)
{if(m_maxX <= m_minX || m_maxY <= m_minY){return;}if(m_viewRect.width() < 5 || m_viewRect.height() < 5){return;}QPainter p(this);p.setBrush(QBrush(Qt::white));p.drawRect(m_viewRect);drawPolyline(&p);drawHCoordinate(&p);drawVCoordinate(&p);drawCoordinateText(&p);
}void PolylineWidget::drawPolyline(QPainter *p)
{if(m_polylineData.isEmpty()) return;p->save();p->setBrush(QBrush(Qt::black));QPoint p1, p2;p1.setX(lpxTodpx(m_polylineData.first().x()));p1.setY(lpyTodpy(m_polylineData.first().y()));// 给点画一个小圆p->drawEllipse(p1, 2, 2);for(int i=1; i<m_polylineData.count(); ++i){p2.setX(lpxTodpx(m_polylineData[i].x()));p2.setY(lpyTodpy(m_polylineData[i].y()));// 给点画一个小圆p->drawEllipse(p2, 2, 2);// 画折线p->drawLine(p1, p2);p1 = p2;}p->restore();
}void PolylineWidget::drawHCoordinate(QPainter *p)
{// 坐标标尺总数int rulerCount = 5;// 1)求出横标尺的逻辑间隔double deltx;int viewLeft, viewRight;viewLeft = m_viewRect.left();viewRight = m_viewRect.right();deltx = (dpxTolpx(viewRight) - dpxTolpx(viewLeft)) / rulerCount;deltx = gAdaptStep(fabs(deltx));// 调整步数// 2)得到开始绘制标尺的逻辑起点,和像素起点double lx; // 逻辑起点double dx; // 像素起点lx = dpxTolpx(viewLeft);lx = ceil(lx / deltx) * deltx; // 调整逻辑起点位置dx = lpxTodpx(lx); // 像素起点while(dx < viewLeft){// 当起点在视图区左侧之外时再向前移动一个步数lx += deltx;dx = lpxTodpx(lx);}// 3)开始绘制标尺及其文字int x, y;x = dx;y = m_viewRect.bottom();QFontMetrics fm = p->fontMetrics();while(x <= viewRight){// 得到显示数值QString str = QString::number(lx, 'f', 1);int strW = fm.width(str);// 绘制数值p->drawText(x - strW/2, y + 18, str);// 绘制标尺短线p->drawLine(x, y+5, x, y);// 修改步进lx += deltx;x = lpxTodpx(lx);}
}void PolylineWidget::drawVCoordinate(QPainter *p)
{// 坐标标尺总数int rulerCount = 5;// 1)求出纵标尺的逻辑间隔double delty;int viewBottom, viewTop;viewBottom = m_viewRect.bottom();viewTop = m_viewRect.top();delty = (dpyTolpy(viewTop) - dpyTolpy(viewBottom)) / rulerCount;delty = gAdaptStep(fabs(delty));// 2)得到开始绘制标尺的逻辑起点,和像素起点double ly; // 逻辑起点double dy; // 像素起点ly = dpyTolpy(viewBottom);ly = floor(ly / delty) * delty; // 调整起点位置dy = lpyTodpy(ly);while(dy > viewBottom){ly += delty;dy = lpyTodpy(ly);}// 3)开始绘制标尺及其文字int x, y;x = m_viewRect.left();y = dy;QFontMetrics fm = p->fontMetrics();int strH = fm.height();while(y >= viewTop){// 得到显示数值QString str = QString::number(ly, 'f', 1);int strW = fm.width(str);// 绘制数值p->drawText(x - strW - 10, y - strH/2,strW, strH, Qt::AlignCenter, str);// 绘制标尺短线p->drawLine(x, y, x-5, y);// 修改步进ly += delty;y = lpyTodpy(ly);}}void PolylineWidget::drawCoordinateText(QPainter *p)
{// 绘制主标题、横坐标标题、纵坐标标题QFontMetrics fm = p->fontMetrics();p->save();p->setFont(QFont(p->font().family(), 16));// 1)绘制主标题int mainTitleX, mainTitleY;mainTitleX = m_viewRect.left() + m_viewRect.width() / 2- fm.width(m_mainTitle) / 2;mainTitleY = m_viewRect.top()-5;p->drawText(mainTitleX, mainTitleY, m_mainTitle);// 2)绘制横坐标标题int hx, hy;hx = m_viewRect.left() + m_viewRect.width() / 2- fm.width(m_HCoordinateText) / 2;hy = m_viewRect.bottom() + m_marginRect.bottom()/*+ fm.height()*/;p->drawText(hx, hy, m_HCoordinateText);// 3) 绘制纵坐标int vx, vy;vx = m_viewRect.left() - m_marginRect.left() + fm.height();vy = m_viewRect.top() + m_viewRect.height() / 2+ fm.width(m_VCoordinateText) / 2;p->translate(vx, vy);p->rotate(-90);p->drawText(0, 0, m_VCoordinateText);p->resetTransform();p->restore();
}int PolylineWidget::lpxTodpx(double lpx)
{return m_viewRect.left() +m_viewRect.width() * (lpx - m_minX) / (m_maxX - m_minX);
}double PolylineWidget::dpxTolpx(int dpx)
{return (dpx -m_viewRect.left()) * (m_maxX - m_minX) / m_viewRect.width() + m_minX;
}int PolylineWidget::lpyTodpy(double lpy)
{return m_viewRect.bottom() - m_viewRect.height() * (lpy - m_minY) / (m_maxY - m_minY);
}double PolylineWidget::dpyTolpy(int dpy)
{return (m_viewRect.bottom() - dpy) * (m_maxY - m_minY) / m_viewRect.height() + m_minY;
}void PolylineWidget::initData()
{if(m_polylineData.isEmpty()){return;}// 对折线数据进行排序qSort(m_polylineData.begin(), m_polylineData.end(), compare);// 计算最值double maxX,minX,maxY,minY;maxX = maxY = -HUGE_VAL;minX = minY = HUGE_VAL;foreach (QPointF point, m_polylineData) {if(maxX < point.x()){maxX = point.x();}if(minX > point.x()){minX = point.x();}if(maxY < point.y()){maxY = point.y();}if(minY > point.y()){minY = point.y();}}// 当获取到有效的数据后对最值做一些偏移,使其不会绘制在边界上if(maxX > minX && maxY > minY){m_maxX = maxX + 0.1 * (maxX - minX);m_minX = minX - 0.1 * (maxX - minX);m_maxY = maxY + 0.1 * (maxY - minY);m_minY = minY - 0.1 * (maxY - minY);}
}

使用定义的折线绘制控件方式【在需要的地方调用即可】:

QVector<QPointF> datas;datas << QPointF(12, 2.5)<< QPointF(13, 12)<< QPointF(14, 3.5)<< QPointF(15, 14)<< QPointF(16, 4.5)<< QPointF(17, 8.5)<< QPointF(18, 3.5)<< QPointF(19, 20.5)<< QPointF(23, 12.2)<< QPointF(19, 20)<< QPointF(52, 4.4);PolylineWidget *pPolyWidget = new PolylineWidget;pPolyWidget->setMainTitleText("PolyWidget");pPolyWidget->setHCoordinateText("HCoordinate"); // 横坐标标题pPolyWidget->setVCoordinateText("VCoordinate"); // 纵坐标标图pPolyWidget->setMargin(60, 50, 30, 35); // 边矩pPolyWidget->setPolylineData(datas); // 需要绘制的数据this->setCentralWidget(pPolyWidget);

效果图:

备注:
1) 以上仅仅对源码进行了粘贴,未进行具体说明,有问题请指正,有需要求留言
2)以上只是说明基础的绘制,未考虑其它过多的东西

【有时间且必要的话会持续更…】

继承QWidget使用QPainter自定义二维图形控件【Qt学习】相关推荐

  1. trackbar控件显示刻度值_安卓自定义电平流图形控件

    引言 在无线电监测方面,需要对信号进行展示,其中一项数据就是设备返回的电平数据,需要对其实时展示,一图胜千言,最好且最直观的方式就是图表展示,这样对其信号强弱的变化,就可以一目了然. 本文主要讲安卓版 ...

  2. FreeCAD二次开发:集成二维CAD控件MxDraw

    济南友泉软件有限公司 FreeCAD是一套基于OpenCASCADE/QT的三维全参数化建模开源代码,虽然提供了Draft.TechDraw等二维绘图功能,但是其二维建模能力仍旧比较弱. Ref. f ...

  3. 二维图形与三维图形自定义几何

    自定义二维图形与三维图形用到的类是不同的. 1.二维图形用到的类为shape(用路径定义二维的形状).ShapeGeometry(创建二维几何对象) 2.三维自定义几何用到的类为Geometry(包含 ...

  4. Android开发指南-二维图形

    二维图形2D Graphics Android 提供一个定制的2D图形库,用来绘制图形图像和制作动画.你将从android.graphics.drawable和android.view.animati ...

  5. Android 自定义二维码

    Android生成二维码使用的是zxing. 1.加入依赖,或者自己选择zxing版本:Releases · zxing/zxing · GitHub dependencies {...impleme ...

  6. 【OpenGL ES】二维图形绘制

    目录 OpenGL ES 学习--2D 着色器语言基础知识 绘制纯色背景 JAVA版本 C++版本 绘制圆点.直线.三角形 JAVA版本 C++版本 绘制彩色三角形 JAVA版本 C++版本 绘制纯色 ...

  7. 计算机图形学——二维图形几何转换

    文章目录 一.实验目的 二.实验要求 三.实验步骤 1.打开直线扫面转换MFC项目工程,及其中的直线类CLine. 2.二维点类CP2添加齐次坐标 3.设计实现二维图像几何变换类 1.新建二维图形几何 ...

  8. 二维图形几何变换(计算机图形学)

    说明:本实验代码在vs2022下可正常运行,本实验适配于计算机图形学新版(VC++ MFC )第二版 1.实验目的 1)掌握3*3矩阵乘法运算的编程实现 2)掌握平移,比例,旋转三种基本二维几何变换矩 ...

  9. php 二维数组排序函数,php自定义二维数组排序函数array

    这次给大家带来php自定义二维数组排序函数array,php自定义二维数组排序函数array的注意事项有哪些,下面就是实战案例,一起来看一下. 这篇文章主要介绍了php自定义二维数组排序函数array ...

最新文章

  1. Android studio 下 JNI 开发实例
  2. Nervos CKB 共识协议 NC-Max:突破 Nakamoto Consensus 吞吐量的极限
  3. C++ 11 新特性(十四)C++类
  4. Centos下MySQL的安装及常见问题
  5. GitHub Corners 挂角链接
  6. synchronized 解决死锁的问题 轉貼
  7. PDF怎么转换成Word文字
  8. 计算机常见的编码规范
  9. 云在脚下 三大云存储方案对比与选型指导
  10. 电工与电子技术和电子电工的区别
  11. c语言的矩阵怎么编程,关于矩阵的编程
  12. XCode 3 中我的常用快捷键
  13. 荐读解惑 | FPGA是有门槛的,零基础并不是你想的那样,不要再被忽悠了!
  14. 哪些人适合转行学习IT技术呢
  15. django报错ERRORS:?: (admin.E402) ‘django.contrib.auth.context_processors.auth‘
  16. 计算机网络笔记(王道考研) 第五章:传输层
  17. idea出现Address localhost:8082 is already in use
  18. Java求解一元二次方程+单元测试(IDEA+Junit)
  19. Python 音频库 及具体使用介绍(包括声卡通道获取及选择) 第一篇: sounddevice
  20. PMP项目管理的前路

热门文章

  1. 标准事件模型和IE事件模型
  2. MD5 c++ 实现
  3. 【R语言】混淆矩阵计算
  4. 准备金率年内第九次上调
  5. 推荐系统 (一): 推荐系统的架构
  6. 学习掘金React进阶实践指南笔记(三)玄学state
  7. 实现倒计时的几种方案汇总
  8. 我说CMMI2.0之管理性能和度量数据
  9. 小米笔记本重装系统没有wifi功能和扬声器没有声音解决的过程(红米G游戏本)
  10. pythonmt4通讯swot矩阵_SWOT矩阵表