最近在空闲时想做一些东西,之前一直想做一个自己的日志记录工具, 然后需要增加文字、图片,可以播放本地音乐等一些功能。
目前实现了文字、图片的编辑和储存(数据储存,之后的打开可以不依赖本地图片数据);
现在将一些代码和思路想法记录下,分享给需要的小伙伴们
1、目前的主要界面如下
上面是菜单栏,工具栏暂时还没有添加。
下面是由一个QTableView和QTextEdit组成的主体部分。之后应该会添加一个音乐播放器的控件在下面
插入一段布局的代码
setStretch是用来控制控件在布局内的拉伸大小的
另外这里不能直接对MainWindow调用this.setLayout()


hboxLayout->setStretch(0, 1);
hboxLayout->setStretch(2, 2.5);
//this->setLayout(hboxLayout);不能直接set,显示不出来并且提示错误
//Attempting to set QLayout "" on MainWindow "", which already has a layout
QWidget *newWidget = new QWidget(this);
newWidget->setLayout(hboxLayout);
this->setCentralWidget(newWidget);
二、文字和图片数据的保存方式
最开始做的时候,以为将文字和图片从QTextEdit通过toPlainText()接口读出来然后保存到一个文件,显示的时候直接readAll()然后丢给控件显示就完了。
后来发现还是太天真。文字数据和图片数据是两种数据,并且QTextEdit是按照富文本格式去处理数据的,通过QTextDocument 的toHtml获取到控件的数据,发现是按照HTML格式编辑的,其中图片已这样的格式保存
<img width="400" height="274" src="C:/Users/Default/AppData/myAppImg/11/IMG_000000011.jpg"/>
也就是显示的时候通过src路径去加载,如果删除本地的图片就显示不了。
所以定下的方案是,将输出数据和其中加载的图片数据组合,保存到自定义数据中,在打开的时候根据规则进行解析,还原成HTML格式文字数据和对应的图片数据。
首先对输出的控件数据中图片路径修改为指向定义的“处理空间”路径,这个处理空间,就是在打开文件时生成的图片备份文件和文字备份文件,这样在控件加载文字数据时,会加载备份的图片,就可以显示了。
对文字数据和图片数据,进行了格式编码。以下是自己定的格式说明 ^-^
数据项 | 格式定义 | 长度 | TAG值 | 数据格式 | 说明 |
文件头 | 文件头 | 4 | 无 | B | 标识一个自定义的文件类型。 clog |
文件长度 | 4 | 无 | B | 整个数据条目的长度 0x00 0x00 0x00 0x24 | |
文字编码 | 文件保存路径 | 1 | 0xC1 | B | 标识文件数据解压后的文件路径 |
文件数据 | 1 | 0XC2 | B | 文件的数据流数据 | |
数据编码 | 图片保存路径 | 1 | 0xD1 | B | 图片格式 jpg、png、bmp |
图片数据 | 1 | 0xD2 | B | Base64格式图片数据 | |
备用 | 1 | 0xE1 | B |
其所有数据都按照TLV(标签-长度-值)据组合,其中长度域有单独设置。
(1)对原文件数据的修改,主要是图片的路径修改


