Qt之UI数据双向绑定初探

4 篇文章 0 订阅

1.引子

    众多语言框架中均有MVC架构,对数据与UI的双向绑定有成熟的解决方案。

  • 以Android为例,谷歌最新推出的Jetpack Compose UI框架,其中声明式UI与其附带的数据双向绑定相当突出。实现声明式UI ≈ 实现一个 编译器,难度可想而知,但是利用Qt实现 UI与数据双向绑定是可以期待的。
  • 在Web前端开发中会应用到MVVM模式,其目的就是通过ViewModel层,将Model层和View层进行双向绑定。使得Model层的数据发生改变,View层也同样改变;用户如果通过View层进行修改,也会反馈到Model层的数据。
       对于Qt这一具体框架而言,略显特殊。以下汇总了搜集的数据绑定方法,供Qter们斟酌。

2.发布者-订阅者模式

3.脏值检查

4.数据劫持

5.数据、界面封装+重载赋值运算符实现数据感知+信号槽通知

    目标:
    实现一个UI组件,支持数据双向绑定。我们更新绑定变量的时候,绑定这个变量的UI组件,数据也会同步更新。同理,我们直接修改UI组件的属性,例如滑动条的位置亦会同步修改变量值。
    在 Qt - 一文理解信号槽机制(万字剖析整理) 中讲到 Qt的信号槽机制就有用到 发布-订阅模型。那么我们将通过信号槽实现。
    随着而来的是,信号槽机制依赖于 QObject,元对象系统。而我们绑定的变量可能是 int、float之类。为了能使用信号槽的通讯机制,我们必须将基本数据类型再封装一次。然后在变量类改变时通知控件类,在控件类改变时通知变量类。
    在Qt中,控件类的改变本身就有相应的 changed() 信号,所以我们只需要接起来即可。
    接下来的问题是如何知晓变量类改变,答案是:重载赋值运算符。
源码

/*! hslider.h */
#ifndef HSLIDER_H
#define HSLIDER_H

#include <QWidget>
#include <QSlider>
#include <QDebug>

class HInt : public QObject
{
    Q_OBJECT
public:
    explicit HInt(int x = 0){number = x;}
    void operator=(const HInt &newValue )
    { Q_EMIT(change(newValue.number)); number = newValue.number;}
signals:
   void change(int newValue);
public slots:
   void update(int newValue){ number = newValue; qDebug() << "HInt::update: " << newValue;}
private:
    int number;
};

class HSlider : public QWidget
{
    Q_OBJECT
public:
    explicit HSlider(QWidget *parent = nullptr);
    void Buid(HInt *newValue);
signals:
   void change(int newValue);
public slots:
    void update(int newValue);
private:
    QSlider* mSlider;
    HInt *mValue;
};

#endif // HSLIDER_H


/*! hslider.cpp */
#include "hslider.h"

HSlider::HSlider(QWidget *parent) : QWidget(parent)
{
    mSlider = new QSlider(this);
    connect(mSlider,&QSlider::valueChanged,this,&HSlider::change);
}

void HSlider::Buid(HInt *newValue){
    if(newValue){
        if(!mValue){
            disconnect(mValue,&HInt::change,this,&HSlider::update);
            disconnect(this,&HSlider::change,mValue,&HInt::update);
        }
        mValue = newValue;
        connect(mValue,&HInt::change,this,&HSlider::update);
        connect(this,&HSlider::change,mValue,&HInt::update);
    }
}

void HSlider::update(int newValue){
    mSlider->setValue(newValue);
    qDebug() << "HSlider::update" << newValue;
}

效果
在这里插入图片描述
   本方法对于数据的封装及其数据变化感知的方式比较原始,借鉴数据劫持、脏值检查希望能找出灵感。

