Qt QAbstractTableModel + QTableView 实现的一个图片播放列表编辑器

目标:

  • 使用Qt Model/View的思想实现一个幻灯片播放列表编辑器. 有上移, 下移, 添加, 删除, 保存等功能. 效果如下图所示:

Model(XmlModel)继承自 QAbstractTableModel, 根据需要实现对应的接口. 主要代码如下:

  • xmlmodel.h
#ifndef XMLMODEL_H
#define XMLMODEL_H

#include <QAbstractTableModel>

class XmlModel : public QAbstractTableModel
{
    Q_OBJECT

public:
    explicit XmlModel(QObject *parent = nullptr);

    // Header:
    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;

    // Basic functionality:
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;
    Qt::ItemFlags flags (const QModelIndex &index) const override;      //控制表格特性, 是否可选中, 可编辑等.

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
    //移动行
    bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) override;
    //拖拽, 暂未使用
    bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
    bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;        //删除行
    bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;        //插入行
    bool upRows(int row, int count, const QModelIndex &parent = QModelIndex());                     //上移一行
    bool downRows(int row, int count, const QModelIndex &parent = QModelIndex());                   //下移一行
    QList<QPair<QString, int>> getList();                                                      //获取model中全部数据, 用于导出等目的

private:
    QList <QPair<QString, int>> m_data;
    QStringList  header;
};

#endif // XMLMODEL_H

  • xmlmodel.cpp
#include "xmlmodel.h"
#include <QDebug>

XmlModel::XmlModel(QObject *parent)
    : QAbstractTableModel(parent)
{
    header<<tr("素材(图片)")<<tr("播放时间(秒)");
//    m_data.push_back(QPair<QString, int>("1.png", 5));
//    m_data.push_back(QPair<QString, int>("2.png", 5));
//    m_data.push_back(QPair<QString, int>("3.png", 5));
//    m_data.push_back(QPair<QString, int>("4.png", 5));
//    m_data.push_back(QPair<QString, int>("5.png", 5));
}

QVariant XmlModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    // FIXME: Implement me!    
    if(role==Qt::DisplayRole&&orientation==Qt::Horizontal)
        return header[section];
    return QAbstractTableModel::headerData(section,orientation,role);
}

int XmlModel::rowCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return 0;
    return m_data.count();

    // FIXME: Implement me!
}

int XmlModel::columnCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return 0;
    return 2;

    // FIXME: Implement me!
}

Qt::ItemFlags XmlModel::flags(const QModelIndex &index) const
{
    if(index.column() == 1) //设置第二列可编辑
        return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
    else
        return  Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

QVariant XmlModel::data(const QModelIndex &index, int role) const
{
    //qDebug() << __FUNCTION__ << __LINE__ <<"index.row() == " << index.row() << "role===" << role;
    if (!index.isValid())
        return QVariant();

    // FIXME: Implement me!
    QPair<QString, int> da = m_data[index.row()];
    switch (role) {
    case Qt::DisplayRole:
    {
        switch (index.column()) {
        case 0:
            return da.first;
        case 1:
            return da.second;
        }
        break;
    }
    //编辑状态时的处理(如数据为int, 会自动将该单元格转为int调整格, 带上下按钮, 并屏蔽字母输入)
    case Qt::EditRole: 
    {
        switch (index.column()) {
        case 1:
            return da.second;
        }
        break;
    }
    default:
        break;
    }

    return QVariant();
}

bool XmlModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    //qDebug() << "index row = " << index.row() << ", index column =" << index.column() << ", role = " << role;
    if(index.isValid()&&role==Qt::EditRole){
        switch (index.column()) {
        case 0:
            m_data[index.row()].first = value.value<QString>();
            break;
        case 1:
            m_data[index.row()].second = value.value<int>();
            break;
        default:
            break;
        }
    }
    return true;
}

bool XmlModel::moveRows(const QModelIndex &srcParent, int srcRow, int count,
                        const QModelIndex &dstParent, int dstChild)
{
    Q_UNUSED(srcParent);
    Q_UNUSED(dstParent);
    beginMoveRows(QModelIndex(), srcRow, srcRow + count - 1, QModelIndex(), dstChild);
    for(int i = 0; i<count; ++i) {
        m_data.insert(dstChild + i, m_data[srcRow]);
        int removeIndex = dstChild > srcRow ? srcRow : srcRow+1;
        m_data.removeAt(removeIndex);
    }
    endMoveRows();
    return true;
}

bool XmlModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
    Q_UNUSED(parent);
    Q_UNUSED(column);

    if(row == -1) {
        row = rowCount();
    }

    return QAbstractTableModel::dropMimeData(data, action, row, 0, parent);
}

bool XmlModel::removeRows(int row, int count, const QModelIndex &parent)
{
    Q_UNUSED(parent);
    beginRemoveRows(QModelIndex(), row, row + count - 1);
    for(int i = 0; i<count; ++i) {
        m_data.removeAt(row);
    }
    endRemoveRows();
    return true;
}

