找回密码
 立即注册
Qt开源社区 门户 查看内容

程设实验项目一:KTV(用Qt C++编程)

2019-7-27 15:13| 发布者: admin| 查看: 1691| 评论: 0

摘要: 2018级 NJU CS 程序设计基础实验 项目一:KTV点歌系统这是一个类似于学生管理系统的项目,数据结构和算法在四个项目里是最简单的。有限制如下:禁止使用其他编程语言,只能使用C/C++语言禁止使用SQL或MySQL等数据库 ...
2018级  NJU CS  程序设计基础实验  项目一:KTV点歌系统

这是一个类似于学生管理系统的项目,数据结构和算法在四个项目里是最简单的。有限制如下:

  • 禁止使用其他编程语言,只能使用C/C++语言

  • 禁止使用SQL或MySQL等数据库软件


图形界面放在了拓展里,不作为基本要求。

在之前的寒假中,已经做了一个简(zhi)化(zhang)的“酒店管理系统”,先是实现了黑底控制台版,后面学点Qt 重做实现真的图形界面。

厌倦了黑底控制台的界面实现,直接选择使用Qt.

整个实现都是围绕着需求来的,那么先溜一波需求。

从课件ppt里选了两张:





管理员账号:

想过加密什么的,但是毕竟不会,感觉搞点小操作也没意思,就采取了最简单的方式,设置单个账号,管理员ID和密码直接输出到文件。

看同学展示发现是有人在账号这方面拓展的,比如游客与用户账号登录,还有超管什么的...

多窗口转换:

这是最开始很困扰的地方,菜得连这个都不会。

