目标:
- 使用Qt Model/View的思想实现一个幻灯片播放列表编辑器. 有上移, 下移, 添加, 删除, 保存等功能. 效果如下图所示:
Model(XmlModel)继承自 QAbstractTableModel, 根据需要实现对应的接口. 主要代码如下:
#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
#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控件实现. 代码如下:
#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
#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();
}
}
<?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>
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}