机械设计软件-Qt实现(2)

本篇博客是该软件的源代码介绍。从软件设计的角度详细分析设计架构。不涉及到机械设计的专业知识。阅读完整篇博客后,用户应该能够动手实现自己的设计软件了。如果你对机械设计部分的实现感兴趣,也可以直接看源码,代码的注释非常丰富。

软件及源代码获取途径见:机械设计软件-Qt实现(1)-CSDN博客

总体框架

源代码主要分为3个部分—主界面设计,设计表类,设计类。采用了模块化设计。主界面设计的重点是Qt数据库模块的使用和模型视图结构。设计表类利用到了多态,继承和Qt的信号与槽机制。设计类则使用了策略模式,将设计UI和设计信息拆分开,方便程序的拓展。

下文中提到主界面类的一律以MechanicalDesign代指,设计表类对应DesignVec,设计类对应Design。设计UI对应DesignUI,设计信息对应DesignInfo。实际设计中也是如此。

主界面介绍

图片如下:

设计好数据库文件如下,直接用我设计好的也行。

具体实现

这里删除了几乎所有槽函数的声明。

class MechanicalDesign : public QMainWindow
{
    Q_OBJECT
private:
    DesignVec* m_Vec;           //设计表类
    QSqlDatabase DB;            //数据库连接
    QSqlTableModel *tabModel = nullptr;   //数据模型,考虑到用户可能没有打开数据库
    QItemSelectionModel *selModel;  //选择模型
    QDataWidgetMapper *dataMapper;  //数据映射
    //状态栏标签
    QLabel * labDate;               //设计日期
    QLabel* labCount;               //设计数量
    QLabel* labInfo;                //设计说明
    //区别开历史设计和本次设计
    int base = 0;                  //历史设计的数量
public:
    explicit MechanicalDesign(QWidget *parent = nullptr);
    ~MechanicalDesign();

private slots:
   void do_addDesign(int index);   //往数据库中添加一个设计

private:
    void OpenTable();               //打开数据表
    void do_currentRowChanged(const QModelIndex&current, const QModelIndex& previous);
    void showRecordCount();           //显示设计数量
private:
    Ui::MechanicalDesign *ui;
};

QSqlDatabase对象是与实际的数据建立连接的。QSqlTableModel对象是为模型视图机构中的“模型”。视图就是上图中“设计信息”框中的那个图表——QTableView对象。QItemSelectionModel对象(selModel)记录当前所选中的模型。QDataWidgetMapper对象(dataMapper)会将数据库文件中的字段映射到对应组件中,这里为文本框。鼠标点击不同设计时,“显示主要设计”文本框中会显示不同数据,这里便用到了selModel和dataMapper。

具体代码如下,省略了构造函数中的不重要代码。

MechanicalDesign::MechanicalDesign(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MechanicalDesign)
{
    ui->setupUi(this);
    m_Vec = new DesignVec(this);        //创建设计表对象
    this->resize(750,500);
    //完成一个设计时,添加一条设计信息
    connect(m_Vec,&DesignVec::finishADesign,this,&MechanicalDesign::do_addDesign);
    ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows); //行选择
    ui->tableView->setSelectionMode(QAbstractItemView::SingleSelection);//单项选择
    ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);  //不可编辑
    ui->tableView->setAlternatingRowColors(true);

}

void MechanicalDesign::OpenTable()
{//创建数据模型,打开数据表
    tabModel = new QSqlTableModel(this,DB);
    tabModel->setTable("designInfo");
    tabModel->setEditStrategy(QSqlTableModel::OnManualSubmit);  //数据保存方式
    tabModel->setSort(tabModel->fieldIndex("Type"),Qt::AscendingOrder); //排序方式
    if(!(tabModel->select())){                  //查询数据失败
        QMessageBox::critical(this,"错误信息","打开数据表错误,错误信息:\n"+tabModel->lastError().text());
        return;
    }
    //显示记录条数
    showRecordCount();
    base = tabModel->rowCount();        //历史设计的数量
    //设置字段显示标题
    tabModel->setHeaderData(tabModel->fieldIndex("Label"),Qt::Horizontal,"设计标签");
    tabModel->setHeaderData(tabModel->fieldIndex("Type"),Qt::Horizontal,"设计种类");
    tabModel->setHeaderData(tabModel->fieldIndex("Level"),Qt::Horizontal,"精度");
    tabModel->setHeaderData(tabModel->fieldIndex("Material"),Qt::Horizontal,"材料");
    tabModel->setHeaderData(tabModel->fieldIndex("Meme"),Qt::Horizontal,"主要设计信息");
    //创建选择模型
    selModel = new QItemSelectionModel(tabModel,this);
    //当前行变化时,发射currentRowChanged信号
    connect(selModel,&QItemSelectionModel::currentRowChanged,this,&MechanicalDesign::do_currentRowChanged);
    //模型视图结构
    ui->tableView->setModel(tabModel);
    ui->tableView->setSelectionModel(selModel);
    ui->tableView->setColumnHidden(tabModel->fieldIndex("Meme"),true);

    //创建界面组件与模型字段的数据映射
    dataMapper = new QDataWidgetMapper(this);
    dataMapper->setModel(tabModel);
    dataMapper->setSubmitPolicy(QDataWidgetMapper::AutoSubmit);
    //与具体字段的映射
    dataMapper->addMapping(ui->textEdit,tabModel->fieldIndex("Meme"));  //显示主要设计信息
    dataMapper->toFirst();              //移动到首记录
    //更新界面组件的使能状态
    ui->actOpenDB->setEnabled(false);
    ui->actSave->setEnabled(true);
}

