使用QT做虚拟示波器,共16通道,波形是重叠在一起(不同颜色区分),想用写好的TCP传输来让单片机与电脑通信,解刨数据,放入到示波器中进行显示。
准备工作:首先我缺一个绘图的控件,于是在网上找了找,网上给我介绍的有三种。分别是qcustomplot、qwt以及qchart。我第一次使用的是qcustomplot,我发现使用qcustomplot如果不生成静态库再使用的话,运行起来特别特别慢,这就对编程带来了不便。这其实也不能怪人家,它毕竟大小就有1M多,我们一般写代码一个文件才几十K,所以想呀,qcustomplot编译起来肯定很花时间。然后我就使用了QChart类,我听说会玩这个的话,要比另外两种强。qwt我还没碰过。声明一下,我只是谈谈我个人的想法,不喜勿喷!
-
QChartView是一个窗口,提供显示的地方。而QChart则像是一张图纸,QLineSeries直线或者QSplineSeries平滑线就相当于是画笔了。我使用继承QChartView来做一个方便我对外使用打开通道、关闭通道、添加数据等功能的接口。
class WaveDisplay : public QChartView
-
使用枚举定义16个通道
//设置通道数 typedef enum { Channel1, Channel2, Channel3, Channel4, Channel5, Channel6, Channel7, Channel8, Channel9, Channel10, Channel11, Channel12, Channel13, Channel14, Channel15, Channel16 }Channel;
-
曲线选择类型,方便后面更改
typedef QLineSeries LineType;//QLineSeries 直线 QSplineSeries 平滑线
-
整个控件需要用到一些共有变量,每一次打开一个通道的新建一只画笔(QSplineSeries),然后这只画笔还得记录下来,为我后续添加数据使用,我在这里使用的是QHash来保存的,个人感觉这个还是挺好用的,推荐!后面三行用于自动控制刻度范围。
QChart *chart; //图纸 QValueAxis *axisX, *axisY; //坐标轴 QHash<Channel, LineType*> seriesHash; //将多条线放在图纸上 QHash<Channel, double> minALL; //记录所有曲线的最小值 QHash<Channel, double> maxALL; //记录所有曲线的最大值 double Xmax = 0, Ymax = -350; double Xmin = 0, Ymin = 350; double Xrang = 3;
-
初始化工作
WaveDisplay::WaveDisplay(QWidget *parent) : QChartView(parent) { mainLayout = new QGridLayout(parent); //居中显示 mainLayout->addWidget(this, 0, 1, 3, 1); chart = new QChart; //画图纸 chart->legend()->setVisible(false); //曲线文本提示 setChart(chart); //添加图纸 axisX = new QValueAxis; //设置坐标轴 axisY = new QValueAxis; chart->addAxis(axisX, Qt::AlignBottom); //将坐标轴加到chart上,居下 chart->addAxis(axisY, Qt::AlignLeft); //居左 setRenderHint(QPainter::Antialiasing, true); //反走样 /*特效*/ //setRubberBand(QChartView::RectangleRubberBand); //矩形缩放 //chart->setAnimationOptions(QChart::SeriesAnimations); //曲线动画模式,不能启用这一项或是选择这个选项,这个会导致曲线闪烁 }
-
打开一个通道
void WaveDisplay::openChannel(Channel channel)//添加一条平滑线并添加到图纸中 { minALL.insert(channel, 100000); maxALL.insert(channel, -100000); if(seriesHash.contains(channel)) //如果重复打开同一个通道就会执行这条语句,主要是防止内存泄漏 { chart->addSeries(seriesHash[channel]); chart->setAxisX(axisX, seriesHash[channel]); //将x和y坐标轴与第一条曲线连接 chart->setAxisY(axisY, seriesHash[channel]); return; } LineType *series = new LineType(); series->setPen(Color[channel]); chart->addSeries(series); chart->setAxisX(axisX, series); //将x和y坐标轴与第一条曲线连接 chart->setAxisY(axisY, series); seriesHash.insert(channel, series); //记录到表中 }
-
颜色如果自己不想配的话可以直接使用我自己配的,配16中人眼看起来完全不同颜色还是挺难受的
namespace ChannelColor{ static QColor Color[16] = { QColor(165, 42, 42), //Channel1Color QColor (255, 127, 80), //Channel2Color QColor (30, 144, 255), //Channel3Color QColor (218, 165, 32), //Channel4Color QColor (255, 0, 255), //Channel5Color QColor (147, 112, 219), //Channel6Color QColor (0, 255, 255), //Channel7Color QColor (0, 0, 128), //Channel8Color QColor (128, 0, 128), //Channel9Color QColor (46, 139, 87), //Channel10Color QColor (152, 251, 152), //Channel11Color QColor (0, 255, 127), //Channel12Color QColor (0, 0, 255), //Channel13Color QColor (135, 206, 250), //Channel14Color QColor (255, 192, 203), //Channel15Color QColor (127, 255, 0) //Channel16Color }; }
-
关闭通道
void WaveDisplay::closeChannel(Channel channel) { if(seriesHash.contains(channel)) //如果先前没有打开这通道,程序就不会执行这条复合语句,主要防止用户点错 { chart->removeSeries(seriesHash[channel]); //先删去图纸上的线 minALL.remove(channel); maxALL.remove(channel); } }
-
可以在设置数据和追加数据的时候添加判断,这样就可以自动刻度了
void WaveDisplay::setData(Channel channel, const QList<QPointF>& data) { if(seriesHash.contains(channel) && chart->series().contains(seriesHash[channel])) { seriesHash[channel]->clear(); seriesHash[channel]->append(data); Xmin = data[0].x() < Xmin ? data[0].x() : Xmin; Xmax = data[data.count() - 1].x() > Xmax ? data[data.count() - 1].x() : Xmax; for(int i = 0; i < data.count(); i++) { maxALL[channel] = data[i].y() > maxALL[channel] ? data[i].y() : maxALL[channel]; minALL[channel] = data[i].y() < minALL[channel] ? data[i].y() : minALL[channel]; } updateChart(); } }
-
追加数据
void WaveDisplay::appendData(Channel channel, const QPointF& data) { if(seriesHash.contains(channel) && chart->series().contains(seriesHash[channel])) { seriesHash[channel]->append(data); if(data.x() > Xrang) { Xmax = data.x(); Xmin = Xmax - Xrang; } maxALL[channel] = data.y() > maxALL[channel] ? data.y() : maxALL[channel]; minALL[channel] = data.y() < minALL[channel] ? data.y() : minALL[channel]; updateChart(); } } void WaveDisplay::appendData(Channel channel, const QList<QPointF>& data) { if(seriesHash.contains(channel) && chart->series().contains(seriesHash[channel])) { seriesHash[channel]->append(data); Xmin = data[0].x() < Xmin ? data[0].x() : Xmin; Xmax = data[data.count() - 1].x() > Xmax ? data[data.count() - 1].x() : Xmax; for(int i = 0; i < data.count(); i++) { maxALL[channel] = data[i].y() > maxALL[channel] ? data[i].y() : maxALL[channel]; minALL[channel] = data[i].y() < minALL[channel] ? data[i].y() : minALL[channel]; } updateChart(); } }
-
按键点击事件:ESC——恢复默认显示;TAB——支持鼠标取点
void WaveDisplay::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_Escape: updateChart(); break; case Qt::Key_Tab://Tab键被按下 showPoint = true; break; default: break; } }
-
按键释放事件:TAB——不支持鼠标取点
void WaveDisplay::keyReleaseEvent(QKeyEvent *event) { if(event->key() == Qt::Key_Tab) showPoint = false; }
-
鼠标点击事件:左键——取点;右键——配合曲线移动
void WaveDisplay::mouseMoveEvent(QMouseEvent *event)//参考:https://blog.csdn.net/qq_31073871/article/details/83019943 { QPoint temp = event->pos();//获取当前坐标的位置 if(showPoint) { QPointF f = chart->mapToValue(temp); QString str = '(' + QString::number(f.x()) + ',' + QString::number(f.y()) + ')'; QToolTip::showText(event->globalPos(), str); }else{ QToolTip::hideText(); } // getPos(event); if(event->buttons() == Qt::RightButton){ // 这里必须使用buttons(),因为鼠标移动过程中会检测所有按下的键,而这个时候button()是无法检测那个按键被按下,所以必须使用buttons()函数,看清楚,是buttons()不是button() QPointF f = chart->mapToValue(temp) - chart->mapToValue(lastPos); axisX->setRange(axisX->min() - f.x(), axisX->max() - f.x()); axisY->setRange(axisY->min() - f.y(), axisY->max() - f.y()); chart->update(); lastPos = temp; update(); } }
-
鼠标点击事件:左键——取点;右键——移动曲线
void WaveDisplay::mousePressEvent(QMouseEvent *event) { if(event->button() == Qt::RightButton){ // 如果是鼠标中键按下 QCursor cursor; cursor.setShape(Qt::ClosedHandCursor); QApplication::setOverrideCursor(cursor); // 使鼠标指针暂时改变形状 lastPos = event->pos(); // 获取指针位置和窗口位置的差值,这个globalPos()获取的是鼠标在桌面上的位置,也可以使用pos()函数获取指针在窗口的位置。 } else if(event->button() == Qt::LeftButton){ getPos(event); } }
-
鼠标移动事件:如果TAB按下则显示鼠标位置;右键:移动曲线
void WaveDisplay::mouseMoveEvent(QMouseEvent *event) { QPoint temp = event->pos();//获取当前坐标的位置 if(showPoint) { QPointF f = chart->mapToValue(temp); QString str = '(' + QString::number(f.x()) + ',' + QString::number(f.y()) + ')'; QToolTip::showText(event->globalPos(), str); }else{ QToolTip::hideText(); } if(event->buttons() == Qt::RightButton){ // 这里必须使用buttons(),因为鼠标移动过程中会检测所有按下的键,而这个时候button()是无法检测那个按键被按下,所以必须使用buttons()函数,看清楚,是buttons()不是button() QPointF f = chart->mapToValue(temp) - chart->mapToValue(lastPos); axisX->setRange(axisX->min() - f.x(), axisX->max() - f.x()); axisY->setRange(axisY->min() - f.y(), axisY->max() - f.y()); chart->update(); lastPos = temp; update(); } }
-
鼠标释放事件:配合曲线移动
void WaveDisplay::mouseReleaseEvent(QMouseEvent *event) { Q_UNUSED(event); QApplication::restoreOverrideCursor(); // 恢复鼠标指针形状 }
-
滚轮事件:参考http://blog.csdn.net/smarterr/article/details/80781368
void WaveDisplay::wheelEvent(QWheelEvent *event) { if(event->delta() > 0){ // 当滚轮远离使用者时 chart->zoomIn(); // 进行放大 Xrang /= 1.5; }else{ // 当滚轮向使用者方向旋转时 chart->zoomOut(); // 进行缩小 Xrang *= 1.5; } }
-
获取鼠标坐标函数,自己简单写了下思路,欢迎提问
void WaveDisplay::getPos(QMouseEvent *event) { QPoint temp = event->pos();//获取当前坐标的位置 if(showPoint) { QPointF f = chart->mapToValue(temp); QString str = '(' + QString::number(f.x()) + ',' + QString::number(f.y()) + ')'; QToolTip::showText(event->globalPos(), str); QHash<Channel, LineType*>::iterator seriesTemp; for(seriesTemp = seriesHash.begin(); seriesTemp != seriesHash.end(); ++seriesTemp)//遍历每条曲线 { QList<QPointF> pointfTemp = seriesTemp.value()->points();//线上所有点对应的屏幕坐标 int low = 0; int high = pointfTemp.count() - 1; if(pointfTemp[low].x() > f.x() || pointfTemp[high].x() < f.x()) { posList[seriesTemp.key()].clear(); continue; } while(low < high - 1)//从某条线找到这个点的附近 { int middle = (low + high) / 2; if(pointfTemp[middle].x() < f.x()) { low = middle; } if(pointfTemp[middle].x() > f.x()) { high = middle; } } if(low == high) posList[seriesTemp.key()] = '(' + QString::number(f.x()) + ',' + QString::number(pointfTemp[high].y()) + ')'; else{ posList[seriesTemp.key()] = '(' + QString::number(f.x()) + ',' + QString::number\ (pointfTemp[low].y() + (pointfTemp[high].y() - pointfTemp[low].y()) / (pointfTemp[high].x() - pointfTemp[low].x()) * (f.x() - pointfTemp[low].x()))\ + ')'; } } }else{ QToolTip::hideText(); } }
-
恢复坐标轴
void WaveDisplay::updateChart() { double num; QHash<Channel, double>::iterator rangTemp; //找最小值 if(minALL.size()) { rangTemp = minALL.begin(); num = rangTemp.value(); ++rangTemp; for(; rangTemp != minALL.end(); ++rangTemp)//遍历每条曲线 { num = rangTemp.value() < num ? rangTemp.value() : num; } Ymin = num; } //找最大值 if(maxALL.size()) { rangTemp = maxALL.begin(); num = rangTemp.value(); ++rangTemp; for(; rangTemp != maxALL.end(); ++rangTemp)//遍历每条曲线 { num = rangTemp.value() > num ? rangTemp.value() : num; } Ymax = num; } axisX->setRange(Xmin, Xmax);//设置图表坐标轴的范围,可以不设置,自动调节的 double range = Ymax - Ymin; axisY->setRange(Ymin - range * 0.1, Ymax + range * 0.1); }