void MainWindow::saveFile(const QString &ht, int row)
{
QTextDocument *textDocument = m_textEdit->document();
QDomDocument dcreateDoc;
if(!ht.isEmpty()){
if(!dcreateDoc.setContent(ht)){
qDebug() << "str html set Content error";
}
}else{
if(!dcreateDoc.setContent(textDocument->toHtml())){
qDebug() << "document html set Content error";
}
}
//这个地方有想过直接修改获取QString字符串,但是我修改之后尝试获取新的字符串是没有被改过的,所以这里用了一个文件来保存在读取
//最外层节点
QDomElement roots = dcreateDoc.documentElement();
QDomNodeList list = roots.childNodes();
//analyImg会更改图片的路径指向备份路径
QByteArray imgBuff = analyImg(list);
QString path = qApp->applicationDirPath() + "/copy.htm";
QFile file(path);
if(!file.open(QIODevice::ReadWrite)){
qDebug() << "open copy file error";
}
QTextStream out(&file);
dcreateDoc.save(out,4);
file.close();//调用file.flush()不能刷新
file.open(QIODevice::ReadOnly);
QByteArray html = file.readAll();
file.close();
file.remove();
//组文件和图片数据
QString fil;
if(row != tag){
fil = m_fileHand->getFileName(row);
}else{
fil = m_fileHand->getFileName(tag);
}
fil = fil.mid(0, fil.indexOf("."));
QString newFileName = BACKSAVE_PATH + tr("/%1/%2").arg(fil).arg(m_fileHand->getFileName(tag));
QByteArray all,all1;
//增加文件标识域 "clog"
all.append(tr("clog").toLatin1());
//储存数据域
all1.append(tlvByte(0xC1, newFileName.toLatin1()));
all1.append(tlvByte(0xC2, html));
all1.append(imgBuff);
//增加数据长度域
QString len = QString("%1").arg(all1.length(), 8, 16, QChar('0'));
all.append(QByteArray::fromHex(len.toLatin1()));
all.append(all1);
//串->clog+数据长度域+数据域
if(!m_fileHand->save(all, row)){
QMessageBox::warning(NULL, "warning",
"save file falt.", QMessageBox::Ok);
}
}
QByteArray MainWindow::analyImg(QDomNodeList &list)
{
//要保证传入的list不为空
if(list.isEmpty()){
return 0;
}
QString imgpath;
QByteArray img;//储存所有的图片的信息
for(int i = 0;i < list.length(); i++){
if(list.at(i).toElement().tagName() == "img"){
//获取src指向的图片路径
imgpath = list.at(i).toElement().attributeNode("src").value();
QFileInfo file(imgpath);
QString path = m_fileHand->getFileName(tag);
path = path.mid(0, path.indexOf("."));
QString newFile = BACKSAVE_PATH +tr("/%1/%2").arg(path).arg(file.fileName());
//替换图片路径
list.at(i).toElement().setAttribute("src",newFile);
//qDebug() << "替换之后路径:" << list.at(i).toElement().attributeNode("src").value();
//tlvByte是用来组tlv格式的数据处理函数
img.append(tlvByte(0xD1,newFile.toLatin1()));
//用readAll读到的图片数据
QByteArray imgByte = imgDataByte(imgpath);
img.append(tlvByte(0xD2,imgByte));
}
QDomNodeList it = list.at(i).childNodes();
if(it.length() >= 1){
//回调,一层一层处理完所有图片数据
img += analyImg(it);
}else{
continue;
}
}
return img;
}
(2)最后获得的数据串,需要经过压缩在进行写入,本次用到的是Qt自带的Zlib库
(3)通过readAll读出文件数据,需要解出各标签数据在写入各个文件。


QByteArray CFileHandle::analyByte(const QByteArray &byte)
{
if(byte.isEmpty()){
return "";
}
QByteArray analy;
QString str;
analy = byte.mid(0,4);
if(!("clog" == str.prepend(analy))){
qDebug() << "it's not app Standard file";
return "";
}
analy = byte.mid(4,4);
int byteLen = analy.toHex().toInt(NULL, 16);
analy = byte.mid(8);//取所有文件数据
//长度不一样也进行解析
if(analy.length() != byteLen)
qDebug() << "file byte len error";
QString filepath = analyTLV(analy);//返回文本文件路径
QFile file(filepath);
if(file.exists()){
if(file.open(QIODevice::ReadOnly)){
analy = file.readAll();
}
file.close();
}
return analy;
}
//解出文件路径返回,并将所有标签数据解出写入对应文件中
QString CFileHandle::analyTLV(const QByteArray &tlvByte)
{
QString file;
QString str;
QList<QString> listStr;
QList<QByteArray> listByte;
QByteArray byteT;
QByteArray byteL;
QByteArray byteV;
QByteArray Llen;
int len = 0;
int length = 0;
for(int i = 0; i < tlvByte.length(); ){
//T
byteT = tlvByte.mid(i,1);
i += 1;
byteL = tlvByte.mid(i, 1);
//如果L的最高位为1,则剩下的位值表示长度
if((byteL.at(0) & 0x80) == 0x80){
len = byteL.at(0) & 0x7f;
i += 1;
Llen = tlvByte.mid(i, len);
//数据长度
length = Llen.toHex().toInt(NULL, 16);
i += len;
}else{
length = byteL.at(0) & 0xff;
i += 1;
}
//V 数据
byteV = tlvByte.mid(i, length);//数据
str = "";
i += length;//循环i不用自增
//每一个C1 D1后都跟着C2 D2,确保数据连贯,则两个list可以对应下标
if((byteT.at(0) & 0xff) == 0xC1){
file = file.prepend(byteV);
listStr.append(str.prepend(byteV));
}else if((byteT.at(0) & 0xff) == 0xD1){
listStr.append(str.prepend(byteV));
}else if((byteT.at(0) & 0xff) == 0xC2
|| (byteT.at(0) & 0xff) == 0xD2){
listByte.append(byteV);
}else
qDebug() << "analy tlv error, byteT is:" << byteT.toHex()
<< " byteV is:" << byteV;
}
//将所有文件创建
QString filepath;
for(int j = 0; j < listStr.length(); j++){
filepath = listStr.at(j);
//创建文件名文件夹,防止同名文件导致的加载错误
QString fp = filepath.mid(0, filepath.lastIndexOf("/"));
QFileInfo fl(fp);
if(!fl.exists()){
QDir dir;
dir.mkpath(fp);
}
QFile file(filepath);
if(!file.exists()){//文件存在不创建
//qDebug() << tr("j is %1").arg(j) << " filepath:" << filepath;
if(file.open(QIODevice::WriteOnly)){
file.write(listByte.at(j));
file.close();
}else{
qDebug() << "create file error, file path:" <<
filepath;
}
}
}
if(file.isEmpty()){
qDebug() << "dont't have file path,please check!";
return "";
}
return file;
}
今天写到这里,之后在继续补充。
放假回来了,继续折腾这个玩意儿。
测试发现,在保存时使用的dcreateDoc.save(out, 4);接口,会格式化保存的htm文件,按照子节点的层级对每层子节点增加4个空格在开始;如果改为0则不会增加。
但是改为0的时候,在显示图片的时候,会将"text-indent:0px;"> <img src="C:" 这样的字符串转为"text-indent:0px;"><img src="C:" ,则当单独对一张图片进行位置移动,保存会格式化,图片一直顶格。
所以后面排除了通过QDomDocument来处理字符串替换路径的操作。直接对QTextEdit获取的串替换img的路径然后对这个串保存。更加简洁。
新代码如下:


void MainWindow::saveFile(int row, const QString &ht)
{
QTextDocument *textDocument = m_textEdit->document();
//qDebug() << "save str :" << textDocument->toHtml();
QString htmlStr;
if(ht.isEmpty()){
htmlStr = textDocument->toHtml();
}else{
htmlStr = ht;
}
QByteArray imgBuff = analyXml(htmlStr, row);
QByteArray html = htmlStr.toUtf8();
//组文件和图片数据
QString fil;
fil = m_fileHand->getFileName(row);
fil = fil.mid(0, fil.indexOf("."));
QString newFileName = BACKSAVE_PATH + tr("/%1/%2").arg(fil).arg(fil) + ".html";
QByteArray all,all1;
//增加文件标识域 "clog"
all.append(tr("clog").toLatin1());
//储存数据域
all1.append(tlvByte(0xC1, newFileName.toLatin1()));
all1.append(tlvByte(0xC2, html));
all1.append(imgBuff);
//增加数据长度域
QString len = QString("%1").arg(all1.length(), 8, 16, QChar('0'));
all.append(QByteArray::fromHex(len.toLatin1()));
all.append(all1);
//串->clog+数据长度域+数据域
if(!m_fileHand->save(all, row)){
QMessageBox::warning(NULL, "warning",
"save file falt.", QMessageBox::Ok);
}
}
QByteArray MainWindow::analyXml(QString &xml, int &row)
{
if(xml.isEmpty())
return "";
QList<QString> xmlList = xml.split('\n');
QString imgTag;
QString leftTag;
QString rightTag;
QString imgpath;
QString str;
QByteArray img;//储存所有的图片的信息
xml.clear();
for(int i = 0; i < xmlList.length(); i++){
int tag = xmlList.at(i).indexOf("src");
if(tag > 0){//如果有图片存在
leftTag = xmlList.at(i).mid(0,tag + 5);//第一段
imgTag = xmlList.at(i).mid(tag + 5);//第二段
tag = imgTag.indexOf('"');
imgpath = imgTag.mid(0, tag);
rightTag = imgTag.mid(tag);
//qDebug() << "path:" << imgpath;
QFileInfo file(imgpath);
QString path = m_fileHand->getFileName(row);
path = path.mid(0, path.indexOf("."));
QString newFile = BACKSAVE_PATH +tr("/%1/%2").arg(path).arg(file.fileName());
str = leftTag + newFile + rightTag;
xml += str;
//qDebug() << "str string is:" << str;
img.append(tlvByte(0xD1,newFile.toLatin1()));
QByteArray imgByte = imgDataByte(imgpath);
img.append(tlvByte(0xD2,imgByte));
}else{
xml += xmlList.at(i);
}
}
//qDebug() << "save xml is:" << xml;
return img;
}