6.QStandardItemModel+QDataWidgetMapper+QStyledItemDelegate

   相比于MVC、MVVM、MVP,Qt所提供的是View-Model-Delegate模式,模型类例如QStandardItemModel,但是Qt普通的组件没有拆分出View,因此还得自己去写对应的View,幸而Qt提供了QDataWidgetMapper 组件,支持实现绑定。

	QStandardItemModel *model=new QStandardItemModel(1,1);
    QDataWidgetMapper *mapper=new QDataWidgetMapper(this);
    mapper->setModel(model);
    auto *editA=new QLineEdit(this);
    auto *editB=new QLineEdit(this);
    mapper->addMapping(editA,0);
    mapper->addMapping(editB,0);
    mapper->toFirst();
    ui->gridLayout->addWidget(editA);
    ui->gridLayout->addWidget(editB);

   上面的代码中,如果改变了editA中文字,发射editingFinished 信号之后,model改变,editB就会改变自己的文字。其实也可以改变mapper的SubmitPolicy,手动调用submit实现更新。
   查看源代码,其原理是将QItemDelegate 中的eventFilter 安装到关联的组件中。因此我们可以继承QAbstractItemDelegate 来自定义,然后调用mapper 的setItemDelegate() ,这样就能引用上自定义的Widget,或者自定义显示的规则了。
   例如我们如果将editB 改成一个按钮pushButtonB ,那么改变editA 的内容并不能如愿以偿地让按钮的文字发生变化,这不是默认行为。不过我们只要自定义委托即可:
MyItemDelegate.h

#pragma once

#include <QStyledItemDelegate>

class MyItemDelegate : public QStyledItemDelegate
{
	Q_OBJECT

public:
	MyItemDelegate(QObject *parent);
	~MyItemDelegate();

	virtual void setEditorData(QWidget *editor, const QModelIndex &index) const override;

	virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;

};

MyDelegate.cpp

#include "MyItemDelegate.h"
#include <QLineEdit>
#include <QPushButton>

MyItemDelegate::MyItemDelegate(QObject *parent)
	: QStyledItemDelegate(parent)
{
}

MyItemDelegate::~MyItemDelegate()
{
}

void MyItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
	auto text = index.data().toString();
	if (qobject_cast<QPushButton*>(editor))
	{
		qobject_cast<QPushButton*>(editor)->setText(text);
	}
	else
	{
		qobject_cast<QLineEdit*>(editor)->setText(text);
	}
}

void MyItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
	if (qobject_cast<QLineEdit*>(editor))
	{
		model->setData(index, qobject_cast<QLineEdit*>(editor)->text(), Qt::EditRole);
	}
}

调用

	QStandardItemModel *model = new QStandardItemModel(1, 1);
	QDataWidgetMapper *mapper = new QDataWidgetMapper(this);
	mapper->setModel(model);
	auto *editA = new QLineEdit(this);
	auto *pushButtonB = new QPushButton(this);
	mapper->addMapping(editA, 0);
	mapper->addMapping(pushButtonB, 0);
	mapper->toFirst();
	mapper->setItemDelegate(new MyItemDelegate(this));
	ui.centralWidget->layout()->addWidget(editA);
	ui.centralWidget->layout()->addWidget(pushButtonB);

   此时文字就能在按钮上显示了。通过这种方式我们能很方便地进行更复杂的设计,例如通过调色板和输入数字同时控制颜色等等。但这种方式较少被书籍和教程重点提到,估计与Qt开发者数量与封闭开发的现实有关系。但确实不失为Qt官方提供的一种UI与数据双向绑定的较为理想的成体系的解决方案,值得推荐。

7.参考资料

