今天要完成的功能是在一个小demo的基础上,进行功能的添加,主要完成6大功能:
- 在原有项目的基础上,添加一个按钮,可以选择系统上某个图片进行加载显示。
- 同时,选择完之后,将该图片的信息写入xml文件。
- 并且,完成在图片上可以完成添加删除的功能。
- 在添加删除的同时,将该信息在xml文件上进行对应的修改。
- 最后再完成一个需求,在上一页 下一页的中间弄一个QSlite,当鼠标移入这个QSlite的时候,不显示(2/13)这种数字,当鼠标移开的时候,显示该数字。继承QSlite的函数,在QPainter上进行重新绘制。
- 这些都弄完后,要求在每次程序重新启动的时候,读取xml文件,将xml文件上面的图片都显示出来。而不要每次都自己选择。
关于这个小demo,我也将主要讲解这5大部分,其他部分将略过。
- QPixmap:针对屏幕进行优化了,和平台相关,不能对图片进行修改。
- QImage:和平台无关,可以对图片进行修改,在线程中绘图。
- QPicture:保存绘图的状态(二进制文件)
void paintEvent(QPaintEvent *event)
{
QPainter p(this);//创建画家,在窗口上绘图
p.drawXXX();
p.drawPixmap(0,0,width(),height(),QPixmal());
p.drawImage();//QImage
p.drawPicture();//QPicture;
p.drawLine();
p.drawPixmap();//QBitmap:黑白,光标,省存储
}
QPixmap->QImage
QPIxmap a;
a.toImage();
QPixmap->QImage
QImage b;;
QPixmap::fromImage(b)
在该槽函数进行dialog的打开以及修改数据结构pictureList,该数据结构作为所有图片路径的一个存储路径,然后,同时调用修改xml文件的函数。
//在点击增加图片按钮所执行的槽函数
void CDeepPictureViewer::slotAddPicture()
{
QString picturePath = QFileDialog::getOpenFileName(this, "open", "../");//这里得到的已经是正确的路径了
//qDebug()<<"CDeepPictureViewer::slotAddPicture path"<
QStringList fileList;
if(false == picturePath.isEmpty())
{
QFileInfo info(picturePath);
fileList.append(info.filePath());//
//pictureList.append(info.filePath());//这里pictureList也要进行更新
}
else
{
qDebug() << "CDeepPictureViewer::slotAddPicture Chose file path is wrong! 118";
}
pictureList.append(fileList);
addImageList(pictureList);//添加入显示的名单之中
addXmlMes(picturePath);
}
根据文件路径打开file,然后,将文件的字节流读入并关联doc。然后,创建一个临时的根节点,将参数写入该临时根节点,并且衔接在root的后面。最后,将其写入xml文件。
//将添加的信息加入xml文件之中
void CDeepPictureViewer::addXmlMes(QString picturePath)
{
QString filePath = qApp->applicationDirPath()+"/xml/test.xml";
QDomDocument doc;
QFile file(filePath);
if(!file.open(QIODevice::ReadOnly))
{
qDebug()<<"CDeepPictureViewer::addXmlMes file open fail";
//return;
}
else{
qDebug()<<"CDeepPictureViewer::addXmlMes open success";
}
QByteArray array = file.readAll();//字节流读入
if(!doc.setContent(array))
{
qDebug()<<"CDeepPictureViewer::addXmlMes fail setContent";
//return;
}
file.close();
QDomElement root = doc.documentElement();
QDomElement childElt = doc.createElement("tmp");//创建一个临时根节点
childElt.setTagName("Picture");
childElt.setAttribute("Path",picturePath);
root.appendChild(childElt);//连接在这个root的后面
bool bSuccess = file.open(QIODevice::WriteOnly);//这里文件要再次打开的话,一定要保证,前面这个文件已经执行关闭操作了
if(bSuccess == false)
{
qDebug()<<"CDeepPictureViewer::addXmlMes file could write";
}
else{
qDebug()<<"CDeepPictureViewer::addXmlMes file open fail";
}
QTextStream out(&file);
doc.save(out, 4);
file.close();
//qDebug()<<"CDeepPictureViewer::addXmlMes"<
}
首先,先在xml文件上删除当前选中的,接下来,在数据结构中pictureList也进行该index的删除,再让整个View重新读取该修改过的pictureList.
void CDeepPictureViewer::deepDelete()
{
deleteXmlMes(pictureList.at(m_currentIndex));//先删除xml文件上当前选中的图片信息
pictureList.removeAt(m_currentIndex);//在pictureList上移除该图片信息
addImageList(pictureList);//在把经过更改的添加入addImageList之中
update();
}
这个与添加的流程是差不多的,不过,这里对于指定的条目的删除采用的是,通过标签名进行定位定位,然后删除特定的index上面的xml文件信息,不过,这里采用这种方式主要还是因为这里的xml文件格式较为简单,不用做过多的搜索,也许在其他xml文件,你就得使用较为复杂的方式进行搜索。
//删除对应图片路径的xml文件信息
void CDeepPictureViewer::keyPressEvent(QKeyEvent * event)
{
if(event->key()==Qt::Key_Delete)//记得PictureList也要进行删除
{
event->accept();
qDebug()<<"CDeepPictureViewer::keyPressEvent";
deepDelete();//若按下按钮delete,则执行删除操作
//emit signalDeepDelete();
}
QWidget::keyPressEvent(event);
}
void CDeepPictureViewer::deleteXmlMes(QString picturePath)
{
QString filePath = qApp->applicationDirPath()+"/xml/test.xml";
QDomDocument doc;
QFile file(filePath);
if(!file.open(QIODevice::ReadOnly))
{
qDebug()<<"CDeepPictureViewer::addXmlMes file open fail";
}
else{
qDebug()<<"CDeepPictureViewer::addXmlMes open success";
}
QByteArray array = file.readAll();//字节流读入
if(!doc.setContent(array))
{
qDebug()<<"CDeepPictureViewer::addXmlMes fail setContent";
//return;
}
file.close();
QDomElement root = doc.documentElement();
QDomNodeList picture=doc.elementsByTagName("Picture"); //由标签名定位
root.removeChild(picture.at(m_currentIndex));//移除该index的child位置
if(!file.open(QFile::WriteOnly|QFile::Truncate))
{
qDebug()<<"CDeepPictureViewer::deleteXmlMes open fail";
return;
}
else
{
qDebug()<<"CDeepPictureViewer::deleteXmlMes open success";
}
QTextStream out_stream(&file);
doc.save(out_stream,4); //缩进4格
file.close();
}
这里,将继承QSlider重写的整个类分开讲解,会比较容易了解。
首先,重写一些鼠标的事件类,因为毕竟我们是要使用鼠标来操作这个QSlider,同时,应注意将鼠标移动的数值通过信号的方式发送到主类进行获取,才可实现通过QSlider控制图片的轮换。并且,还有重写一下鼠标移入移出的函数,为后面的文本在鼠标移入时消失,在移出时出现做好铺垫。
void CImageSlider::mousePressEvent(QMouseEvent *event){
// this.x:控件原点到界面边缘的x轴距离;
// globalPos.x:鼠标点击位置到屏幕边缘的x轴距离;
// pos.x:鼠标点击位置到本控件边缘的距离;
// this.width:本控件的宽度;
//注意应先调用父类的鼠标点击处理事件,这样可以不影响拖动的情况
QSlider::mousePressEvent(event);
m_isMoving = false;
m_mousePress = true;
//qDebug()<<"CImageSlider::mousePressEvent"<
//获取鼠标的位置,这里并不能直接从ev中取值(因为如果是拖动的话,鼠标开始点击的位置没有意义了)
double pos = event->pos().x() / (double)width();
double value = pos * (maximum() - minimum()) + minimum();
// qDebug()<<"CImageSlider::mouseMoveEvent"<
//value + 0.5 四舍五入
if(value>maximum()){
value=maximum();
}
if(value<minimum()){
value=minimum();
}
m_value=value+0.5;
setValue(m_value);
//emit sliderMoved( m_value );
//向父窗口发送自定义事件event type,这样就可以在父窗口中捕获这个事件进行处理
QEvent evEvent(static_cast<QEvent::Type>(QEvent::User + 1));
QCoreApplication::sendEvent(parentWidget(), &evEvent);
}
void CImageSlider::mouseMoveEvent(QMouseEvent *event){
QSlider::mouseMoveEvent(event);
double pos = event->pos().x() / (double)width();
double value = pos * (maximum() - minimum()) + minimum();
if(value>maximum()){
value=maximum();
}
if(value<minimum()){
value=minimum();
}
//value + 0.5 四舍五入
if(m_mousePress){
m_value=value + 0.5;
m_isMoving=true;
//emit sliderMoved(m_value);
}
setValue(value + 0.5);
//向父窗口发送自定义事件event type,这样就可以在父窗口中捕获这个事件进行处理
QEvent evEvent(static_cast<QEvent::Type>(QEvent::User + 1));
QCoreApplication::sendEvent(parentWidget(), &evEvent);
}
void CImageSlider::mouseReleaseEvent(QMouseEvent *event){
QSlider::mouseReleaseEvent(event);
//qDebug()<<"mouseReleaseEvent"<
m_mousePress = false;
m_isMoving=false;
emit sliderReleasedAt(m_value);//抛出有用信号
}
void CImageSlider::enterEvent(QEvent *event)
{
m_SliderisEnter = true;
QWidget::enterEvent(event);
update();
}
void CImageSlider::leaveEvent(QEvent *event)
{
m_SliderisEnter = false;
QWidget::leaveEvent(event);
//qDebug()<<"CImageSlider::leaveEvent";
update();
}
然后,获取当前View中的所有图片的数量以及当前所选中的图片的Index,并设置该QSlider的最大值。因为我们文本的书写格式为当前index/所有图片的数量
。
int CImageSlider::returnTotalImgNum(int pictureNum)
{
int sliderPictureNum = pictureNum;
allPictureNum = pictureNum;
qDebug()<<" CImageSlider::returnTotalImgNum"<<sliderPictureNum;
this->setMinimum(0);
this->setMaximum(sliderPictureNum-1);//设置slder的最大值
qDebug()<<"CImageSlider::returnTotalImgNum"<<sliderPictureNum;
return sliderPictureNum;
}
void CImageSlider::returnCurrentIndex(int index)
{
currentIndex = index;
this->setValue(index);
}
最后,就是最重要的一步,进行文本的绘制。这里要注意非常重要的一点,一定要加QSlider::paintEvent(event);
不然,你是无法显示该QSlider的,只能显示那个文本,你在什么控件上绘制,你这句话也要进行对应的修改,假如你是在主类QWidget上进行绘制,这句话就应该写成QWidget::paintEvent(event)
。我在这里卡了有一天,所以,也给你们提个醒。你如果对这个知识还不是很明白,可以看下这个博客QT关键问题解决之paintevent理解,其他东西的绘制应该就比较基本了,直接照猫画虎就可以了。
void CImageSlider::paintEvent(QPaintEvent *event)
{
QSlider::paintEvent(event);
if(m_SliderisEnter==false)
{
QImage image(1000,1000,QImage::Format_ARGB32);//定义图片,在图片上画
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.begin(&image);
painter.setPen(QColor(255, 255, 255));
QFont font = painter.font();
font.setPixelSize(20);//改变字体大小
font.setFamily("Microsoft YaHei");
painter.setFont(font);
int x = 35;
int y = 15;
painter.drawText(x,y, QStringLiteral("%1/%2").arg(currentIndex+1).arg(allPictureNum));
painter.end();
}
}
这里进行xml文件的读取,也是由于xml文件的格式较为简单,也就直接nextChild就可以读取全部了,并将读取的全部信息写入pictureList这个数据结构。然后,就有函数会将该数据结构进行处理,显示在View上面了。
//解析xml文件,将xml文件中的信息显示成图片(xml文件信息是图片)
QStringList CDeepPictureViewer::readFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
std::cerr << "Error:"
"Cannot read file " << qPrintable(fileName)
<< ": " << qPrintable(file.errorString())
<< std::endl;
//return ;
}
QString errorStr;
int errorLine;
int errorColumn;
QDomDocument doc;
if (!doc.setContent(&file, false, &errorStr, &errorLine, &errorColumn))
{
std::cerr << "Error: Parse error at line " << errorLine << ", "
<< "column " << errorColumn << ": "
<< qPrintable(errorStr) << std::endl;
//return false;
}
QDomElement root = doc.documentElement();
if (root.tagName() != "root")
{
std::cerr << "Error: Not a root file" << std::endl;
//return false;
}
else{
QFileInfo appInfo(root.attribute("Path"));
QString value = appInfo.baseName();//文件名
//item1 = new QStandardItem((value));
//this->appendRow(item1);
//qDebug()<<"root";
}
QDomNode child = root.firstChild();
while(!child.isNull())
{
QFileInfo appInfo(child.toElement().attribute("Path"));
QString filepath = appInfo.filePath();
pictureList.append(filepath);
//qDebug()<<"CDeepPictureViewer::parseAllMembers xml filepath"<
child = child.nextSibling();
}
return pictureList;
}
https://download.csdn.net/download/weixin_38809485/12624914里面的imageViewer
由于时间问题,如有错误,欢迎指正 ~