void MechanicalDesign::do_addDesign(int index)
{//在数据库中添加一条设计信息
    if(tabModel == nullptr){
        return; //没有打开数据库的话
    }
    DesignInfo* tmpInfo = m_Vec->infoFromIndex(index);  //返回一条设计信息
    if(tmpInfo == nullptr)//判断返回信息是否有效
        return;
    QSqlRecord rec = tabModel->record(); //获取一条空记录,只有字段定义
    int val = (tabModel->rowCount() + 1)%100 * 1000 + QRandomGenerator::global()->bounded(11,999);
    //随机生成一段标签序列号
    QString Label = QDate::currentDate().toString("yyyy-MM-dd")+"-"+QString::number(val);
    rec.setValue(tabModel->fieldIndex("Label"),Label);
    rec.setValue(tabModel->fieldIndex("Type"),tmpInfo->type());
    rec.setValue(tabModel->fieldIndex("Level"),tmpInfo->level());
    rec.setValue(tabModel->fieldIndex("Material"),tmpInfo->material());
    rec.setValue(tabModel->fieldIndex("Meme"),tmpInfo->info());
    tabModel->insertRecord(tabModel->rowCount(),rec);   //插入数据模型的最后

    selModel->clearSelection();
    QModelIndex curIndex = tabModel->index(tabModel->rowCount()-1,1);
    selModel->setCurrentIndex(curIndex,QItemSelectionModel::Select);//设置当前行
    showRecordCount();
    ui->actSave->setEnabled(true);  //使能保存按钮
}

构造函数分为两部分,一是创建DesignVec对象,并将DesignVec的信号finishADesign,与槽函数do_addDesign连接。二是设计tableView的属性。

一个设计完成后,应该将设计信息显示到左侧的“设计信息”中。这里利用了DesignVec的infoFromIndex接口获取对应的设计信息。selModel要更新当前选择的设计(最新的一个)。

主界面设计的关键部分就这些。其它操作的实现,如显示“不同设计”,直接参考源代码即可。

设计表类介绍

DesignVec类可以说是本项目中最关键的部分,充分体现了面向对象的设计思想。DesignVec类的存在使得主界面类不需要关心设计类是如何工作的,主界面只需要调用对应的接口即可

具体实现

class DesignVec : public QObject
{
    Q_OBJECT
private:
    QList<Design*> m_vec;       //使用多态,以容器储存设计类指针
public:
    explicit DesignVec(QObject *parent = nullptr);
    QString showInfo(int index);        //显示单行设计信息
    QStringList showAll();              //显示所有设计信息
    void addDesign(int type);           //添加设计
    void deleteDesign(int index);       //删除设计
    void deleteAll(){m_vec.clear();}    //删除所有设计
    DesignInfo* infoFromIndex(int index);   //返回设计信息指针
    enum DesignType {TGearDesign, TAxleDesign, TBearingDesign, TKeyDesign};//枚举变量
signals:
    void finishADesign(int index);       //完成一个设计后,自动发送该信号
private slots:
    void do_finishADesign();    //对应的槽函数
};

