2026a

# 专题说明


本文介绍利用 Qt 进行开发。

# Qt 界面开发教程

Sysplorer 依赖于 Qt 对界面进行开发,因此推荐用户对 APP 的界面开发也使用 Qt 程序,以下简单介绍 Qt 相关教程,更详细教程可在网上进行学习。

Qt 是一个跨平台的 C++ 开发库,主要用来开发图形用户界面(Graphical User Interface,GUI)程序,Qt 还存在 Python、Ruby、Perl 等脚本语言的绑定, 也就是说可以使用脚本语言开发基于 Qt 的程序。下面主要介绍 C++ 平台利用 Qt 进行界面开发。

# VS2017 中配置 Qt5.14教程 (opens new window)

# Qt 界面设计教程

  1. 基于 VS2017 新建一个 Qt Application 工程:

  2. 双击打开对应的\*.ui文件,进入designer.exe程序如下:

  3. 软件主要界面介绍,详细介绍可查看相关官方教程。

  4. 创建一个用于设计的窗口:初次打开软件,应该已经直接创建了,若不存在可在 File>New 中打开,或者工具栏第一个,简略介绍如下:

    直接选第一个,单击 Create,创建完成之后:

  5. 可将旁边的其他控件拖拽进主窗口中,进行界面的开发和设计,常用控件及介绍如下:

    • 标签(Label)—— QLabel(PyQt5)
    • 按钮(Button)—— QPushButton
    • 行编辑(lineEdit)—— QLineEdit
    • 组合框(ComboBox)—— QComboBox
    • 复选框(CheckBox)—— QCheckBox
  6. 利用 Qt 开发完成的弹簧阻尼 APP 设计如下:

# Qt 信号和槽

  1. 什么是信号和槽?

    信号和槽是用于对象之间的通信,它是 Qt 的核心机制,在 Qt 编程中有着广泛的应用。

    举个例子,在一个十字路口,信号灯变成了绿色,对面的汽车看到后就启动了。信号灯就是发送信号的对象,绿灯亮是它发送的信号(signal),汽车是接收对象,汽车行驶是汽车对信号的响应,也叫槽(slot)。

    再举一个例子,比如在一个主窗口内有一个关闭按钮,如果单击这个按钮,窗口就会关闭,那么关闭按钮是发送信号的对象,它发送的信号是单击,接收信号的对象是窗口,响应信号的槽是关闭窗口。

  2. 信号和槽代码实例

    • 主要通过 connect + 宏的方式进行通信连接

    connect(发送对象,信号,接收对象,槽函数),其中发送信号和槽函数需要用 SIGNAL() 和 SLOT() 来进行声明。

    connect 函数声明如下:

    [static] QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
    

    比如单击按钮关闭窗口的例子,代码可以这样写:

    connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(close()));
    

    如果想自定义槽函数,需要先将槽函数的声明添加到类的 slots 中。比如我们对一个 QLineEdit 控件添加一个接收 textEdited 信号的槽函数 onTextEdited。

    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
    
    private slots:
        void onTextEdited(QString);
    
    private:
        Ui::MainWindow *ui;
    };
    

    然后实现函数,并用 connect 与信号连接。

    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
        , ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(close()));    
        connect(ui->lineEdit, SIGNAL(textEdited(QString)), this, SLOT(onTextEdited(QString)));
    }
    
    void MainWindow::onTextEdited(QString s)
    {
        qDebug() << s;
    }
    
  • 使用 Qt Creator 界面添加信号的槽函数

    另外一种方式不需要使用 connect 函数,可以通过 Qt Creator 界面来完成发送信号和槽函数的连接,比如我们右击一个按钮,然后选择转到槽

    选择信号,我们单击 QAbstractButton 的 clicked() 信号,表示按钮被右击:

    接下来,Qt Creator 会自动为我们生成如下代码,首先是槽函数的声明:

    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
    
    private slots:
        void on_pushButton_clicked();
        
    private:
        Ui::MainWindow *ui;
    };
    

    然后是槽函数的实现:

    void MainWindow::on_pushButton_clicked()
    {
        
    }
    

    使用这种方法我们不需要使用 connect 函数将信号与槽函数做连接。 这里槽函数的命名有一定的规则,一般是 on_objectname_signal 这样来命名的。这种方法优点是减少了自己手动敲代码的工作量,缺点是究竟有哪些信号与槽函数做了连接不易被发现,没有 connect 函数看起来直观。

