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

目录

1 需求

2 开发流程

1 搭建框架

2 构造函数

3 打开工程

4 实现应用程序参数加载

5 QCustomPlot和TableView的联动

6 数据的可视化修改

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

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

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


1 需求

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

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

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

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

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

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

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

2 开发流程

1 搭建框架

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

#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 构造函数

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

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。具体代码如下:

// 打开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函数中,这样就可以实现在构造函数中调用,代码如下:

 if(!inNameList.isEmpty())
        open(inNameList);

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

5 QCustomPlot和TableView的联动

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

主要实现2个流程:

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

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

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



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个方法的实现代码为:

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

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

6 数据的可视化修改

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

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

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

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

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

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

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

具体的代码为:


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

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

// 测线列表 单击事件
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函数来达到此目的。

下面是代码:


/*  实现多选的复制粘贴 */
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控件和绘图库控件配合完成。

代码如下:


// 横坐标范围模式切换
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中实现:

   // 设置绘图范围
    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());

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/191543.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MyBatis插入操作返回主键报错问题记录

一开始用直接传参数的方法写的插入操作 StudentMapper.java接口 Integer insertStudent(Param("sname") String name,Param("sage") int age); 然后在网上搜了返回主键的方法 StudentMapper.xml: <insert id"insertStudent" useGenerat…

CAN通信协议

CAN 文章目录 CAN前言一、什么是CAN二、CAN的用途三、CAN协议简解1.can的通信过程1.1空闲状态1.2.起始状态1.3 仲裁机制1.4 位时序 前言 前面学了232、485、IIC、SPI等通信协议&#xff0c;还有一个强大的协议CAN&#xff0c;值得记录一下 一、什么是CAN CAN是Controller Ar…

爬取极简壁纸

js反编译的代码需要解密之类的&#xff0c;直接给我干蒙圈了&#xff0c;借助selenium可以直接获取到调式工具中的源码&#xff0c;可以获取渲染后的链接&#xff0c;然后将链接交给下载函数&#xff08;使用异步提高效率&#xff09;即可。 后续学习完js反编译的话&#xff0…

Unity-链接MySql8.0

链接MySql8.0 1.准备dll 一、找到l18N相关的dll 这里给出一个参考地址 D:\Unity\2020.3.48f1c1\Editor\Data\MonoBleedingEdge\lib\mono\unityjit在里面找到如下图的四个dll 二、下载数据库链接dll https://downloads.mysql.com/archives/c-net/在这里搜索历史版本(Archiv…

AIGC ChatGPT4总结Linux Shell命令集合

在Linux中,Shell命令的数量非常庞大,因为Linux提供了各种各样的命令来处理系统任务。这些命令包括GNU核心工具集、系统命令、shell内置命令以及通过安装获得的第三方应用程序命令。以下是一些常见的Linux命令分类及其示例,但请注意,这不是一个全面的列表,因为列出所有命令…

linux复习笔记05(小滴课堂)

hell脚本与crontab定时器的运用 查看状态&#xff1a; 关闭服务&#xff1a; 开启服务&#xff1a; 重启服务&#xff1a; crontab定时器的使用&#xff1a; 我们可以看到没有任何任务。 编辑&#xff1a; 我们可以看到这个任务了。 删除所有任务&#xff1a; 这代表着每分钟…

【NeRF】3、MobileR2L | 移动端实时的神经光场(CVPR2023)

论文&#xff1a;Real-Time Neural Light Field on Mobile Devices 代码&#xff1a;https://github.com/snap-research/MobileR2L 出处&#xff1a;CVPR2023 贡献&#xff1a; 设计了一套移动端实时的 R2L 网络结构 MobileR2L&#xff0c;在 iphone13 上渲染一张 1008x756…

PgSQL技术内幕-Analyze做的那些事-pg_stat_all_tables

PgSQL技术内幕-Analyze做的那些事-pg_stat_all_tables pg_stat_all_tables视图中记录有analyze信息&#xff0c;比如何时做的analyze、表元组个数&#xff08;活元组、死元组&#xff09;等。重启后发现该视图中表的统计信息重置不见了&#xff0c;发生了什么&#xff1f; 1、p…

Centos 7.9 Install Docker Insecure Registry

文章目录 1. 镜像存储规划2. 安装定制 docker3. 部署 registry4. 验证镜像仓库 1. 镜像存储规划 linux LVM /dev/sdb mount dir /data【linux LVM 磁盘挂载目录】 创建两个目录 一个 docker 数据存储目录 &#xff1a;/data/docker&#xff0c;默认一般为linux为 /var/lib/d…

