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

前言

今天要完成的功能是在一个小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"<<picturePath;
    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"<<picturePath;
}

在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"<<maximum()<<minimum();
    //获取鼠标的位置,这里并不能直接从ev中取值(因为如果是拖动的话,鼠标开始点击的位置没有意义了)
    double pos = event->pos().x() / (double)width();
    double value = pos * (maximum() - minimum()) + minimum();
    // qDebug()<<"CImageSlider::mouseMoveEvent"<<value;
    //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_value;
    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"<<filepath;
        child = child.nextSibling();
    }
    return pictureList;
}

关于删除功能的重写

之前这篇文章QT学习笔记(17)——重写QSlider控制图片轮换很早之前就完成了,但其实最后的删除部分是有做了比较大的更改的,但是没有记录下来,这里在这稍微记录一下。
那到底跟之前有什么不同呢?

以前的删除功能,就是在数据结构上面进行修改,然后,全部重新读入。可我们很容易想到其实这样的效率是很低的,万一有很多张图片,每次删除一张就得重新读取全部,就会导致很大程度的效率低下。所以,现在要求删除类似于把那个四个格子当做控件,把其中一个控件删除= 将该控件移除布局,并在控件放在末尾,然后,重新读入下一页的第一张图片。这样的话,就可以每次把对应的图片的控件删掉即可,qt会自动把控件往左边移。

删除部分的讲解

整个执行效果当然跟之前删除的效果是没什么差异的。
主要就是这两个函数的修改:

//执行删除的函数
void CDeepPictureViewer::deepDeleteLayout()
{
    //deleteXmlMes(pictureList.at(m_currentIndex));//先删除xml文件上当前选中的图片信息,调试程序时会先注释掉。
    if(m_totalImageList.size()<=m_col&&m_totalVector.size()>0)//如果当前图片的数量小于等于列数,就比如总共只有4张的情况,直接删除该控件即可
    {
        CImgViewElement *item2 = deleteImgPic();//获得要删除的那个控件
        p_layout->removeWidget(item2);//将该控件进行删除
        delete item2;//斩草除根
        m_totalImageList.removeAt(m_currentIndex);
        m_totalVector.remove(m_currentIndex);
        pictureList.removeAt(m_currentIndex);
        if(m_totalVector.size()>0)
            setSeleceted(m_currentIndex);
    }
    else if(m_totalImageList.size()>m_col)
    {
        CImgViewElement *item2 = deleteImgPic();//获得要删除的那个控件
        p_layout->removeWidget(item2);//将该控件进行删除
        delete item2;//斩草除根
        m_totalVector.remove(m_currentIndex);//在数据结构上面进行修改
        m_totalImageList.removeAt(m_currentIndex);
        pictureList.removeAt(m_currentIndex);
        setSeleceted(m_currentIndex); //让方框重新定位到删除后的下一个控件
        int m_replaceIndex = (m_currentPage+1)*m_col; 
        QString fileName=pictureList.at(m_replaceIndex-1);;//这个fileName的数值要注意
        CImgViewElement *item = nextImgPic();//新创建一个新的item控件,加载后面的图片,当前这种情况是后面还有图片的情况
        item->loadImage(fileName);
        if(m_size.isValid())
            item->setFixedSize(m_size.width(),m_size.height());
        p_layout->addWidget(item);
    }
    updatePage();
    this->update();
}
CImgViewElement* CDeepPictureViewer::deleteImgPic()
{
    CImgViewElement *item = nullptr;
    item = m_totalVector.at(m_currentIndex);
    return  item;
}

关于这两个函数,该讲的也都在注释中了,整体思路就是,先判断还有多少张图片,是要直接删控件,还是得删掉控件,在末尾再创建控件,这取决于图片的张数。然后,获得控件,删除控件,在数据结构中也进行删除,然后分情况进行处理。

代码地址