【1】Vue.js数据双向绑定实现
【2】Qt - UI数据双向绑定简易实现
【3】初学Qt的小坑记录(6)——数据与组件的绑定

  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 使用QtUI界面设计串口数据采集,主要分为以下几个步骤: 1. 打开串口:通过Qt提供的QSerialPort类,可以很方便地实现串口的打开,设置波特率、数据位、校验位等参数。 2. 接收数据:通过QSerialPort的信号readyRead(),可以实现串口数据的接收。在该信号的槽函数中,可以读取串口缓冲区中的数据,并进行处理。 3. 数据处理:对于接收到的串口数据,可以根据具体需求进行处理,例如解析数据、显示数据等。可以使用Qt提供的控件(如QLabel、QTextEdit)来显示数据。 4. 发送数据:如果需要向串口发送数据,可以通过QSerialPort的write()函数来实现。可以在UI界面上添加一个发送按钮,通过点击按钮来发送数据。 5. 关闭串口:在程序退出或不需要使用串口时,需要关闭串口以释放资源。可以在UI界面上添加一个关闭串口的按钮,点击该按钮触发关闭串口的操作。 在UI界面设计上,可以使用Qt Designer工具来创建串口数据采集的界面。可以设计一个包含打开串口按钮、发送按钮、接收区域等控件的界面。通过设置控件的属性、信号与槽的连接,实现与串口通信相关的功能。 总结来说,使用QtUI界面设计串口数据采集需要先打开串口,接收并处理串口数据,同时可以发送串口数据,最后关闭串口以释放资源。设计上可以使用Qt Designer工具创建界面,通过设置控件属性和信号与槽的连接来实现功能。 ### 回答2: 在Qt中设计串口数据采集的UI界面可以通过以下步骤实现: 首先,通过Qt Creator创建一个新的Qt项目,并选择“MainWindow”作为主窗口类型。 接下来,在UI界面中添加所需的控件,可以包括一个按钮来打开或关闭串口,一个下拉菜单用于选择串口波特率,一个文本框来显示串口接收到的数据等。 然后,在主窗口类中添加相关的槽函数来处理控件的事件响应。比如,添加一个槽函数来处理打开或关闭串口的按钮点击事件,可以使用Qt提供的串口类(QSerialPort)来实现串口的打开和关闭操作。在打开串口时,需要设置串口的波特率以及其他参数,如数据位、停止位和校验位等。 接着,在主窗口类的构造函数中初始化串口类实例,并连接相关的信号和槽函数。比如,连接串口的readyRead()信号与一个槽函数,用于读取串口接收到的数据,并在文本框中显示出来。 最后,在UI界面的其他控件事件响应函数中,可以添加一些额外的逻辑来实现串口数据的采集,如将接收到的数据保存到文件中、进行数据处理或显示等。 需要注意的是,由于串口数据读取是一个耗时操作,为了避免界面卡顿,可以将串口读取相关的逻辑放在一个单独的线程中运行,然后通过信号和槽机制与主界面进行通信。可以使用Qt提供的多线程类(QThread)来实现。 综上所述,通过以上步骤可以实现使用QtUI界面设计串口数据采集的功能。这样设计的界面用户可以方便地打开/关闭串口,选择波特率,实时显示串口接收到的数据,并且可以进行数据采集以及其他操作。 ### 回答3: 在使用Qt进行串口数据采集的UI界面设计时,首先需要使用Qt提供的QSerialPort类来实现串口的打开、读写、关闭等操作。 在UI界面设计方面,可以使用Qt的图形设计工具Qt Designer来创建界面,或者手动编写代码实现。以下是一个简单的示例: 1. 首先,在Qt Designer中创建一个主窗口界面,并添加串口设置相关的控件,如按钮、下拉框、文本框等。可以设置按钮用来打开串口、读取数据等操作。 2. 在主窗口的类文件中,需要引入QSerialPort类的头文件,以及其它相关的Qt类文件。 3. 在打开串口的按钮点击事件中,实例化QSerialPort对象,并设置串口的名称、波特率、数据位、校验位和停止位等参数。然后,调用QSerialPort的open函数打开串口。 4. 在循环读取数据的函数中,通过QSerialPort的read函数读取串口接收到的数据,并将数据显示在相应的文本框中或者进行其它业务逻辑处理。 5. 最后,在关闭串口的槽函数中,调用QSerialPort的close函数关闭串口。 通过以上步骤,可以设计一个简单的Qt界面,用于串口数据的采集。当用户点击相应按钮时,可以打开串口、读取数据,并在界面上显示或者进行进一步处理。在设计时,还可以添加错误处理、自动刷新等功能,以提高用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值