想法是开始窗口,选择进入管理员/用户窗口,退出后回到开始窗口

    //main.cppintmain(int argc, char *argv[]){    QApplication a(argc, argv); StartDialog *s = new StartDialog;    s->show();return a.exec();}
    //startdialog.hStartDialog::StartDialog(){ ... connect(some_object, some_signal, this, &StartDialog::showUserWindow); ...}
    //startdialog.cppvoid StartDialog::showUserWindow(){ UserWindow *w = new UserWindow; close(); w->show();}
    //userwindow.hclassUserWindow : publicQMainWindow{ ...protected:voidcloseEvent(QCloseEvent *event); ...};
    //userwindow.cppvoid UserWindow::closeEvent(QCloseEvent *ev){ songsModel->saveData(); StartDialog *s = new StartDialog; ev->accept(); s->show(); s->move((QApplication::desktop()->width() - s->width())/2,(QApplication::desktop()->height() - s->height())/2); //在屏幕中央显示}

    Model/View:

    核心采用Qt提供的model/view架构。同时使用QSortFilterProxyModel,排序和查询就不用写了。
      struct Song{    int id;  //# 这是一个困惑的地方,因为按照上面的需求这就是在list中的下标(索引),感觉没必要QString name;QString singerName;QString pinyinAbbr;float score;int scoredTimes;int playedTimes;
          QUrl url;  //# 后面为了播放音乐加的};
      //# 重载插入和提取运算符,之后便可对QList类型直接输入输出QDataStream &operator<<(QDataStream &stream, const Song &song);QDataStream &operator>>(QDataStream &stream, Song &song);
      class SongsModel : public QAbstractTableModel{private: QList<Song> songs; ......};
      根据文档,重载

      rowCount(), columnCount(), data(), headerData(),

      以及insertRows(), removeRows()

      其他的就不讲了.

      起初我把SongsModel作为其他类的一个成员,而管理员类和用户类是分开的,用起来不方便,然后就创了个全局对象.

      要求里提到了分页(上图),而用Qt的表格类是不需要做分页的(有滚动条),但是我也想做个分页跳转。

      Qt没有直接提供分页,网上搜也没搜到,唯一搜到的是用来解决大数据响应慢的(虽然只显示一部分但Qt会加载全部数据),而那样的话就不是直接用model/view架构那么简单了。

      最后在StackOverflow上我找到了我想要的——调用verticalScrollBar()

      如此一来一个无用的分页跳转就能完成了.



      完工后的管理员界面

      底下填数字的框用了QSpinBox,当时想的是QSpinBox直接面向数字就用了,但是相关signal在这里要绕一下,而且右侧的上下键多余了。

      要把spinBox 和 verticalScrollBard的value关联起来,并且是双向的,那么就有个问题。这两个对象能用的signal只有valueChanged,直接connect的话,其中一个valueChanged改变另一个的值,就会再次触发信号,造成无限循环。

      解决方法很简单,在其中一个槽函数中,对要改变值的对象调用setEnabled(false),取消其对信号的反应,最后再setEnabled(false)
        pageSpinBox->setEnabled(false);pageSpinBox->setValue(some_val);pageSpinBox->setEnabled(true);

        也可以用QLineEdit,利用信号editingFinished()就不用上面那样了,但是需要加输入判断。

        歌曲导入:

        该项目并不要求真的播放歌曲,我一开始也没想,但是做着做着就想用音乐文件了(主要是想起了音乐文件会附带相关信息)。所以原本的从txt文本导入,我做成了从音乐文件(*.mp3 *.wav *.ape *.flac)导入。在导入过程中,需要手动输入拼音缩写。

        顺带提一下,我听的歌名字都是日文/英文,拼音缩写就很zz了。

        在实现过程中,我并不想用除Qt以外其他的库,因为找库和安装感觉有点麻烦。但是麻烦这点终究逃不过。

        首先添加 multimedia 模块,在 .pro项目文件中加上:

          QT += core guiQT       += multimediagreaterThan(QT_MAJOR_VERSION, 4): QT += widgets
          (写这篇blog的时候已经忘了这几个语句的作用了)

            QMediaPlayer *player = new QMediaPlayer;player->setMedia(some_url);player->play();
            按理上面3行代码就能播放音乐了,但是,什么声音都没有而且程序输出了一串神秘的错误信息。

            一番折腾后,我知道了,还需要自行下载安装解码器(如LAV Filters)

            听到程序放出了歌,有点小开心。

            然后是从文件获取歌曲信息,找来找去只找到下面一种方式:
              connect(player, &QMediaObject::metaDataChanged, this, &getMetaData);//这里简写了player->setMedia(some_url);
              void getMetaData() { ... player->metaData(const QString &key) ...}/* player->setMedia(some_url); player->metaData(const QString &key);    如果这样不使用信号槽的话,无法获取正确的信息*/
              无法直接获取歌曲信息...

              一番思索后想出的解决方法:

              new 一个QMediaPlayer对象,连接信号和槽,获取后delete,然后再new和connect来获取下一首歌曲的信息
                int curGetInfoRow;void AddSongsDialog::getInfoFromFile(int beginRow){if (beginRow >= songs.size())return; curGetInfoRow = beginRow; getInfoProxyPlayer = new QMediaPlayer; connect(getInfoProxyPlayer, QOverload<>::of(&QMediaObject::metaDataChanged), this, &AddSongsDialog::getMetaData); getInfoProxyPlayer->setMedia(songs.at(curGetInfoRow).url);}
                //slotvoid AddSongsDialog::getMetaData(){ Song &song = songs[curGetInfoRow]; QString title = getInfoProxyPlayer->metaData(QMediaMetaData::Title).toString();if (!title.isEmpty())        song.name = title;
                QString author = getInfoProxyPlayer->metaData(QMediaMetaData::Author).toStringList().join(", ");if (!author.isEmpty())        song.singerName = author;
                delete getInfoProxyPlayer;
                curGetInfoRow++;if (curGetInfoRow >= songs.size())return;
                getInfoProxyPlayer = new QMediaPlayer; connect(getInfoProxyPlayer, QOverload<>::of(&QMediaObject::metaDataChanged), this, &AddSongsDialog::getMetaData); getInfoProxyPlayer->setMedia(songs.at(curGetInfoRow).url);}

                歌曲播放控制:

                直接参照了Qt示例Music Player

                失败的模块文件模块划分:

                先做的AdminWindow,后做的UserWindow,带进度条控制的歌曲播放也是做用户窗口时想出来的。当时觉得用户窗口类成员太多,就新开了一个类,但是很多不该放到这个类里的放进去了,然后整体结构就没那么好看。

                评分星星:

                想实现的是很常见的那种滑动评分的。选择了一张包含所有评分情况的图片(从豆瓣上搞来的),鼠标移动时切换到对应的图片。
                  classRatingWidegt :public QLabel{    Q_OBJECTprotected://鼠标事件voidmouseMoveEvent(QMouseEvent *ev);voidmouseReleaseEvent(QMouseEvent *ev);voidleaveEvent(QEvent *ev);......}
                  //更新显示调用的主要函数QLabel::setPixmap(const QPixmap &);QPixmap::copy(int x, int y, int width, int height);

                  播放列表窗:

                  想法是点下“列表”按钮,显示播放列表的小窗,然后点击非该窗口的地方关闭窗口。自己想了一个方案:
                    MyWidget *playListWidget;connect(showTableBtn, &QAbstractButton::clicked, this, &UserWindowWidgets::showListTable);
                    classMyWidget :public QWidget{protected:    bool event(QEvent *ev);public: MyWidget(QAbstractButton *btn, QWidget *parent = nullptr);private:/* timer的作用是延迟显示列表按钮setEnabled,否则点击按钮会再次打开该窗口  没有想到其他解决方法,只有这个妥协了的方案 */    QTimer timer; QAbstractButton *btn;};
                    MyWidget::MyWidget(QAbstractButton *button, QWidget *parent): QWidget(parent), btn(button){ timer.setSingleShot(true); timer.setInterval(50); connect(&timer, &QTimer::timeout, this, [this]{ btn->setEnabled(true); }); setWindowFlag(Qt::Popup);}bool MyWidget::event(QEvent *ev){if (ev->type() == QEvent::Hide) timer.start();if (ev->type() == QEvent::ActivationChange)if (QApplication::activeWindow() != this) hide();return QWidget::event(ev);}
                    void UserWindowWidgets::showListTable(){ showTableBtn->setEnabled(false);    //点的数据是大致计算然后多次测试得来的 QPoint p = showTableBtn->mapToGlobal(QPoint(0,0)) - QPoint(316, 240); playListWidget->move(p);    playListWidget->show();}


                    图标哪来的?

                    在电脑上下载app.apk文件,解压缩,翻文件夹。

                    这里所有图标均来自于网易云音乐app.

                    参考Qt官方示例:


                    • addressbook

                      (model/view framwork)

                    • Music Player

                      (control with position slider)


                    文件下载:


                    • 项目代码文件:

                      https://pan.baidu.com/s/1SAV5ACjful2oJqFINnQQOA

                      提取码: itj4

                    • 可执行文件:

                      https://pan.baidu.com/s/1Cf-_9pPRWvNdk5RksqqhpA

                      提取码: ux3n

                    (别说github,又不是真公开,不想麻烦再创个号)

                    这个项目是在3月底到4月初做的,到写这篇blog已经过了3个多月,再翻代码还是基本都看得懂的,虽然什么注释都没写。

                    也算是真正写了一个项目,不过和实际项目工程相比还是太幼稚太年轻。

                    第四周的最终展示,应该是我第一次自己做出成果然后pre的了,讲的挺差的,不过一运行程序还是挺惊艳的,有了“产品”的标签。


                    ----------------------------------------------------------------------------------------------------------------------
                    我们尊重原创,也注重分享,文章来源于微信公众号:VoidZero,建议关注公众号查看原文。如若侵权请联系qter@qter.org。
                    ----------------------------------------------------------------------------------------------------------------------

                    鲜花

                    握手

                    雷人

                    路过

                    鸡蛋
                    
                    公告
                    可以关注我们的微信公众号yafeilinux_friends获取最新动态,或者加入QQ会员群进行交流:190741849、186601429(已满) 我知道了