https://download.csdn.net/download/weixin_38809485/12624914里面的imageViewer
由于时间问题,如有错误,欢迎指正 ~

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以通过继承QSlider类并重写paintEvent()函数来自定义QSlider滑动条,同时可以通过设置QStyleOptionSlider中的subControls属性来设置滑块的形状为圆形。具体实现可以参考以下代码: ``` class CustomSlider : public QSlider { public: CustomSlider(QWidget *parent = nullptr) : QSlider(parent) { setStyleSheet("QSlider::groove:horizontal { height: 10px; background: #ddd; }" "QSlider::handle:horizontal { width: 20px; border-radius: 10px; background: #333; }"); } protected: void paintEvent(QPaintEvent *event) override { QSlider::paintEvent(event); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QStyleOptionSlider opt; initStyleOption(&opt); QRect grooveRect = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this); QRect handleRect = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this); QPoint center = handleRect.center(); painter.setPen(Qt::NoPen); painter.setBrush(QColor("#333")); painter.drawEllipse(center, handleRect.width() / 2, handleRect.height() / 2); } }; ``` 在上述代码中,我们通过设置QSlider的样式表来设置滑动条的样式,其中groove表示滑动条的轨道,handle表示滑块。在paintEvent()函数中,我们首先调用QSlider的paintEvent()函数来绘制默认的滑动条,然后获取滑动条的轨道和滑块的矩形区域,并计算出滑块的中心点。最后,我们使用QPainter绘制一个圆形,将其填充为黑色,并以滑块的中心点为圆心,滑块宽度的一半为半径进行绘制,从而实现了圆形滑块的效果。 ### 回答2: QT是一款跨平台的C++图形用户界面应用程序开发框架。在QT中,我们可以自定义QSlider滑动条以及滑块的形状。 要自定义QSlider滑动条,首先我们需要创建一个自定义的滑动条类,继承QSlider。在该类中,我们可以重写一些QSlider的虚函数,如paintEvent()、sliderChange()等,以实现自定义的滑动条样式和功能。 要实现圆形的滑块,我们可以通过设置滑块样式表来实现。在QSlider的子类中,通过重写paintEvent()函数,我们可以在滑动条上绘制自定义的滑块。 以下是一个自定义的QSlider滑动条,滑块为圆形的示例代码: ```cpp class CustomSlider : public QSlider { Q_OBJECT public: CustomSlider(QWidget *parent = nullptr) : QSlider(parent) { setStyleSheet("QSlider::handle {" " background: green;" " border-radius: 8px;" " width: 16px;" " height: 16px;" "}"); } protected: void paintEvent(QPaintEvent *event) override { QSlider::paintEvent(event); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); QRect grooveRect = style()->subControlRect(QStyle::CC_Slider, this, QStyle::SC_SliderGroove, this); int x = grooveRect.x() + grooveRect.height() / 2 - 8; int y = grooveRect.y() + grooveRect.height() / 2 - 8; painter.setBrush(QColor(51, 153, 255)); painter.setPen(Qt::NoPen); painter.drawEllipse(QRect(x, y, 16, 16)); } }; ``` 在上述代码中,我们重写了CustomSlider的构造函数和paintEvent()函数。构造函数中通过设置样式表,将滑块的背景设为绿色,圆角半径设为8px,并设置滑块的大小为16x16px。paintEvent()函数中,我们通过获取滑动条的轨道区域,计算滑块的位置,然后绘制一个蓝色圆形作为滑块。 我们可以在应用程序中使用CustomSlider类来替代QSlider类,从而实现自定义的滑动条和圆形滑块。 ### 回答3: 要自定义QSlider滑动条,使滑块圆形,可以通过重写QStyle类的drawComplexControl方法来实现。具体步骤如下: 1. 创建一个继承自QStyle的自定义样式类,并重写drawComplexControl方法。 2. 在drawComplexControl方法中,首先调用父类的drawComplexControl方法,这样可以绘制默认样式的滑块。 3. 获取滑块的矩形区域,并将其设置为圆形。 4. 根据滑块的圆形矩形区域,绘制一个圆形。 5. 最后,将自定义样式类应用到QSlider控件上。 下面是实现上述步骤的示例代码: ```C++ #include <QtWidgets> class CustomSliderStyle : public QStyle { public: using QStyle::QStyle; void drawComplexControl(ComplexControl control, const QStyleOptionComplex* option, QPainter* painter, const QWidget* widget = nullptr) const override { if (control == CC_Slider && option->subControls == SC_SliderHandle) { QStyleOptionSlider opt = *qstyleoption_cast<const QStyleOptionSlider*>(option); // 绘制默认样式的滑块 QStyle::drawComplexControl(control, option, painter, widget); // 获取滑块的矩形区域 QRect grooveRect = subControlRect(CC_Slider, option, SC_SliderHandle, widget); QRect handleRect = opt.rect; // 将滑块的矩形区域设置为圆形 handleRect.setSize(QSize(handleRect.width(), handleRect.width())); handleRect.moveCenter(grooveRect.center()); // 绘制圆形滑块 painter->save(); painter->setRenderHint(QPainter::Antialiasing, true); painter->setPen(Qt::NoPen); painter->setBrush(opt.palette.buttonText()); painter->drawEllipse(handleRect); painter->restore(); } else { QStyle::drawComplexControl(control, option, painter, widget); } } }; int main(int argc, char *argv[]) { QApplication app(argc, argv); QSlider slider; CustomSliderStyle customStyle; slider.setStyle(&customStyle); slider.show(); return app.exec(); } ``` 通过以上代码,可以自定义QSlider滑动条,使滑块变为圆形。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值