Qt4利用MVC开发曲线数据编辑器

目录

[1 需求](#1 需求)

[2 开发流程](#2 开发流程)

[1 搭建框架](#1 搭建框架)

[2 构造函数](#2 构造函数)

[3 打开工程](#3 打开工程)

[4 实现应用程序参数加载](#4 实现应用程序参数加载)

[5 QCustomPlot和TableView的联动](#5 QCustomPlot和TableView的联动)

[6 数据的可视化修改](#6 数据的可视化修改)

[7 列表点击事件事先键盘控制](#7 列表点击事件事先键盘控制)

[8 表格实现复制,粘贴,删除等一系列功能](#8 表格实现复制,粘贴,删除等一系列功能)

[9 曲线实现自适应范围和统一范围](#9 曲线实现自适应范围和统一范围)


1 需求

之前编过1个曲线编辑器,但有几个问题,1是加载太慢,2是没法保存工程。

现在将需求重新整理一下,再开发个曲线编辑器。此外也总结了三点技术问题,分别为:

(1)曲线空间QCustomPlot和表格控件TableView的联动,目的是实现曲线编辑;

(2)数据分类显示,目的是数据按不同分类来绘图,避免叠合在一起看不清。

(3)表格实现复制,粘贴,删除等一系列功能。

(4)列表控件实现键盘控制,解放鼠标,加速曲线切换。

(5)不同曲线实现自适应范围和统一范围。用于对比。

2 开发流程

1 搭建框架

新建main window工程,并使其支持中文

cpp 复制代码
#include <QtGui/QApplication>
#include "mainwindow.h"
#include <QTextCodec>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    // 文本编码规定
    QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF8"));
    QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF8"));
    QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF8"));

    MainWindow w;
    w.show();

    return a.exec();
}

2 构造函数

在构造函数中定好模型,视图以及控件初始化。

cpp 复制代码
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    winName = "磁测剖面编辑器 V1.0";
    setWindowTitle(winName);

    // 默认编辑开关为否
    customEditOn = false;

    // 默认曲线范围为自适应
    curveXfit = true;
    curveYfit = true;

    // 表格模型视图
    model = new QStandardItemModel(0,22);
    model->setHorizontalHeaderLabels(QStringList()<<"Line"<<"Point"<<"PLon"<<"PLat"<<"DiuCorr"
                                     <<"Read"<<"Sq"<<"Lon"<<"Lat"<<"Elevation"
                                     <<"Date"<<"Time"<<"Instr"<<"GeoMag"<<"ΔT"
                                     <<"Note"<<"PX"<<"PY"<<"X"<<"Y"<<"Pdistance"<<"Anomaly");

    connect(model,SIGNAL(dataChanged(QModelIndex,QModelIndex)),this,SLOT(slotDataChanged(QModelIndex,QModelIndex)));

    proxyModel = new QSortFilterProxyModel(this);
    proxyModel->setSourceModel(model);
    proxyModel->setFilterKeyColumn(0);
    ui->tableView->setModel(proxyModel);
    ui->tableView->resizeColumnsToContents();
    ui->tableView->resizeRowsToContents();


    // 列表模型视图
    lines = new QStringListModel;
    ui->listView->setModel(lines);
    ui->listView->setEditTriggers(false);

    // plot点击事件
    connect(ui->curveView, SIGNAL(plottableClick(QCPAbstractPlottable*,int,QMouseEvent*)),
            this, SLOT(graphClicked(QCPAbstractPlottable*,int)));

    // 进度条
    ui->progressBar->setRange(0,100);
    ui->progressBar->setValue(0);
    ui->progressBar->hide();

}

3 打开工程

这里要重点改一下,先来看之前的逻辑

  • 指定文件名
  • 读取文件到局部变量data
  • 计算公里网
  • 多级排序
  • 计算测线名称及点数
  • 计算距离和异常列
  • 写入模型
  • 挂载测线列表并触发点击
  • 控件显示。

这里要重点改新增加的这几列,有些列需要预留。

更改原始文件,将投影变换的功能移除,现在的逻辑变为:

  • 读取数据
  • 点名分离为线和点
  • 计算测线
  • 计算点数(这个实际MVC中不用,只是用于后期用户统计)
  • 数据写入模型
  • 挂载测线列表
  • 触发点击事件。

此逻辑较老版的更为简介,且加载速度更快。

4 实现应用程序参数加载

为在主程序中调用曲线编辑器,可采用QProcess来调用,这时候需要改造构造函数,并重写打开action。具体代码如下:

cpp 复制代码
// 打开line文件
void MainWindow::on_actionOpen_triggered()
{
    // 1指定文件
    QString tmpName = QFileDialog::getOpenFileName(this,"Open","","*.txt");
    if(tmpName.isEmpty())
        return;
    fileName = tmpName;
    open(QStringList()<<fileName);

}

所有的第3步提到的业务逻辑全部打包至open函数中,这样就可以实现在构造函数中调用,代码如下:

cpp 复制代码
 if(!inNameList.isEmpty())
        open(inNameList);

这里要注意的是,传入参数是个list,需要取第0个值作为文件名。

5 QCustomPlot和TableView的联动

由于QCustomPlot仅仅为绘图库,不是MVC结构,因此只能实现2个单向的联动,以此来模拟MVC机制。

主要实现2个流程:

(1)当绘图数据点击时,实现表格的选择;

(2)当表格数据修改时,实现绘图的更新。

具体第1个方法的代码是:

cpp 复制代码
void MainWindow::graphClicked(QCPAbstractPlottable *plottable, int dataIndex)
{
    // plot中只有QCPGraphs,因此可以立即调用interface1D()
    // 建议先检查interface1D()是否返回非零
    double dataValue = plottable->interface1D()->dataMainValue(dataIndex);
    QString message = QString("Clicked on graph '%1' at data point #%2 with value %3.").arg(plottable->name()).arg(dataIndex).arg(dataValue);
    ui->statusBar->showMessage(message);
    // 少数据的话就不准了
    ui->tableView->setCurrentIndex(proxyModel->index(dataIndex,19,QModelIndex()));
}

第2个方法的实现代码为:

cpp 复制代码
/* 数据改动回调 */
void MainWindow::slotDataChanged(QModelIndex ind1, QModelIndex ind2)
{
    Q_UNUSED(ind1)
    Q_UNUSED(ind2)
    if(customEditOn == true)
        plot();
}

代码解析:1个是绘图库控件单击事件,并不修改表格数据;1个是表格数据的修改事件槽函数,调用重新绘图方法。因此两个并不是咬合的关系,但却恰恰实现了需求的功能。

6 数据的可视化修改

下面来展示下第5点所属的修改过程。

文件加载了数十条曲线,上图显示的第13条。这条曲线明显被一些废点所影响了,如果单纯看表格是很难找到的,毕竟有上千个数据。而曲线则可以轻易的找到废点位置。

只需要点击曲线,就能找到废点在表格中的位置。

选中之后,用户可以选择删除或者修改。

可见删除之后,绘图库控件即时进行了刷新。曲线恢复了正常形态。其余的废点也可以按照这种放方法进行处理。

7 列表点击事件事先键盘控制

在切换曲线时,需要鼠标逐个选择列表项,这相当的麻烦,因此需要实现键盘事件,以此来加速曲线的切换操作。

具体的代码为:

cpp 复制代码
// 方向键上下加回车可调用点击事件
void MainWindow::on_listView_activated(const QModelIndex &index)
{
    on_listView_clicked(index);
}

代码较为简单,仅仅是调用了点击事件。

cpp 复制代码
// 测线列表 单击事件
void MainWindow::on_listView_clicked(const QModelIndex &index)
{
    // 模型过滤后绘图
    int row = index.row();
    QString ln = lines->stringList().at(row);
    // 关闭编辑标记后再修改代理模型 避免在修改时频繁调用plot
    customEditOn = false;
    proxyModel->setFilterRegExp(QRegExp(ln, Qt::CaseInsensitive, QRegExp::FixedString));
    plot();
    customEditOn = true;
}

具体逻辑为:先获取选中行的序号,再找到字符串,之后设置proxymodel的正则化过滤器,刷新绘图后,打开修改开关。

通过上述2个函数配合,就可以实现回车与点击事件的同步操作。简化了曲线切换的麻烦。

8 表格实现复制,粘贴,删除等一系列功能

表格数据需要实现复制,粘贴,删除等一系列功能,这就涉及到tableview的子类化问题,上一篇博文我们用mainwindow来实现复制粘贴,本节则采用对qtableview子类化的方式,重写event函数来达到此目的。

下面是代码:

cpp 复制代码
/*  实现多选的复制粘贴 */
void TableView::keyPressEvent(QKeyEvent *keyEvent)
{
    if(keyEvent->matches(QKeySequence::Copy))//复制
    {
        QModelIndexList indexList = selectionModel()->selectedIndexes();
        if(indexList.isEmpty())
            return;
        int startRow = indexList.first().row();
        int endRow = indexList.last().row();
        int startCol = indexList.first().column();
        int endCol = indexList.last().column();
        QStringList clipboardTextList;
        for(int i = startRow;i <= endRow;i++)
        {
            QStringList rowText;
            for(int j = startCol;j <= endCol;j++)
            {
                rowText.append(model()->data(model()->index(i,j)).toString());

            }
            clipboardTextList.append(rowText.join("\t"));
        }
        QString clipboardText = clipboardTextList.join("\n" );
        QApplication::clipboard()->setText(clipboardText);
    }
    else if (keyEvent->matches(QKeySequence::Paste))
    {
        QString clipboardText = QApplication::clipboard()->text();
        if(clipboardText.isEmpty())
            return;
        QStringList rowTextList = clipboardText.split('\n');
        if(rowTextList.last().isEmpty())//从word或者excel复制的内容后面可能会带'\n',导致split出来后面有个空字符串。
            rowTextList.removeLast();
        QModelIndexList indexList = selectionModel()->selectedIndexes();
        if(indexList.isEmpty())
            return;
        QModelIndex startIndex = indexList.first();
        for(int i = 0;i < rowTextList.size();i++)
        {
            QStringList itemTextList = rowTextList.at(i).split('\t');
            for(int j = 0;j < itemTextList.size();j++)
            {
                QModelIndex curIndex = model()->index(i + startIndex.row(),j + startIndex.column());
                if(curIndex.isValid())
                {
                    model()->setData(curIndex,itemTextList.at(j));
                }
            }
        }
    }
    else if (keyEvent->matches(QKeySequence::Delete))
    {
        // 获取选中行
        QItemSelectionModel *selections = selectionModel();
        QModelIndexList selected = selections->selectedIndexes();
        // 循环选中的各个index并写为空
        foreach(QModelIndex index,selected)
        {
            model()->setData(index,"");
        }
    }
    else if (keyEvent->matches(QKeySequence::SelectAll))
    {
        QModelIndex topLeft;
        QModelIndex bottomRight;
        topLeft = model()->index(0,0);
        bottomRight = model()->index(model()->rowCount()-1,model()->columnCount()-1);
        QItemSelection selection(topLeft,bottomRight);
        selectionModel()->select(selection,QItemSelectionModel::Select);
    }
    else if (keyEvent->matches(QKeySequence::MoveToNextLine))
    {
        if(currentIndex().row()>-1)
        {
            if(currentIndex().row()<model()->rowCount()-1)
                setCurrentIndex(model()->index(currentIndex().row()+1,currentIndex().column()));
        }
    }
    else if (keyEvent->matches(QKeySequence::MoveToPreviousLine))
    {
        if(currentIndex().row()>0)
        {
            setCurrentIndex(model()->index(currentIndex().row()-1,currentIndex().column()));
        }
    }
    else if (keyEvent->matches(QKeySequence::MoveToNextChar))
    {
        if(currentIndex().column()>-1)
        {
            if(currentIndex().column()<model()->columnCount()-1)
                setCurrentIndex(model()->index(currentIndex().row(),currentIndex().column()+1));
        }
    }
    else if (keyEvent->matches(QKeySequence::MoveToPreviousChar))
    {
        if(currentIndex().column()>0)
        {
            setCurrentIndex(model()->index(currentIndex().row(),currentIndex().column()-1));
        }
    }
    else if (keyEvent->matches(QKeySequence::MoveToNextPage))
    {
        int row = currentIndex().row();
        if(row>-1)
        {
            int col = currentIndex().column();
            int step = 20;
            int count = model()->rowCount();
            if(row+step<count-1)
                setCurrentIndex(model()->index(row+step,col));
            else
                setCurrentIndex(model()->index(count-1,col));
        }
    }
    else if (keyEvent->matches(QKeySequence::MoveToPreviousPage))
    {
        int row = currentIndex().row();
        if(row>-1)
        {
            int col = currentIndex().column();
            int step = 20;
            if(row-step>0)
                setCurrentIndex(model()->index(row-step,col));
            else
                setCurrentIndex(model()->index(0,col));
        }
    }
}

代码外层是比较简单的判断语句,分别实现了ctrl+c,ctrl+v,ctrl+a,delete等功能。这样就可以对表格实现较多的单选,多选,指定区域的复制,粘贴,删除等操作。

9 曲线实现自适应范围和统一范围

主要用lineEdit控件和绘图库控件配合完成。

代码如下:

cpp 复制代码
// 横坐标范围模式切换
void MainWindow::on_actionXlim_toggled(bool arg1)
{
    if(arg1==true)
        curveXfit = false;
    else
        curveXfit = true;
}

// 纵坐标范围模式切换
void MainWindow::on_actionYlim_toggled(bool arg1)
{
    if(arg1==true)
        curveYfit = false;
    else
        curveYfit = true;
}

通过切换自适应开关来实现绘图范围的控制。在绘图plot中实现:

cpp 复制代码
   // 设置绘图范围
    QString xmin = ui->xmin->text();
    QString xmax = ui->xmax->text();
    if(curveXfit==true || xmin=="" || xmax=="")
        ui->curveView->xAxis->rescale();
    else
        ui->curveView->xAxis->setRange(xmin.toDouble(),xmax.toDouble());

    QString ymin = ui->ymin->text();
    QString ymax = ui->ymax->text();
    if(curveYfit==true || ymin=="" || ymax=="")
        ui->curveView->yAxis->rescale();
    else
        ui->curveView->yAxis->setRange(ymin.toDouble(),ymax.toDouble());

用一个判断来实现绘图范围控制的切换,以此来实现曲线的对比和显示。

相关推荐
用户805533698034 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner4 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz9 天前
QML Hello World 入门示例
qt
xcyxiner12 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner13 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner13 天前
DicomViewer (添加模型类)3
qt
xcyxiner14 天前
DicomViewer (目录调整) 2
qt
xcyxiner14 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
桥田智能16 天前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构
森G16 天前
75、服务器源码解析---------云视频服务项目
linux·服务器·网络·c++·qt