勇哥学习了十来天的QT,边学边写了个小练习。放上来做为一些知识点的备查。
这个小程序的功能有:
磁盘读取歌曲名列表
双击歌曲名调用外部播放器播放
可以支持首拼方法模糊搜索歌曲
控件的右键菜单

功能很简单,但是用到了一些常用的Qt知识点,包括:
QListWidget控件
QTabWidget控件
QPushButton控件
QLineEdit控件
控件相关的信号与槽
SIGNAL和SLOT宏
foreach
try...catch...
QSettings类
QFile类
QTextStream类
QCoreApplication类
QString类
QStringList类
QProcess类
tuple
qDebug
QMap
QMenu
QAction
vistual assist插件
Qt Designer
下面是一些知识点相关的贴子:
磁盘文件操作
控件右键菜单
汉字转拼音
启动外部程序
异常处理
配置文件读写
下面贴上业务逻辑的主要代码:
work.h
#pragma once
#pragma execution_character_set("utf-8")
#include <QSettings>
#include "Config.h"
#include <tuple>
class Work
{
public:
//歌曲列表类型
static enum MusicListTypeEnum
{
allMusic,
selMusic,
//搜索结果列表,用于全部歌曲标签
searchMusicAll,
//搜索结果列表,用于选中的歌曲标签
searchMusicSel
};
private:
//全部歌曲的音乐列表,<歌曲名,歌曲文件名路径串>
QMap<QString, QString> musicListAll;
//选中歌曲的音乐列表,<歌曲名,歌曲文件名路径串>
QMap<QString, QString> musicListSelect;
//全部歌曲名称的拼音字典
QMap<QString, QString> musicAllPyList;
//选择歌曲名称的拼音字典
QMap<QString, QString> musicSelPyList;
Config* config;
private:
//更新歌曲的拼音(全部的歌曲)
void updateMusicAllPy();
//更新歌曲的拼音(选中的歌曲)
void updateMusicSelPy();
public:
Work(Config* config);
~Work();
//读取音乐列表
void readMusicList(Work::MusicListTypeEnum musictype);
//按歌曲名或者简拼搜索歌曲
QMap<QString, QString> searchMuslicList(QString name, Work::MusicListTypeEnum musictype);
//取歌曲列表(枚举:allMusic,selMusic)
QMap<QString, QString> getMusicList(Work::MusicListTypeEnum musictype);
//添加歌曲到选中的歌单
void addMusic(std::tuple<QString, QString > data);
//删除选中歌单的歌(不能删除全部歌单中的歌)
void delMusic(QString musicName);
//取歌曲名拼音列表(全部歌曲)
QMap<QString, QString> getMusicAllPyList();
//取歌曲名拼音列表(选中的歌曲)
QMap<QString, QString> getMusicSelPyList();
//删除缓存的歌曲列表
void delMusicListFile(Work::MusicListTypeEnum musictype);
};work.cpp
#include "Work.h"
#include "CdiskFile.h"
#include "Cchineseletterhelper.h"
Work::Work(Config* config)
{
this->config = config;
}
Work::~Work()
{
}
void Work::readMusicList(Work::MusicListTypeEnum musictype)
{
QString allmusicPath = config->get("mtvconfig", "allMusic").toString();
QString selmusicPath = config->get("mtvconfig", "selMusic").toString();
QString mtvPath = config->get("mtvconfig", "mtvDir").toString();
if (musictype == Work::MusicListTypeEnum::selMusic){
QString txt = CdiskFile::readAllTxt(selmusicPath);
if (musicListSelect.count() < 2){
QFile f1;
if (f1.exists(selmusicPath)){
//如果selMusic.txt存在由磁盘缓存文件重建内存表
musicListSelect = CdiskFile::QMapLoad(selmusicPath);
}
f1.close();
updateMusicSelPy();
}
return;
}
else{
try
{
QString txt = CdiskFile::readAllTxt(allmusicPath);
if (musicListAll.count() < 2){
QFile f1;
if (f1.exists(allmusicPath)){
//如果allMusic.txt存在由磁盘缓存文件重建内存表
musicListAll = CdiskFile::QMapLoad(allmusicPath);
}
else{
//如果allMusic.txt不存在则读磁盘文件名重建内存表
QStringList list1 = CdiskFile::getFileNames(mtvPath, QStringList() << "*.mkv");
foreach(QString m, list1){
musicListAll.insert(m, mtvPath + "/" + m);
}
}
updateMusicAllPy();
CdiskFile::QMapSave(allmusicPath, musicListAll);
}
return;
}
catch (...)
{
}
}
}
QMap<QString, QString> Work::searchMuslicList(QString name, Work::MusicListTypeEnum musictype)
{
QMap<QString, QString> map;
if (musictype == Work::MusicListTypeEnum::allMusic){
auto v= musicListAll.find(name);
if (v != musicListAll.end()){
map.insert(v.key(), v.value());
}
}
else{
auto v = musicListSelect.find(name);
if (v != musicListSelect.end()){
map.insert(v.key(), v.value());
}
}
return map;
}
QMap<QString, QString> Work::getMusicList(Work::MusicListTypeEnum musictype)
{
if (musictype == Work::MusicListTypeEnum::allMusic){
return musicListAll;
}
else{
return musicListSelect;
}
}
void Work::addMusic(std::tuple<QString ,QString > data)
{
QString selmusicPath = config->get("mtvconfig", "selMusic").toString();
if (!musicListSelect.contains(std::get<0>(data))){
musicListSelect.insert(std::get<0>(data), std::get<1>(data));
CdiskFile::QMapSave(selmusicPath, musicListSelect);
}
}
void Work::delMusic(QString musicName)
{
QString selmusicPath = config->get("mtvconfig", "selMusic").toString();
if (musicListSelect.contains(musicName)){
musicListSelect.remove(musicName);
CdiskFile::QMapSave(selmusicPath, musicListSelect);
}
}
void Work::updateMusicAllPy()
{
foreach(QString m, musicListAll.keys()){
musicAllPyList.insert(m, Cchineseletterhelper::GetFirstLetters(m));
}
}
void Work::updateMusicSelPy()
{
foreach(QString m, musicListSelect.keys()){
musicSelPyList.insert(m, Cchineseletterhelper::GetFirstLetters(m));
}
}
QMap<QString, QString> Work::getMusicAllPyList(){
return musicAllPyList;
}
QMap<QString, QString> Work::getMusicSelPyList(){
return musicSelPyList;
}
void Work::delMusicListFile(MusicListTypeEnum musictype)
{
QFile file;
auto fname1 = config->get("mtvconfig", "allMusic").toString();
auto fname2 = config->get("mtvconfig", "selMusic").toString();
try
{
if (musictype == Work::MusicListTypeEnum::allMusic){
if (file.exists(fname1)){
QFile::remove(fname1);
}
}
else{
if (file.exists(fname2)){
QFile::remove(fname2);
}
}
}
catch (...)
{
}
}QtForOpencv.h
#pragma once
#pragma execution_character_set("utf-8")
#include <QtWidgets/QWidget>
#include "ui_QtForOpencv.h"
#include <QLabel>
#include <QCoreApplication>
#include <QSettings>
#include "Config.h"
#include "Work.h"
#include "CdiskFile.h"
#include "CcsvHelper.h"
#include <QTabWidget>
#include <QListWidget>
#include <QMenu>
class QtForOpencv : public QWidget
{
Q_OBJECT
public:
QtForOpencv(QWidget *parent = Q_NULLPTR);
~QtForOpencv();
private:
Ui::QtForOpencvClass ui;
Config* config;
Work* work;
QMenu * allMusicMenu;
QMenu * selMusicMenu;
QAction * updateMusicAction;
QAction * addMusicAction;
QAction * delMusicAction;
//更新音乐列表
void updateMusicList(QMap<QString,QString> data, Work::MusicListTypeEnum type);
//注册UI信号与槽
void ConnectUiEvent();
private slots:
void OnBtnUpdateMusic();
void OnBtnAddSelMusic();
void OnBtnDelMusic();
void OnLineEditSearchMusic(const QString data);
void OnTabChange(int index);
void OnListwAllMusic(QListWidgetItem * item1);
void OnAllMusicListClick(QListWidgetItem*);
void OnAllMusicListPlay(QListWidgetItem*);
void OnSelMusicListClick(QListWidgetItem*);
void OnSelMusicListPlay(QListWidgetItem*);
void OnUpdateMenuAction(const QPoint &pos);
public:
QLabel* imgLabel;
};QtForOpencv.cpp
#include "QtForOpencv.h"
#include <opencv2\opencv.hpp>
#include <opencv2\core.hpp>
#include <opencv2\imgcodecs.hpp>
#include <opencv2\imgproc.hpp>
#include <QPushButton>
#include <qdebug.h>
#include <QProcess>
#include <QMessageBox>
#include "Cchineseletterhelper.h"
using namespace cv;
using namespace std;
QtForOpencv::QtForOpencv(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
ConnectUiEvent();
allMusicMenu = new QMenu(this);
selMusicMenu = new QMenu(this);
updateMusicAction = new QAction("刷新歌曲",this);
addMusicAction = new QAction("添加到选中的歌曲", this);
delMusicAction = new QAction("删除歌曲", this);
allMusicMenu->addAction(updateMusicAction);
allMusicMenu->addAction(addMusicAction);
selMusicMenu->addAction(delMusicAction);
connect(updateMusicAction, &QAction::triggered, [=]{
qDebug() << "updateMusicAction";
OnBtnUpdateMusic();
});
connect(addMusicAction, &QAction::triggered, [=]{
qDebug() << "addMusicAction";
OnBtnAddSelMusic();
});
connect(delMusicAction, &QAction::triggered, [=]{
qDebug() << "delMusicAction";
OnBtnDelMusic();
});
// 给控件设置上下文菜单策略
ui.listwAllMusic->setContextMenuPolicy(Qt::CustomContextMenu);
//鼠标右键点击控件时会发送一个void QWidget::customContextMenuRequested(const QPoint &pos)信号
//给信号设置相应的槽函数
//以slot函数方式connect
connect(ui.listwAllMusic, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(OnUpdateMenuAction(const QPoint&)));
//以Lambada表达式方法connect
ui.listwSelMusic->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui.listwSelMusic, &QListWidget::customContextMenuRequested, [=](const QPoint &pos)
{
qDebug() << pos;
selMusicMenu->exec(QCursor::pos());
});
//初始化配置
QString inifilePath = QCoreApplication::applicationDirPath() + "/myTest.ini";
config = new Config(inifilePath);
config->set("mtvconfig", "allMusic", QCoreApplication::applicationDirPath()+"/allmusic.txt");
config->set("mtvconfig", "selMusic", QCoreApplication::applicationDirPath() + "/selmusic.txt");
config->set("mtvconfig", "mtvDir", "e:/music");
config->set("mtvconfig", "playerDir", "E:/Program Files/SPlayer/splayer.exe");
//初始化工作逻辑
work = new Work(config);
work->readMusicList(Work::MusicListTypeEnum::allMusic);
work->readMusicList(Work::MusicListTypeEnum::selMusic);
ui.tabmain->setCurrentIndex(0);
updateMusicList(work->getMusicList(Work::MusicListTypeEnum::allMusic), Work::MusicListTypeEnum::allMusic);
auto s = sizeof(double);
}
void QtForOpencv::OnUpdateMenuAction(const QPoint &pos){
qDebug() << pos;//参数pos用来传递右键点击时的鼠标的坐标,这个坐标一般是相对于控件左上角而言的
allMusicMenu->exec(QCursor::pos());
}
void QtForOpencv::ConnectUiEvent()
{
//更新歌单
connect(ui.btnUpdateMusic, SIGNAL(clicked()), this, SLOT(OnBtnUpdateMusic()));
//添加到选中的歌单
connect(ui.btnAddSelMusic, SIGNAL(clicked()), this, SLOT(OnBtnAddSelMusic()));
//删除歌曲(仅选中的歌单)
connect(ui.btnDelMusic, &QPushButton::clicked, this, &QtForOpencv::OnBtnDelMusic);
//歌曲搜索框内容改变
connect(ui.lineEditSearchMusic, SIGNAL(textChanged(const QString)), this, SLOT(OnLineEditSearchMusic(const QString)));
//tab标签被点中
connect(ui.tabmain, SIGNAL(tabBarClicked(int)), this, SLOT(OnTabChange(int)));
//全部歌曲列表项目被单击或双击运行
connect(ui.listwAllMusic, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(OnAllMusicListPlay(QListWidgetItem*)));
connect(ui.listwAllMusic, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(OnAllMusicListClick(QListWidgetItem*)));
//选中的歌曲列表项目被单击或双击运行
connect(ui.listwSelMusic, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(OnSelMusicListPlay(QListWidgetItem*)));
connect(ui.listwSelMusic, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(OnSelMusicListClick(QListWidgetItem*)));
}
void QtForOpencv::OnBtnUpdateMusic()
{
//刷新歌曲
qDebug() << "OnBtnUpdateMusic";
work->delMusicListFile(Work::MusicListTypeEnum::allMusic);
updateMusicList(work->getMusicList(Work::MusicListTypeEnum::allMusic), Work::MusicListTypeEnum::allMusic);
}
void QtForOpencv::OnBtnAddSelMusic()
{
//添加到选中的歌曲
qDebug() << "OnBtnAddSelMusic";
std::tuple<QString,QString> data;
QString musicName = ui.listwAllMusic->selectedItems()[0]->text();
QString musicPath = config->get("mtvconfig", "mtvDir").toString() + "/" + musicName;
data = make_tuple(musicName, musicPath);
work->addMusic(data);
updateMusicList(work->getMusicList(Work::MusicListTypeEnum::selMusic), Work::MusicListTypeEnum::selMusic);
QMessageBox::about(NULL, "信息", "添加成功!");
}
void QtForOpencv::OnBtnDelMusic()
{
qDebug() << "OnBtnDelMusic";
//删除选中歌曲列表中的歌曲
if (ui.tabmain->currentIndex() > 0){
QString musicName = ui.listwSelMusic->selectedItems()[0]->text();
work->delMusic(musicName);
updateMusicList(work->getMusicList(Work::MusicListTypeEnum::selMusic), Work::MusicListTypeEnum::selMusic);
QMessageBox::about(NULL, "信息", "已经删除!");
}
}
void QtForOpencv::OnTabChange(int index)
{
qDebug() << "tab..." << index ;
switch (index)
{
case 0:
ui.btnAddSelMusic->setEnabled(true);
ui.btnUpdateMusic->setEnabled(true);
ui.btnDelMusic->setEnabled(false);
updateMusicList(work->getMusicList(Work::MusicListTypeEnum::allMusic), Work::MusicListTypeEnum::allMusic);
break;
case 1:
ui.btnAddSelMusic->setEnabled(false);
ui.btnUpdateMusic->setEnabled(true);
ui.btnDelMusic->setEnabled(true);
updateMusicList(work->getMusicList(Work::MusicListTypeEnum::selMusic), Work::MusicListTypeEnum::selMusic);
break;
default:
break;
}
}
void QtForOpencv::OnLineEditSearchMusic(const QString data)
{
qDebug() << data;
if (data.length() > 0){
//QString firstLetters = Cchineseletterhelper::GetFirstLetters("");
QMap<QString, QString> map1;
if (ui.tabmain->currentIndex() == 0){
auto list = work->getMusicAllPyList();
auto path = config->get("mtvconfig", "mtvDir").toString();
for (QMap<QString, QString>::iterator i = list.begin(); i != list.end(); ++i){
if (i.value().indexOf(data.toUpper()) >= 0){
map1.insert(i.key(), path + "/" + i.key());
}
}
if (map1.count() > 0){
updateMusicList(map1, Work::MusicListTypeEnum::searchMusicAll);
}
}
else if (ui.tabmain->currentIndex() == 1){
auto list = work->getMusicSelPyList();
auto path = config->get("mtvconfig", "mtvDir").toString();
for (QMap<QString, QString>::iterator i = list.begin(); i != list.end(); ++i){
if (i.value().indexOf(data.toUpper()) >= 0){
map1.insert(i.key(), path + "/" + i.key());
}
}
if (map1.count() > 0){
updateMusicList(map1, Work::MusicListTypeEnum::searchMusicSel);
}
}
}
else{
if (ui.tabmain->currentIndex() == 0){
updateMusicList(work->getMusicList(Work::MusicListTypeEnum::allMusic), Work::MusicListTypeEnum::allMusic);
}
else if (ui.tabmain->currentIndex() == 1){
updateMusicList(work->getMusicList(Work::MusicListTypeEnum::selMusic), Work::MusicListTypeEnum::selMusic);
}
}
}
void QtForOpencv::OnListwAllMusic(QListWidgetItem * item1)
{
qDebug() << "OnListwAllMusic";
}
void QtForOpencv::updateMusicList(QMap<QString, QString> data, Work::MusicListTypeEnum type)
{
QStringList list1;
foreach(QString m, data.keys()){
list1.append(m);
}
if (data.count() < 1) return;
if (type == Work::MusicListTypeEnum::allMusic || type == Work::MusicListTypeEnum::searchMusicAll){
ui.listwAllMusic->clear();
ui.listwAllMusic->addItems(list1);
}
else if (type == Work::MusicListTypeEnum::selMusic || type == Work::MusicListTypeEnum::searchMusicSel){
ui.listwSelMusic->clear();
ui.listwSelMusic->addItems(list1);
}
}
void QtForOpencv::OnAllMusicListClick(QListWidgetItem* item)
{
//qDebug() << "OnAllMusicListClick";
}
void QtForOpencv::OnAllMusicListPlay(QListWidgetItem* item)
{
//播放歌曲
qDebug() << "OnAllMusicListPlay";
auto mtvdir = config->get("mtvconfig", "mtvDir").toString();
auto txt = item->text();
auto playDir = config->get("mtvconfig", "playerDir").toString();
auto pro = new QProcess(this);
pro->start(playDir, QStringList(mtvdir+"/"+txt));
pro->waitForStarted();
}
void QtForOpencv::OnSelMusicListClick(QListWidgetItem* item)
{
//qDebug() << "OnSelMusicListClick";
}
void QtForOpencv::OnSelMusicListPlay(QListWidgetItem* item)
{
qDebug() << "OnSelMusicListPlay";
OnAllMusicListPlay(item);
}
QtForOpencv::~QtForOpencv()
{
delete config;
delete work;
}Config.h
#pragma once
#pragma execution_character_set("utf-8")
#include <QVariant>
#include <QSettings>
class Config
{
public:
Config(QString filename="");
virtual ~Config();
//保存配置项(节点名,键名,键值)
void set(QString, QString, QVariant);
//读取配置项(节点名,键名)
QVariant get(QString, QString);
private:
QString m_qstrfilename;
QSettings * m_psetting;
};Config.app
#include "Config.h"
#include <QCoreApplication>
#include <QSettings>
#include <QDebug>
Config::Config(QString filename)
{
if (filename.isEmpty())
{
m_qstrfilename = QCoreApplication::applicationDirPath() + "/config.ini";
}
else
{
m_qstrfilename = filename;
}
m_psetting = new QSettings(m_qstrfilename, QSettings::Format::IniFormat);
qDebug() << m_qstrfilename;
}
Config::~Config()
{
delete m_psetting;
m_psetting = 0;
}
void Config::set(QString qstrNodeName, QString qstrKeyName, QVariant qvarValue)
{
/* /mtvconfig/allMusic */
m_psetting->setValue(QString("/%1/%2").arg(qstrNodeName).arg(qstrKeyName), qvarValue);
}
QVariant Config::get(QString qstrNodeName, QString qstrKeyName)
{
QVariant qvar = m_psetting->value(QString("/%1/%2").arg(qstrNodeName).arg(qstrKeyName));
return qvar;
}CdiskFile.h
#pragma once
#pragma execution_character_set("utf-8")
#include <QString>
#include <QMap>
#include "CcsvHelper.h"
#include "qdir.h"
class CdiskFile
{
public:
CdiskFile();
virtual ~CdiskFile();
//读取文本文件全部内容(文件名带路径)
static QString readAllTxt(const QString filepath);
//取指定目录下全部符合扩展名的文件名列表,递归搜索。(目录路径,扩展名列表)
static QStringList getFileNames(const QString filepath,QStringList filters);
//保存QMap<QString, QString>的数据到磁盘文件。(文件名路径,map数据)
static bool QMapSave(const QString fileNamePath, QMap<QString, QString> mapdata);
//从磁盘文件读取内容到QMap<QString, QString>。(文件名路径,文件名)
static QMap<QString, QString> QMapLoad(const QString fileNamePath);
//由文件名路径串拆分出文件名与目录串
static std::tuple<QString, QString> splitFileNamePath(const QString filenamepath);
};CdiskFile.cpp
#include "CdiskFile.h"
#include <QFile>
#include <QDir>
#include "qtextstream.h"
CdiskFile::CdiskFile()
{
}
CdiskFile::~CdiskFile()
{
}
QString CdiskFile::readAllTxt(const QString filepath)
{
try
{
QFile file(filepath);
file.open(QIODevice::ReadOnly | QIODevice::Text);
QByteArray ary = file.readAll();
file.close();
return QString(ary);
}
catch (...)
{
throw;
}
}
QStringList CdiskFile::getFileNames(const QString filepath, QStringList filters)
{
QStringList list1;
QDir dir(filepath);
list1 = dir.entryList(filters, QDir::Files | QDir::AllEntries, QDir::Name);
return list1;
}
bool CdiskFile::QMapSave(const QString fileNamePath, QMap<QString, QString> mapdata)
{
auto tup=CdiskFile::splitFileNamePath(fileNamePath);
QString filepath = std::get<0>(tup);
QString filename = std::get<1>(tup);
QDir dir;
if (!dir.exists(filepath)){
if (!dir.mkpath(filepath)) return false;
}
QString tmp;
QList<QString> list2;
foreach(auto m,mapdata.keys()){
list2.clear();
list2.append(m);
list2.append(mapdata[m]);
tmp.append(CcsvHelper::createCSVRow(list2)+"\n");
}
try{
QFile file(filepath + "/" + filename);
file.open(QIODevice::WriteOnly | QIODevice::Text);
file.write(tmp.toUtf8());
file.close();
return true;
}
catch (...){
return false;
}
}
QMap<QString, QString> CdiskFile::QMapLoad(const QString fileNamePath)
{
QMap<QString, QString> map;
auto tup = CdiskFile::splitFileNamePath(fileNamePath);
QString filepath = std::get<0>(tup);
QString filename = std::get<1>(tup);
QFile file(filepath + "/" + filename);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)){
QTextStream in(&file);
in.setCodec("UTF-8");
auto res = in.readAll();
auto list1 = res.split("\n");
foreach(QString m, list1){
auto ary1 = CcsvHelper::explainCSVRow(m);
if (ary1.length() == 2)
map.insert(ary1[0], ary1[1]);
else
{
int k1 = 0;
}
}
}
return map;
}
std::tuple<QString, QString> CdiskFile::splitFileNamePath(const QString filenamepath)
{
std::tuple<QString, QString> tup;
int pos1=filenamepath.lastIndexOf('/');
QString strPath = filenamepath.left(pos1 + 1);
QString strFileName = filenamepath.right(filenamepath.count() - pos1 - 1);
tup = std::make_tuple(strPath, strFileName);
return tup;
}
这款点歌的小程序勇哥还写过其它语言版本的,放这里给各位把玩一下吧:
Python版本的:
《Python Tkinter 学习成果:点歌软件music》
plc+单片机+c#版本:
《欧姆龙cp1h常用指令学习(十四)练习篇一:红外线遥控点歌》
C#版本的:
再早之前,还有一个易语言版本写的,怕被各位鄙视,还是不要放上来了吧。
这个小程序配置采用ini文件,其实相对于xml和C#的对象序列化来生成配置文件来看,是相当的原始。
而且现在也流行用PropertyGrid之类的属性流览控件来设置配置项,我想把这个程序也改造成这样的。

(属性浏览控件指的就是Qt Designer或者vs里面用到的控件属性设置面板)
等勇哥搞出来了会另起一个贴子登出来。
---------------------
作者:hackpig
来源:www.skcircle.com
版权声明:本文为博主原创文章,转载请附上博文链接!


少有人走的路



