总结:Qt 使用 QDesigner 进行界面设计开发,并利用 Qt 语言结合 C++ 特性以及相关的信号和槽函数机制对界面相关事件进行处理,详细开发教程可通过网上资源 QT 学习教程 (opens new window)进行学习。

# 开发工程构建

基于 VS2017 版本进行开发,请确保已安装 VS2017 Qt 插件,并安装 Qt5.14.2 等相关开发环境。

# 新建工程

基于 VS2017 新建一个 Qt Application 工程,命名为 MassSpringDamperApp:

# 输出目录配置

  • 将输出目录配置到安装的 SDK 路径的 bin 目录:$(MWBin)$(Configuration)

  • 将 MWBin 换为实际 SDK 对应路径:SDK安装路径\bin\win_msvc2017x64

  • $(MWBin)$(MoKeyerface)$(MWInclude)需在电脑中的系统环境变量中将 SDK 安装路径配置上:

# 附加包含目录配置

  • 包含目录添加 SDK 的 include 与 interface 目录:

    .\GeneratedFiles
    .
    $(QTDIR)\include
    .\GeneratedFiles\$(ConfigurationName)
    $(QTDIR)\include\QtCore
    $(QTDIR)\include\QtGui
    $(QTDIR)\include\QtWidgets
    $(MWInclude)
    $(MWInclude)\boost161
    $(MoKeyerface)
    $(MoKeyerface)\modelica_services
    $(MoKeyerface)\common_kits
    
  • MWInclude ——SDK安装路径/include

  • MoKeyerface ——SDK安装路径/interface

# 链接库依赖项配置

  • 依赖库目录添加 SDK 的 bin\lib 与文件输出目录

    $(OutDir)
    $(QTDIR)\lib
    $(MWBin)\lib
    

# 附加依赖项配置

  • Debug 下的附加依赖项设置为:

    qtmaind.lib
    Qt5Cored.lib
    Qt5Guid.lib
    Qt5Widgetsd.lib
    mw_develop_d.lib
    modelica_compiler_d.lib
    mw_graphics_view_d.lib
    mw_class_manager_d.lib
    mw_global_d.lib
    mw_help_d.lib
    mw_sim_inst_d.lib
    mw_sim_plot_d.lib
    model_var_tree_d.lib
    
  • Release 下的附件依赖项设置为:

    qtmain.lib
    Qt5Core.lib
    Qt5Gui.lib
    Qt5Widgets.lib
    mw_develop.lib
    modelica_compiler.lib
    mw_graphics_view.lib
    mw_class_manager.lib
    mw_global.lib
    mw_help.lib
    mw_sim_inst.lib
    mw_sim_plot.lib
    model_var_tree.lib
    

# 内置案例库

SDK 安装完成后,SDK 安装目录下的 examples 存在以下案例,使用 Visual Stiudio 2017 打开Examples_vs2017.sln可查看并运行对应案例的源码,文件目录如下所示:

案例中的代码运行环境需满足以下配置:

类型 环境
操作系统 Win7 SP1 及以上版本
Qt Qt5.14.2 x86 或 x64 版本
IDE Microsoft Visual Studio 2017
IDE插件 VS2017 的 Qt 开发插件
编译环境 Release x64 或 Debug x64

# MassSpringDamperApp

质量-弹簧-阻尼仿真模型软件,软件界面如下所示,代码详解请参见质量弹簧阻尼 APP 介绍

# MwBatchSimPlugin

批量仿真插件开发,软件界面如下所示,代码详解请参见批量仿真 APP

# MwVehicleApp

车辆综合模型软件,软件界面如下所示,代码详解请参见车辆仿真 APP

# Step Forward Demo

本示例主要展示单步仿真,包括:

  • 单步模式启动仿真、控制仿真
  • 仿真结束显示变量曲线

MWORKS.SDK 曲线窗口类定义为 MwSimPlotWindow,本示例 CustomPlotWindow 继承自该类。