AIGC ChatGPT 4 快速整理不规则数据

从业务系统中采集到的数据如下: 序号 省份 英文 2022年销售额 2021年销售额 增量 1 广东guangDOng129068.58 124319.67 4748.91 2 江苏 JiangSu 122825.6 116314.2 6511.4 3 山东ShAnDong 87385 83045.9 4339.1 4 浙江…

C语言:求Sn=a+aa+aaa+aaaa+……(n个a)之值,其中a表示一个数字,n表示a的位数,n由键盘录入。

分析&#xff1a; 在主函数 main 中&#xff0c;程序首先定义四个整型变量 a、n、i 和 sn&#xff0c;并初始化 a、n 和 i 的值&#xff0c;其中 sn 用于记录数列的和。然后使用 scanf 函数从标准输入中读取用户输入的两个整数 a 和 n。 接下来&#xff0c;程序通过 while …

1.5 C语言之字符输入输出

1.5 C语言之字符输入输出 一、概述二、字符计数三、行计数四、单词计数五、练习 一、概述 字符文本流&#xff0c;是由多行字符构成的字符序列&#xff0c;而每行字符都由0个或多个字符组成&#xff0c;行末是一个换行符。 标准库提供的输入输出模型&#xff0c;用于读取文本内…

树状数组 / pbds解法 E2. Array Optimization by Deque

Problem - 1579E2 - Codeforces Array Optimization by Deque - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 树状数组解法 将 a i a_i ai​插入到队头&#xff0c;贡献为&#xff1a;原队列中所有比 a i a_i ai​小的数的数量将 a i a_i ai​插入到队尾&#xff0c;贡献为&a…

JPA 自关联 设置单向多对一

Spring boot 3 JPA中&#xff0c;遇到一个需求&#xff0c;建一个数据字典表&#xff1a; Dictionary&#xff0c;存放两级数据&#xff0c;第一级为字典项目&#xff0c;第二级为项目内容&#xff0c;查询时要把parent_id对应父项的名称也一起查出来&#xff0c;返回前端。 …

python类和对象

1.使用对象组织数据 class Student:nameNone #记录名字 stu1Student() #创建对象 stu1.name"abc" #为对象属性赋值2.类的定义和使用 2.1成员方法的定义语法 传参的时候self是透明的&#xff0c;不用管 class Stu:nameNonedef sayHi(self):print(f"你好&#x…

Kotlin应用——使用kt进行web开发 使用h2database进行初始化数据库 mybatis-plus使用

Kotlin 是一门现代但已成熟的编程语言&#xff0c;旨在让开发人员更幸福快乐。 它简洁、安全、可与 Java 及其他语言互操作&#xff0c;并提供了多种方式在多个平台间复用代码&#xff0c;以实现高效编程。 kt入门的合集文章如下&#xff1a; Kotlin学习——kt入门合集博客 &…

realname,soname和linkname

背景 当在看/lib下的一些文件时候&#xff0c;我们发现几乎都是三个动态库文件&#xff0c;为啥&#xff1f; 分析 当我发布一个动态库的时候&#xff0c;比如版本是maj.min.patch&#xff08;10.2.1&#xff09;的格式&#xff0c;当我改了小版本的号的时候&#xff08;10…

redisserver一闪而过 redis闪退解决版本

1.进入Redis根目录 2.输入redis-server 或 redis-server.exe redis.windows.conf 启动redis命令&#xff0c;看是否成功。 执 一闪而过的问题 可能是因为已启动或者其他问题&#xff0c;需要重启 先输入redis-cli.exe再输入shutdown再输入redis-server.exe redis.windows.c…

<Linux> 文件理解与操作

目录 前言&#xff1a; 一、关于文件的预备知识 二、C语言文件操作 1. fope 2. fclose 3. 文件写入 3.1 fprintf 3.2 snprintf 三、系统文件操作 1. open 2. close 3. write 4. read 四、C文件接口与系统文件IO的关系 五、文件描述符 1. 理解文件描述符 2. 文…

水面倒影可视化渲染方法

水面材质在三维可视化场景中的使用非常广泛。水面材质非常重要的一个光学特性就是反射倒影&#xff0c;有了倒影的加持能使水面更加逼真的渲染出来。本文主要讨论水面材质中倒影的渲染方法。 要有倒影&#xff0c;必须先有水面&#xff0c;第一步要做的就是确定水面所在的平面…