QT学习笔记(17)——重写QSlider控制图片轮换

目录

  • 前言
    • 绘图小知识点
  • 知识点
  • 程序详解
    • 效果图
    • 按钮的添加-——实现添加图片并写入xml文件
    • 将图片信息写入xml文件
    • 在View上使用delete键执行删除的功能
    • 在xml文件上执行删除的功能
    • 绘制QSlider,并在上面绘制对应的文本
    • 每次程序启动都读取xml文件上的信息进行显示
  • 代码地址

前言

今天要完成的功能是在一个小demo的基础上,进行功能的添加,主要完成6大功能:

  1. 在原有项目的基础上,添加一个按钮,可以选择系统上某个图片进行加载显示。
  2. 同时,选择完之后,将该图片的信息写入xml文件。
  3. 并且,完成在图片上可以完成添加删除的功能。
  4. 在添加删除的同时,将该信息在xml文件上进行对应的修改。
  5. 最后再完成一个需求,在上一页 下一页的中间弄一个QSlite,当鼠标移入这个QSlite的时候,不显示(2/13)这种数字,当鼠标移开的时候,显示该数字。继承QSlite的函数,在QPainter上进行重新绘制。
  6. 这些都弄完后,要求在每次程序重新启动的时候,读取xml文件,将xml文件上面的图片都显示出来。而不要每次都自己选择。

关于这个小demo,我也将主要讲解这5大部分,其他部分将略过。

绘图小知识点

知识点

  1. 首先,注意有三种绘图设备:
  1. QPixmap:针对屏幕进行优化了,和平台相关,不能对图片进行修改。
  2. QImage:和平台无关,可以对图片进行修改,在线程中绘图。
  3. QPicture:保存绘图的状态(二进制文件)
  1. 接下来,讲解一下paintEvent函数:
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:黑白,光标,省存储
}
  1. 绘图设备的转换:

QPixmap->QImage
QPIxmap a;
a.toImage();

QPixmap->QImage
QImage b;;
QPixmap::fromImage(b)

  1. 绘图基本流程:画家->制定绘图设备->begin->绘图动作->end

程序详解

效果图

按钮的添加-——实现添加图片并写入xml文件

在该槽函数进行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);
}

将图片信息写入xml文件

根据文件路径打开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"<
}

在View上使用delete键执行删除的功能

首先,先在xml文件上删除当前选中的,接下来,在数据结构中pictureList也进行该index的删除,再让整个View重新读取该修改过的pictureList.

void CDeepPictureViewer::deepDelete()
{
    deleteXmlMes(pictureList.at(m_currentIndex));//先删除xml文件上当前选中的图片信息
    pictureList.removeAt(m_currentIndex);//在pictureList上移除该图片信息
    addImageList(pictureList);//在把经过更改的添加入addImageList之中
    update();
}

在xml文件上执行删除的功能

这个与添加的流程是差不多的,不过,这里对于指定的条目的删除采用的是,通过标签名进行定位定位,然后删除特定的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,同时,应注意将鼠标移动的数值通过信号的方式发送到主类进行获取,才可实现通过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文件的读取,也是由于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
由于时间问题,如有错误,欢迎指正 ~

你可能感兴趣的:(QT,qt)