编译 PID_Controller 模型成功后,创建 MwSimData 并设置仿真模式为 Sim_InteractiveStepMode,然后启动仿真。

void CustomPlotWindow::SlotCompileFinish()
{
    compileThread->quit();
    this->setEnabled(true);

    QString sim_dir = QFileInfo(QApplication::applicationDirPath() + "\\..\\..\\..\\examples\\StepForward\\Sim").absoluteFilePath();
    QString sim_result_path = QFileInfo(sim_dir + "\\Result.msr").absoluteFilePath();
    simData = new MwSimData(L"PID_Controller", sim_result_path.toStdWString(), sim_dir.toStdWString());
    if (!simData->InitializeSimInst())
    {
        simData = nullptr;
        return;
    }
    simCtrl->RebindSimData(simData);
    MwExperimentData exp_data;
    exp_data.stopTime = 10.0;
    exp_data.intervalLength = 1;
    exp_data.numberOfIntervals = 10;
    simData->ApplyExperimentData(&exp_data);
    simCtrl->StartSimulate(MwSimControl::Sim_InteractiveStepMode);
    progressBar->setValue(0);
    labelTime->setText("0.0s");
}

单步模式下,每一步都会触发 SigSimStep 信号,并暂停仿真。 需调用 SimulateNextStep 恢复仿真。

void CustomPlotWindow::SlotSimStep(double cur_time)
{
    progressBar->setValue(cur_time * 10);
    labelTime->setText(QString::number(cur_time) + "s");
    this->setEnabled(true);
}

仿真结束后,调用 MwSimPlotWindow 的接口 AddCurveToCurrentView 将变量曲线添加到曲线视图中。

void CustomPlotWindow::SlotSimStopped(int exit_code)
{
    this->setEnabled(true);
    progressBar->reset();
    labelTime->setText("0.0s");
    this->AddCurveToCurrentView("PI.y", simData);
    actionSimStep->setEnabled(false);
}

# Component Parameter Panel Demo

本示例主要展示自定义修改组件参数面板,包括:

  • 调整参数面板的列顺序
  • 组件参数的显示与隐藏

MWORKS.SDK 组件参数面板的类定义为 MwModelParameterTabWidget,本示例 CustomParameterTabWidget 继承自该类。

MWORKS 组件参数面板的默认列顺序为:参数名称、参数值、参数单位、参数说明, 本示例将其调整为:参数说明、参数值、参数单位、参数名称。

此外,函数 IsNeedToDisplay() 让用户决定每个参数的显示或隐藏。系统默认是每个参数都显示。

CustomParameterTabWidget::CustomParameterTabWidget(MwMoGraphicsViewController* mo_controller,
    MwParamEditMode pem, QWidget* parent)
    : MwModelParameterTabWidget(mo_controller, pem, parent)
{
    //调整列顺序
    this->SetColumnOrder(3, 1, 2, 0);
}

CustomParameterTabWidget::~CustomParameterTabWidget()
{

}
bool CustomParameterTabWidget::IsNeedToDisplay(const MwParamInfo& param) const
{
    if (iModelKey == 0)
    {
        return true;
    }
    MwClassManager *class_mgr = MainWindow::GetInstance()->GetClassManager();
    //模型全名
    std::string type_name = class_mgr->GetMoHandler()->GetFullnameProp(iModelKey);

    //隐藏参数集
    QList<std::string> hidden_params;
    hidden_params << "pi";

    if (hidden_params.count(param.ident))
    {
        return false;
    }
    return true;
}

# GraphicsViewDropEvent

本示例主要展示,将模型树上的组件拖拽到模型视图中:

主要通过在 TreeView 中重写startDrag函数,将拖拽的组件全名字符串与image/modelica-type进行绑定,设置到 QmimeData 数据中,主要代码如下:

void ClassBrowserTreeWidget::startDrag(Qt::DropActions supported_actions)
{
    MoKey selected_key = GetSelectedKey();
    if (selected_key == 0)
    {
        return;
    }
    QStandardItem* item = treeModel->itemFromIndex(currentIndex());
    QModelIndexList index_list;
    index_list << currentIndex();
    QMimeData* mime_data = treeModel->mimeData(index_list);
    QByteArray item_data;
    QDataStream dataStream(&item_data, QIODevice::WriteOnly);
    QString selected_name = QString::fromStdString(ClassMgrPtr->GetMoHandler()->GetFullnameProp(selected_key));
    dataStream << selected_name;
    mime_data->setData("image/modelica-type", item_data);
    QDrag *drag = new QDrag(this);
    drag->setMimeData(mime_data);
    //Drag时图标
    {
        QIcon icon = item->icon();
        QPixmap pixmap;
        if (icon.isNull())
        {
            //获得默认图标
            pixmap = QPixmap();
        }
        else
        {
            pixmap = icon.pixmap(QSize(50, 50));
        }
        drag->setPixmap(pixmap);
        qreal adjust = 25;
        drag->setHotSpot(QPoint(adjust, adjust));
    }
    drag->exec(supported_actions);
}

# License 配置

为保证大型模型能够正常仿真,及相关功能能够正常使用,在调用 SDK API 前需提前设置 License 文件,SDK 提供 MwLicenseService 类用于设置相关 License,该类中提供了两种设置方法,通过有 UI 方式弹出 License 设置对话框或无 UI 方式接口配置 License。

MwLicenseService 类初始化:

//license
    MwLicenseService* pLicenseService = new MwLicenseService(nullptr);
    pLicenseService->Initialize();
  1. 弹出 License 设置对话框:

    pLicenseService->StartupLicenseSetDialog();
    
  2. 接口方式配置 License,可以设置单机版 License 文件路径,也可以设置网络版 License 的 IP 地址和端口号:

    bool load_license_success = pLicenseService->SetLicenseFilePath(L"E:\\File\\MWorks.Sysplorer\\License\\mworks_sdk.lic");
    
    bool load_license_success = pLicenseService->SetLicenseIPAddress("172.16.1.18", "27000");
    

# MwGraphicsView 响应机制

# 模块结构

MwGraphicsView 模块是 Sysplorer.SDK 的图形模块,同时也是核心模块,为系统提供图形化建模和文本建模能力,其结构如下图所示:

整个 MwGraphicsView 模块使用 MVC(Model、View、Controller)设计模式。

图形模块中的基本图元充当 MVC 中的“M”(Model),基本实体主要包括:

  • 线段
  • 矩形
  • 椭圆
  • 多边形
  • 文字
  • 图片,支持多种格式(bmp、jpg、png、gif 等)

具体结构如下:

文本视图、图标视图和图形视图三种视图充当 MVC 中的“V”(View),相同的 Model 在不同 View 中有不同的表现形式,同样的组件在图标视图中表示图标,在组件视图表示组件,在文本视图中表示 Modelica 文本;图形视图(MwModelicaGraphicsView)是系统可视化建模最主要的场所,主要负责显示和编辑模型的组件图形数据:

  • 显示图形元素;
  • 交互添加图形元素;
  • 编辑图形元素:拣选,拖动,旋转,翻转,对齐,调整显示顺序等;
  • 进入组件模式,显示组件类型对应的模型视图。

图形视图控制器(MwMoGraphicsViewController)充当 MVC 中的“C”(Controller),主要负责响应底层模型变化,刷新图形视图状态并同步转发模型变化信号,以同步更新界面的其他组件状态。

# 运行机制

图形视图控制器(MwMoGraphicsViewController)定义了一组视图事件(MoGvEvent),这组事件包含:

  • 非法事件:None
  • 图元选择变化:SelectChanged
  • 视图中组件层次变化:ComponentLevelChanged
  • 视图类型变化:ViewChanged
  • 模型窗口变化:MoWindowChanged
  • 活动窗口变化:ActivateWindowChanged
  • 模型数据变化:ModelDataChanged
  • 模型添加信号:ModelAppended
  • 模型移除信号:ModelRemoved

界面组件一方面通过视图控制器向全局发送视图事件信号,另一方面每个组件自身定义了 SlotUpdate 槽函数并与视图控制器的 SigUpdate 信号连接,各自响应全局视图事件信号。

除了视图事件信号外,图形视图控制器还响应内核模型添加(AppendClassSignals)、模型移除(RemoveClassSignals)、模型替换(ReplaceClassSignals)和模型更新(UpdateClassSignals)信号。响应完毕后同步转发到全局供界面控件各自刷新。