Design * tmpDesign = nullptr;
    switch(type){
    case TGearDesign:
        tmpDesign = new GearDesign(this);break;
    case TAxleDesign:
        tmpDesign = new AxleDesign(this);break;
    case TKeyDesign:
        tmpDesign = new KeyDesign(this);break;
    case TBearingDesign:
        tmpDesign = new BearingDesign(this);break;
    default:
        break;
    }
    if(tmpDesign != nullptr){
        m_vec.append(tmpDesign);        //添加设计对象
        //建立连接
        connect(tmpDesign,&Design::finishADesign,this,&DesignVec::do_finishADesign);
        tmpDesign->startDesign();       //启动设计UI
    }

这里定义了一个枚举值:DesignType,方便调用addDesign函数。还定义了一个QList对象m_vec,用来统一管理所有的设计类,元素类型为Design*。为什么能这么做?因为不同的设计类都继承自Design类,实现了类型统一。

因为使用了继承,所有每一个设计类都有一个相同的公共接口,DesignVec类只需调用接口即可,无需关心操作的到底是何种设计。这里用到了对接口编程,而不是对实现编程,当然,也用到了多态,理解成动态绑定也行。

主界面中,一个设计完成后,应该将设计信息显示到左侧的“设计信息”中,利用DesignVec的infoFromIndex接口获取了对应的设计信息。但DesignVec如何知道哪个设计完成了呢?可以利用观察者模式。让DesignVec成为观察者。这里我使用的是Qt的信号与槽机制。通过sender函数获取信号的发送者。再利用循环得出信号发送者的在DesignVec中的位置。

void DesignVec::do_finishADesign()
{//需要知道信息发送者的在设计表中的位置
    Design* object = static_cast<Design*>(sender());     //信号发送者
    int index = -1;     //对应的位置
    for(int i = 0; i < m_vec.count();i++){
        if(object == m_vec.at(i)){
            index = i;
            break;
        }
    }
    emit finishADesign(index);  //发送完成一个设计的信号
}

设计类介绍

class Design : public QObject{  //抽象基类
    Q_OBJECT
protected:
    DesignInfo* m_info; //设计信息
    DesignUI* m_UI;     //设计UI
signals:    //发送信号
    void finishADesign();   //完成设计
public slots:
    void do_finishDesign();
public:
    explicit Design(QObject *parent = nullptr);
    virtual void startDesign() = 0;       //开始设计,纯虚函数
    virtual DesignInfo* info() const {return nullptr;}//返回设计信息
};

//设计UI类
class DesignUI : public QMainWindow{
    Q_OBJECT
protected:
    DesignInfo* m_info;
signals:
    void finishDesign();        //完成设计信号
public:
    explicit DesignUI(QWidget *parent = nullptr);
    ~DesignUI();
    virtual void startDesign(DesignInfo* info){} //两个虚函数
protected:
    virtual void reFresh(){}
};

Design中有两个对象DesignUI,和DesignInfo。DesignUI负责显示UI和计算参数,DesignInfo负责保存设计结论,提供公共接口startDesign。内部启动UI对象。因为Design类是抽象基类,所以子类必须实现自己的startDesign函数。DesignUI中有一个DesignInfo对象,Design类中又有一个DesignInfo对象,这是为啥?主要是因为主界面显示设计时,需要用到DesignInfo对象,但主界面类不应直接操作DesignUI类。如果Design类给DesignUI传入一个DesignInfo对象,启动设计。DesignVec再从Design类中获取DesignInfo对象即可解决这个问题。

如果创建了一个新的设计类,例如GearDesign,只需要重新实现一个UI类,如果保存不同格式的设计信息,实现一个Info类即可。这里把一个Design要进行的工作交给DesignUI和DesignInfo实现,Design类不必关心他们是如何实现的,只需调用对应接口。这样一来,程序就变得很灵活了。所使用的就是策略模式

DesignInfo的实现非常简单,这里就不贴出代码了。其它设计类的代码也很简单,因为我把不同设计的重点都放在DesignUI中了。当然还有图表的显示。

有些设计需要等待用户输入完一些参数后才能继续下一步操作。那如何知道用户输完信息没?我一开始的设想是用一个while循环判断一个bool变量,用户按下按钮后更改bool变量,进行下一步。但实际操作中,UI界面没有出现,Debug后发现是线程卡在while循环中了,用多线程可以解决这个问题,但有点杀鸡用牛刀的嫌疑了。最后的实现是把一部分操作放到按钮对应的槽函数中。点击按钮,继续进行设计。

后记

源代码的介绍就到此为止了。UI界面的设计其实也是一个很重要的部分,因为关系到软件好不好使用。熟练使用Qt Designer是关键。

                                                                                                                                2024/9/5 于长沙

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值