bool XmlModel::insertRows(int row, int count, const QModelIndex &parent)
{
    Q_UNUSED(parent);
    beginInsertRows(QModelIndex(), row, row + count - 1);
    for(int i = 0; i<count; ++i) {
        m_data.insert(row, QPair <QString, int>("", 5));
    }
    endInsertRows();
    return true;
}

bool XmlModel::upRows(int row, int count, const QModelIndex &parent)
{
    return moveRows(parent, row, count, parent, row -1);
}

bool XmlModel::downRows(int row, int count, const QModelIndex &parent)
{
    return moveRows(parent, row, count, parent, row +2);    //因为是前插, 这里要加2
}

QList<QPair<QString, int> > XmlModel::getList()
{
    return m_data;
}

View使用QTableView控件实现. 代码如下:

  • widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include "xmlmodel.h"

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_pushButton_add_clicked();

    void on_pushButton_up_clicked();

    void on_pushButton_down_clicked();

    void on_pushButton_del_clicked();

    void on_pushButton_open_clicked();

    void on_pushButton_save_clicked();

    void on_pushButton_saveas_clicked();

    void on_pushButton_close_clicked();

private:
    Ui::Widget *ui;
    XmlModel * m_model;
    QString m_curFile;
    bool saveFile(QString path);
    bool closeFile();
    bool m_saveStatus;  //true 文件已保存, false 文件未保存;
};

#endif // WIDGET_H

  • widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QStandardItemModel>
#include <xmlmodel.h>
#include <QMessageBox>
#include <QDebug>
#include <QFileDialog>
#include <QDomDocument>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    m_curFile = "";
    this->m_saveStatus = true;
    m_model = new XmlModel(this);
    ui->tableView->setModel(m_model);
    ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);//所有列设置自动列宽
    ui->tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed);//对第0列单独设置固定宽度
    ui->tableView->setColumnWidth(1, 100);//设置固定宽度
    ui->tableView->setSelectionMode(QAbstractItemView::SingleSelection);    //设置只能选单行
    ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);     //设置只能选中行
}

Widget::~Widget()
{
    delete ui;
}

//添加
void Widget::on_pushButton_add_clicked()
{
    //m_model->insertRows(m_model->rowCount(), 1);
    QString strs;
    QStringList file_list, output_name;
    //"*.jpg" << "*.png" << "*.gif" << "*.jpeg"
    QStringList str_path_list = QFileDialog::getOpenFileNames(this, tr("选择素材"), tr(""), tr("图片文件(*.jpg *.png *.gif *.jpeg);"));

    //qDebug() << __FUNCTION__ <<__LINE__ << "str_path_list.size===" << str_path_list.size();
    if(0 == str_path_list.size())
        return;
    int curNewRow = m_model->rowCount();
    m_model->insertRows(m_model->rowCount(), str_path_list.size());
    QModelIndex index;
    for(int i = 0; i < str_path_list.size(); ++i)
    {
        index = m_model->index(curNewRow + i, 0);
        m_model->setData(index, str_path_list[i]);
        index = m_model->index(curNewRow + i, 1);
        m_model->setData(index, 5);
    }
    this->m_saveStatus = false;
}

//删除
void Widget::on_pushButton_del_clicked()
{
    int row = ui->tableView->currentIndex().row();
    if(nullptr == m_model || row < 0 || row > m_model->rowCount()-1)
    {
        QMessageBox::information(this,"提示","找不到操作项");
        return ;
    }
    if(nullptr == m_model || row < 0 || row > m_model->rowCount()-1)
        return;
    m_model->removeRows(row, 1);
    this->m_saveStatus = false;
}

//上移
void Widget::on_pushButton_up_clicked()
{
    int row = ui->tableView->currentIndex().row();
    if(nullptr == m_model || row < 0 || row > m_model->rowCount()-1)
    {
        QMessageBox::information(this,"提示","找不到操作项");
        return ;
    }
    //上移需要判断是否第一行,不移动
    if(0 == row)
    {
        QMessageBox::information(this,"提示","已经是第一行");
        return ;
    }
    m_model->upRows(row, 1);
    this->m_saveStatus = false;
}

//下移
void Widget::on_pushButton_down_clicked()
{
    int row = ui->tableView->currentIndex().row();
    if(nullptr == m_model || row < 0 || row > m_model->rowCount()-1)
    {
        QMessageBox::information(this,"提示","找不到操作项");
        return ;
    }
    //下移需要判断是否最后一行,不移动
    if(row == m_model->rowCount()-1)
    {
        QMessageBox::information(this,"提示","已经是最后一行");
        return ;
    }
    m_model->downRows(row, 1);
    this->m_saveStatus = false;
}

void Widget::on_pushButton_open_clicked()
{
    if(!this->m_saveStatus)
    {
        int r = QMessageBox::question(this, "提示","已打开的文件尚未保存, 是否保存? ",QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel,QMessageBox::Yes);
        switch (r) {
        case QMessageBox::Cancel:
            return;
        case QMessageBox::Yes:
            on_pushButton_save_clicked();
            break;
        case QMessageBox::No:
            break;
        default:
            break;
        }
        closeFile();
    }
    QString str_path = QFileDialog::getOpenFileName(this, tr("选择列表文件"), tr(""), tr("列表文件(*.xspf);"));
    if(str_path.isEmpty())
        return;
    //qDebug() << "str_path === " << str_path;
    // FIXME: Implement me!
    m_curFile = str_path;

}

void Widget::on_pushButton_save_clicked()
{
    if(0 == m_model->rowCount())
    {
        int r = QMessageBox::question(this, "提示","列表为空, 仍然保存吗? ",QMessageBox::Ok|QMessageBox::Cancel,QMessageBox::Ok);
        if (r != QMessageBox::Ok)
            return;
    }
    if(m_curFile.isEmpty())
    {
        QString str_path = QFileDialog::getSaveFileName(this, tr("保存列表文件"), tr(""), tr("列表文件(*.xspf);"));
        if(str_path.isEmpty())
            return;
        if(saveFile(str_path))
            QMessageBox::information(this,"提示","保存成功");
    }
    else
        if(saveFile(m_curFile))
            QMessageBox::information(this,"提示","保存成功");
}

void Widget::on_pushButton_saveas_clicked()
{
    if(0 == m_model->rowCount())
    {
        int r = QMessageBox::question(this, "提示","列表为空, 仍然保存吗? ",QMessageBox::Ok|QMessageBox::Cancel,QMessageBox::Ok);
        if (r != QMessageBox::Ok)
            return;
    }
    QString str_path = QFileDialog::getSaveFileName(this, tr("保存列表文件"), tr(""), tr("列表文件(*.xspf);"));
    if(str_path.isEmpty())
        return;
    if(saveFile(m_curFile))
        QMessageBox::information(this,"提示","保存成功");
}

bool Widget::saveFile(QString path)
{
    // FIXME: Implement me!
    Q_UNUSED(path);
    return true;
}

bool Widget::closeFile()
{
    //清空model, 初始化参数
    if(m_model->rowCount() > 0)
        m_model->removeRows(0, m_model->rowCount());
    this->m_curFile = "";
    this->m_saveStatus = true;
    return true;
}

//关闭文件
void Widget::on_pushButton_close_clicked()
{
    if(!this->m_saveStatus)
    {
        int r = QMessageBox::question(this, "提示","文件尚未保存, 是否保存后关闭? ",QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel,QMessageBox::Yes);
        switch (r) {
        case QMessageBox::Cancel:
            return;
        case QMessageBox::Yes:
            on_pushButton_save_clicked();
            break;
        case QMessageBox::No:
            break;
        default:
            break;
        }
        closeFile();
    }
    else
    {
        closeFile();
    }

}

  • widget.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Widget</class>
 <widget class="QWidget" name="Widget">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>792</width>
    <height>494</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>素材编辑工具</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
   <property name="spacing">
    <number>20</number>
   </property>
   <property name="leftMargin">
    <number>20</number>
   </property>
   <property name="topMargin">
    <number>20</number>
   </property>
   <property name="rightMargin">
    <number>20</number>
   </property>
   <property name="bottomMargin">
    <number>20</number>
   </property>
   <item>
    <widget class="QTableView" name="tableView"/>
   </item>
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout_4">
     <item>
      <layout class="QHBoxLayout" name="horizontalLayout">
       <item>
        <widget class="QPushButton" name="pushButton_open">
         <property name="text">
          <string>打开列表</string>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QPushButton" name="pushButton_save">
         <property name="text">
          <string>保存</string>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QPushButton" name="pushButton_saveas">
         <property name="text">
          <string>另存为</string>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QPushButton" name="pushButton_close">
         <property name="text">
          <string>关闭文件</string>
         </property>
        </widget>
       </item>
      </layout>
     </item>
     <item>
      <spacer name="horizontalSpacer">
       <property name="orientation">
        <enum>Qt::Horizontal</enum>
       </property>
       <property name="sizeHint" stdset="0">
        <size>
         <width>40</width>
         <height>20</height>
        </size>
       </property>
      </spacer>
     </item>
     <item>
      <layout class="QHBoxLayout" name="horizontalLayout_3">
       <item>
        <widget class="QPushButton" name="pushButton_add">
         <property name="text">
          <string>添加素材</string>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QPushButton" name="pushButton_del">
         <property name="text">
          <string>删除素材</string>
         </property>
        </widget>
       </item>
      </layout>
     </item>
     <item>
      <spacer name="horizontalSpacer_2">
       <property name="orientation">
        <enum>Qt::Horizontal</enum>
       </property>
       <property name="sizeHint" stdset="0">
        <size>
         <width>40</width>
         <height>20</height>
        </size>
       </property>
      </spacer>
     </item>
     <item>
      <layout class="QHBoxLayout" name="horizontalLayout_2">
       <item>
        <widget class="QPushButton" name="pushButton_up">
         <property name="text">
          <string>上移</string>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QPushButton" name="pushButton_down">
         <property name="text">
          <string>下移</string>
         </property>
        </widget>
       </item>
      </layout>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
 <layoutdefault spacing="6" margin="11"/>
 <resources/>
 <connections/>
</ui>
  • main.cpp
#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}
posted @ 2020-08-13 20:29  技术不支持  阅读(926)  评论(0编辑  收藏  举报