一点一滴成长

导航

《QT Creator快速入门》第十六章:模型/视图编程

1、模型、视图、委托

  MVC包含模型(Model表示数据)、视图(View用户界面)、控制器(Controller界面对用户输入的反应方式)三部分,Qt中还引入了委托(Delegate,又称代理)来对用户输入进行灵活处理,使用它可以定制数据的渲染和编辑方式。

  

  QAbstractItemModel是模型的抽象基类,其子类有:QAbstractListModel是列表模型抽象类、QAbstractTableModel是表格模型抽象类、QStringListModel是文本列表模型,QStandardItemModel是标准项模型,QFileSystemModel是文件系统模型,QSqlQueryModel、QSqlTableModel、QSqlRelationalTableModel用来访问数据库。

  QAbstractItemView是视图的抽象基类,QListView是列表视图类, QTableView是表格视图类,QTreeView是树型视图类,可以子类化它们来定制视图。

  QAbstractItemDelegate是委托的抽象基类,其子类有QStyledItemDelegate和QItemDelegate。

  下面是使用文件系统模型、列表视图、树形视图的例子:

#include <QApplication>
#include <QTreeView>
#include <QListView>
#include <QFileSystemModel>
#include <QStandardItemModel>
#include "stringlistmodel.h"
#include <QTableView>


int main(int argc, char**argv)
{
    QApplication app(argc, argv);

    QFileSystemModel model; //文件系统模型,代表了本地文件系统中的文件和目录
    model.setRootPath(QDir::currentPath()); //设置文件系统模型监视的目录

    QTreeView tree; //树形视图
    tree.setModel(&model); //为视图指定模型
    //void QAbstractItemView::setRootIndex ( const QModelIndex & index )
    //QModelIndex QFileSystemModel::index ( const QString & path, int column = 0 ) //根据指定目录获得模型项索引
    tree.setRootIndex(model.index(QDir::currentPath())); //指定根索引
    tree.show();

    QListView list; //列表视图
    list.setModel(&model);
    list.setRootIndex(model.index(QDir::currentPath()));
    list.show();

    return app.exec();
}
View Code

  

2、模型

  QAbstractItemModel是模型的抽象基类,其抽象子类有:QAbstractListModel列表模型抽象类、QAbstractTableModel表格模型抽象类,具体模型类有:QStringListModel文本列表模型,QStandardItemModel标准项模型,QFileSystemModel文件系统模型,QSqlQueryModel、QSqlTableModel、QSqlRelationalTableModel用来访问数据库以呈现数据(QSqlTableModel是QSqlQueryModel子类,它用起来几乎不用写sql,但是高封装的必然结果就是没有QSqlQueryModel灵活)。

  QStringListModel可以提供简单的文本列表数据模型,QStandardItemModel可以提供复杂列表、表格、树形结构的数据模型,也可以子类化QAbstractItemModel、QAbstractListModel、QAbstractTableModel这些抽象模型来实现自己自定义的模型。

①、 QModelIndex 和 Qt::ItemDataRole

  模型提供接口供视图和委托来访问数据项,当模型中的数据发生变化时,模型会通过信号和槽机制告知相关联的视图。模型中包含了数据项,数据项使用QModelIndex模型索引来表示,视图和委托使用模型索引来请求数据项。模型索引是对一块数据的临时引用,可以用来检索或修改模型中的数据,不需要也不应该存储模型索引,如果想要长时间的引用的话应该使用QPersistentModelIndex创建模型索引。获得数据项的模型索引需要指定行号、列号、父项的模型索引这三个属性:

QModelIndex modelIndex = model->index(row, column, parent);

  如下所示,模型中有一个根数据项是所有顶层数据项的父项,在获取顶层数据项对应的模型索引时父项的模型索引可以使用QModelIndex()来表示,如下为获取表格模型中B项和树形模型中A、B项的索引示意:

QModelIndex tableIndexB = model->index(1, 1, QModelIndex());

QModelIndex treeIndexA = model->index(0, 0, QModelIndex());
QModelIndex treeIndexC = model->index(1, 0, treeIndexA);

  数据项中包含了不同角色的数据,如文本、图标、提示等,这些角色由枚举变量Qt::ItemDataRole来定义,常用的角色如下图所示,查看所有角色可以在Qt帮助中索引Qt::ItemDataRole。

  可以通过向模型指定数据项的模型索引和角色来获取角色数据:

QVariant value = model->data(modexIndex, role);

 下面使用模型索引重新设置了文本列表第一行显示的数据,并添加了一行数据:

#include <QApplication>
#include <QListView>
#include <QStringListModel>

int main(int argc, char**argv)
{
    QApplication app(argc, argv);

    QStringListModel listModel; //文本列表模型,只支持Qt::EditRole或Qt::DisplayRole
    listModel.setStringList({"e", "f", "b", "c"}); //设置文本列表模型中显示文本数据

    QListView listView;
    listView.setModel(&listModel);
    listView.show();

    QModelIndex index = listModel.index(0, 0, QModelIndex()); //获得第一行数据项的模型索引
    listModel.setData(index, "a"); //设置第一行的显示数据
    listModel.removeRow(1); //删除第二行
    listModel.insertRow(listModel.rowCount()); //向末尾插入一行
    listModel.setData(listModel.index(listModel.rowCount() - 1), "d", Qt::DisplayRole); //设置末尾行的显示数据

    return app.exec();
}
View Code

  

②、 QStandardItemModel

  QStringListModel只支持Qt::DisplayRole和Qt::EditRole、两个角色,所以其适合简单的文本列表,对于复杂的列表显示或者表格、树形结构可以使用QStandardItemModel。

  QStandardItemModel标准项模型的setItem()方法可以添加一个标准项,这样就相当于给视图添加了一个数据项。QStandardItem是标准项类,可以在标准项里设置项显示的文本等,下面是表格视图使用标准项模型的一种方法:

#include <QApplication>
#include <QTreeView>
#include <QListView>
#include <QFileSystemModel>
#include <QStandardItemModel>
#include <QTableView>

int main(int argc, char**argv)
{
    QApplication app(argc, argv);

    QStandardItemModel *model = new QStandardItemModel(/*&tb*/);
    model->setRowCount(3);
    model->setColumnCount(4);
    for(int i = 0; i < 3; ++i)
    {
        for(int j = 0; j < 4; ++j)
        {
            model->setData(model->index(i, j),QString("x"));
        }
    }

    QTableView tableView;
    tableView.setModel(model);
    tableView.show();

    return app.exec();
}
View Code

   

  下面是表格视图使用标准项模型的另一种方法:

#include <QApplication>
#include <QTreeView>
#include <QListView>
#include <QFileSystemModel>
#include <QStandardItemModel>
#include <QTableView>

int main(int argc, char**argv)
{
    QApplication app(argc, argv);

    QStandardItemModel* model = new QStandardItemModel(7, 4);
    for(int row = 0; row < 4; ++row)
    {
        for(int column = 0; column < 3; ++column)
        {
            QStandardItem* item = new QStandardItem(QString("%1").arg(row * 3 + column));
            if(column == 0)
            {
                item->setCheckable(true);
                item->setCheckState(Qt::Checked);
            }
            model->setItem(row, column, item);
        }
    }

    QTableView tableView;
    tableView.setModel(model);
    tableView.show();

    return app.exec();
}
View Code

  

  下面是树形视图通过标准项模型和标准项来设置对应数据项中各角色数据的示例,既可以通过标准项的setText()、setIcon()等方法来直接设置对应角色数据,也可以通过setData()来设置指定角色的数据:

#include <QApplication>
#include <QTreeView>
#include <QListView>
#include <QFileSystemModel>
#include <QStandardItemModel>
#include "stringlistmodel.h"
#include <QTableView>

int main(int argc, char**argv)
{
    QApplication app(argc, argv);

    QStandardItemModel model; //创建树形列表模型
    QStandardItem* parentItem = model.invisibleRootItem(); //获取模型的根项,根项是不可见的

    //创建标准项item0,设置其显示文本、图标、提示后,将其作为根项的子项
    QStandardItem* item0 = new QStandardItem;
    item0->setText("A"); // item0->setData("A", Qt::EditRole);
    QPixmap pixmap0(50, 50);
    pixmap0.fill("red");
    item0->setIcon(QIcon(pixmap0)); // item0->setData(QIcon(pixmap0), Qt::DecorationRole);
    item0->setToolTip("indexA"); // item0->setData("indexA", Qt::ToolTipRole);
    parentItem->appendRow(item0);

    //创建标准项item1,设置其显示文本、图标、提示后,将其作为item0的子项
    QStandardItem* item1 = new QStandardItem;
    QPixmap pixmap1(50, 50);
    pixmap1.fill("green");
    item1->setData("B", Qt::EditRole); // item1->setText("B");
    item1->setData(QIcon(pixmap1), Qt::DecorationRole); // item1->setIcon(QIcon(pixmap1)); 
    item1->setData("indexB", Qt::ToolTipRole); // item1->setToolTip("indexB");
    item0->appendRow(item1);

    //在树视图中显示模型
    QTreeView tree;
    tree.setModel(&model);
    tree.show();

    return app.exec();
}
View Code

 

③、 自己实现模型

  下面是子类化抽象模型QAbstractListModel来实现自定义模型的一个例子,如下的StringListModel类继承自QAbstractListModel,并重写了QAbstractListModel的rowCount()、data()、headerData()方法:

#ifndef STRINGLISTMODEL_H
#define STRINGLISTMODEL_H
#include <QAbstractListModel>
#include <QStringList>
#include <QFont>
#include <QString>
#include <QSize>
#include <QColor>

class MyStringListModel: public QAbstractListModel
{
public:
    MyStringListModel(const QStringList& strings, QObject* parent = nullptr)
        :QAbstractListModel(parent),
          stringList(strings)
    {

    }

    //返回模型的行数
    int rowCount(const QModelIndex &parent)const
    {
        return stringList.size();
    }

    //返回指定模型索引的数据项
    QVariant data(const QModelIndex &index, int role)const
    {
        if(!index.isValid() || index.row() >= stringList.size())
            return QVariant();

        if(role == Qt::DisplayRole)
            return stringList.at(index.row());
        else if(role == Qt::FontRole)
            return QFont(QString::fromStdWString(index.row() > 0 ? L"微软雅黑" : L"宋体"));
        else if(role == Qt::TextAlignmentRole)
            return (index.row() & 1) ? Qt::AlignLeft: Qt::AlignRight;
        else if(role == Qt::BackgroundRole)
            return (index.row() & 1) ? QColor(255, 0, 0, 25) : Qt::white;
        else
            return QVariant();
    }

    //设置表头数据
    QVariant headerData(int section, Qt::Orientation orientation,
                                int role)const
    {
        if(role != Qt::DisplayRole)
            return QVariant();

        if(orientation == Qt::Horizontal)
            return QString("Column %1").arg(section);
        else
            return QString("Row %1").arg(section);
    }
private:
    QStringList stringList;
};

#endif // STRINGLISTMODEL_H
View Code
#include <QApplication>
#include <QTreeView>
#include <QListView>
#include <QFileSystemModel>
#include <QStandardItemModel>
#include "stringlistmodel.h"
#include <QTableView>

int main(int argc, char**argv)
{
    QApplication app(argc, argv);

    QStringList list;
    list << QString::fromStdWString(L"测试1") << QString::fromStdWString(L"测试2") << "test";
    MyStringListModel model(list);

    QListView listView; //列表视图
    listView.setModel(&model);
    listView.show();

    QTableView tableView; //表格视图
    tableView.setModel(&model);
    tableView.show();

    return app.exec();
}
View Code

   

  QStringListModel和QStandardItemModel中支持项目的双击编辑操作,对于上面示例程序中我们自己实现的Model,如果想要添加列表项也支持编辑功能的话需要重写flags()、setData()、data()方法,flags()用来告知委托项目的标志(项目的标志如下所示),这里添加可以编辑标记,setData()用来告知委托向模型中设置的数据,这个方法是在编辑完成后被调用的,data()方法用来设置在不同角色下项目的数据,这里在data()方法中添加编辑角色的数据:

//返回指定模型索引的数据项
QVariant StringListModel::data(const QModelIndex &index, int role)const
{
    if(!index.isValid() || index.row() >= stringList.size())
        return QVariant();

    if(role == Qt::DisplayRole || role == Qt::EditRole)
        return stringList.at(index.row());
    else
        return QVariant();
}

bool StringListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if(index.isValid() && role == Qt::EditRole)
    {
        stringList.replace(index.row(), value.toString());
        emit dataChanged(index, index);//发送dataChanged()信号:数据被设置后模型必须让视图知道有数据已经改变
        return true;
    }
    return false;
}

Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
    if(!index.isValid())
        return Qt::ItemIsEnabled;

    return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}
View Code

  

  要想上面我们自实现的模型实现显示CheckBox功能的话,需要再次对flags()、data()、setData()方法进行调整:

//返回指定模型索引的数据项
    QVariant data(const QModelIndex &index, int role)const
    {
        if(!index.isValid() || index.row() >= stringList.size())
            return QVariant();

        if(role == Qt::DisplayRole || role == Qt::EditRole)
            return stringList.at(index.row());
        else if(role == Qt::FontRole)
            return QFont(QString::fromStdWString(index.row() > 0 ? L"微软雅黑" : L"宋体"));
        else if(role == Qt::TextAlignmentRole)
            return (index.row() & 1) ? Qt::AlignLeft: Qt::AlignRight;
        else if(role == Qt::BackgroundRole)
            return (index.row() & 1) ? QColor(255, 0, 0, 25) : Qt::white;
        else if(role == Qt::CheckStateRole){
            if(index.column() == CHECK_COLUMN)
                if (m_mapCheckState.contains(index.row()))
                    return m_mapCheckState[index.row()] == Qt::Checked ? Qt::Checked : Qt::Unchecked;
                else
                    return Qt::Unchecked;
        }
        else
            return QVariant();
    }

    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole)override
    {
        if(!index.isValid())
            return false;

        if(role == Qt::EditRole)
        {
                stringList.replace(index.row(), value.toString());
                emit dataChanged(index, index);//发送dataChanged()信号:数据被设置后模型必须让视图知道有数据已经改变
                return true;
        }
        if (role == Qt::CheckStateRole && index.column() == CHECK_COLUMN)
        {
                m_mapCheckState[index.row()] = (value == Qt::Checked ? Qt::Checked : Qt::Unchecked);
                //emit dataChanged(index, index);//发送dataChanged()信号:数据被设置后模型必须让视图知道有数据已经改变
                return true;
        }

        return false;
    }

    Qt::ItemFlags flags(const QModelIndex &index) const override
    {
        if(!index.isValid())
            return Qt::ItemIsEnabled;

        if (index.column() == CHECK_COLUMN)
            return QAbstractItemModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsUserCheckable;

        return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
    }
private:
    QStringList stringList;
    QMap<int, Qt::CheckState> m_mapCheckState;    
View Code

  要想上面我们自实现的模型实现插入行和删除行功能,需要重写insertRows()和removeRows()方法,在上面的示例中加上这两个方法的重写:

//插入行
bool StringListModel::insertRows(int row/*插入位置*/, int count/*插入行数*/, const QModelIndex &parent/*父项的模型索引*/)
{
    beginInsertRows(QModelIndex(), row, row + count - 1); //告知其他组件指定的行将要发生变化
    for(int r = 0; r < row; ++r)
    {
        stringList.insert(row, "insert");
    }
    endInsertRows(); //告知其他组件模型大小发生了变化

    return true;
}

//删除行
bool StringListModel::removeRows(int row, int count, const QModelIndex &parent)
{
    beginRemoveRows(QModelIndex(), row, row + count - 1);
    for(int r = 0; r < count; ++r)
    {
        stringList.removeAt(r);
    }
    endRemoveRows();

    return true;
}
View Code
#include <QApplication>
#include <QTreeView>
#include <QListView>
#include <QFileSystemModel>
#include <QStandardItemModel>
#include "stringlistmodel.h"
#include <QTableView>

int main(int argc, char**argv)
{
    QApplication app(argc, argv);

    QStringList list;
    list << "a" << "b" << "c";
    StringListModel model(list);

    QListView listView; //列表视图
    listView.setModel(&model);
    listView.show();

    QTableView tableView; //表格视图
    tableView.setModel(&model);
    tableView.show();

    model.insertRows(3, 2); //从第四行开始创建两行
    model.removeRows(0, 1); //从第一行开始删除1行

    return app.exec();
}
View Code

  

  如果向设计一个更复杂的模型,可以参考Qt的Simple Tree Model程序。

④、代理模型

  代理模型可以将一个模型中的数据进行排序或者过滤,然后提供给视图进行显示,QSortFilterProxyModel是标准的代理模型,如下示例代码,其中的MainWindow是QMainWindow的派生窗口,上面放置了一个ListView和一个按钮一个line edit,我们点击“排序”按钮则列表项目会按文本进行正序或倒序显示,点击“过滤”则只会显示符合指定正则的数据项。关于QSortFilterProxyModel的使用,可以参考Qt中的Basic Sort/Filter Model和Address Book示例程序,如果想自定义代理模型,可以参考Custom Sort/Filter Model示例。

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QStringListModel>
#include <QSortFilterProxyModel>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QStringList list;
    list << "yafei" << "yafeilinux" << "Qt" << "Qt Creator";

    QStringListModel* listModel = new QStringListModel(list, this);
    m_filterModel = new  QSortFilterProxyModel(this);
    m_filterModel->setSourceModel(listModel); //为代理模型添加源模型

    QListView* l = ui->listView;
    ui->listView->setModel(m_filterModel); //在视图中使用代理模型
}

void MainWindow::on_pushButton_clicked()
{
    QRegExp rx(ui->lineEdit->text());
    m_filterModel->setFilterRegExp(rx);
}

void MainWindow::on_pushButton_2_clicked()
{
    static bool bFlag = true;
    Qt::SortOrder order = bFlag ? Qt::SortOrder::AscendingOrder: Qt::SortOrder::DescendingOrder;
    m_filterModel->sort(0, order);
    bFlag = !bFlag;
}
View Code

     

⑤、数据-部件映射器

  数据-部件映射器QDataWidgetMapper可以在数据模型的一列和一个窗口部件之间提供一个映射,这样就可以在一个窗口部件上显示和编辑模型中的一行数据,如下所示代码为在QMainWindow派生窗口上拖放了两个Label、两个Line Edit、两个Push Button,点击按钮可以在模型中的每行数据进行切换,在Line Edit里对数据进行修改后会直接反应到数据模型上。关于QDataWidgetMapper类的使用也可以参考Qt中的Simple Widget Mapper和Combo Widget Mapper示例。

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QDataWidgetMapper>
#include <QStandardItemModel>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QStandardItemModel* model = new QStandardItemModel(3, 2, this);
    model->setItem(0, 0, new QStandardItem("A"));
    model->setItem(0, 1, new QStandardItem("90"));
    model->setItem(1, 0, new QStandardItem("B"));
    model->setItem(1, 1, new QStandardItem("75"));
    model->setItem(2, 0, new QStandardItem("C"));
    model->setItem(2, 1, new QStandardItem("60"));

    m_mapper = new QDataWidgetMapper(this);
    m_mapper->setModel(model); //设置模型
    m_mapper->addMapping(ui->lineEdit, 0); //设置模型中的第一列和窗口部件映射
    m_mapper->addMapping(ui->lineEdit_2, 1); //设置模型中的第二列和窗口部件映射
    m_mapper->toFirst(); //窗口部件显示模型中的第一行数据
}

//"上一条"按钮
void MainWindow::on_pushButton_clicked()
{
    m_mapper->toPrevious(); //窗口部件显示模型中的上一行的数据
}

//"下一条"按钮
void MainWindow::on_pushButton_2_clicked()
{
    m_mapper->toNext(); //窗口部件显示模型中的下一行的数据
}
View Code

  

3、视图

 ①、 QAbstractItemView

  QAbstractItemView是视图的抽象基类,QListView是列表视图类, QTableView是表格视图类,QTreeView是树型视图类,可以子类化它们来定制视图,比如如果想要实现条形图或者饼状图等显示方法需要重新实现视图类,QT示例程序中有一个Chart Example示例,它就是从QAbstractItemView继承实现了饼状图。

  视图可以处理项目的选择,还提供了拖放等用户接口。 QTableView和QTreeView还可以显示表头,它们使用QAbstractItemModel::headerData()从模型中获取数据,然后一般使用一个标签来显示表头数据,可以通过子类化QHeaderView来设置标签的显示。

 ②、选择行为和选择模式

  下面是视图中的选择行为(QAbstractItemView::SelectionBehavior)和选择模式(QAbstractItemView::SelectionMode),可以通过视图的selectionBehavior()和setSelectionBehavior()方法来获取和设置选择行为,QListView和QListView默认的选择行为是SelectItems,通过视图的selectionMode()和setSelectionMode()方法来获得视图的选择模式,QListView的默认选择模式是SingleSelection,QListView的默认选择模式是ExtendedSelection。

  

 ③、选择项目和获得选择的项目

  视图中可以有一个当前项目和一个或多个被选择的项目,如下0处为当前项目,5处为选择项目:

   

  QItemSelectionModel是所有项目选择状态的记录,下面是设置选择项目、获得选择的项目、获得当前项目的示例:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QStandardItemModel>
#include <QTableView>


MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QStandardItemModel* model = new QStandardItemModel(7, 4, this);
    for(int row = 0; row < 7; ++row)
    {
        for(int column = 0; column < 4; ++column)
        {
            QStandardItem* item = new QStandardItem(QString("%1").arg(row * 4 + column));
            model->setItem(row, column, item);
        }
    }

    m_tableView = new QTableView;
    m_tableView->setModel(model);
    setCentralWidget(m_tableView);

    //获取视图的项目选择模型
    QItemSelectionModel* selectionModel = m_tableView->selectionModel();

    //定义左上角和右下角的索引,使用这两个索引创建选择
    QModelIndex topLeft = model->index(1, 1, QModelIndex());
    QModelIndex bottomRight = model->index(5, 2, QModelIndex());
    QItemSelection selection(topLeft, bottomRight);

    //选择项目
    selectionModel->select(selection, QItemSelectionModel::Select);
    
    //获得所有选择的项目
    QModelIndexList list = selectionModel->selectedIndexes();

    //获得当前项目
    QModelIndex curModelIdx = selectionModel->currentIndex();
}
View Code

  使用QItemSelectionModel::select()选择项目的时候还可以指定其他模式,如QItemSelectionModel::Select | QItemSelectionModel::Rows表示选中选择项目所在的行,QItemSelectionModel::Toggle()将项目的当前状态反转(未被选择的成为选择状态,已选择的成为未选择状态)。

 ④、选择改变的信号

  当选择的项目改变的时候,模型QItemSelectionModel会发生相关的信号,如下所示,我们在上面的示例中添加选择模型中选择项目改变和当前项目改变的信号和槽:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    ......

    //添加信号(选择模型中选择的项目改变、当前项目改变)和槽的管理
    connect(selectionModel, SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
            this, SLOT(updateSelection(QItemSelection, QItemSelection)));
    connect(selectionModel, SIGNAL(currentChanged(QModelIndex, QModelIndex)),
            this, SLOT(changeCurrent(QModelIndex, QModelIndex)));
}

//更新选择的项目中的值并清空上次选择项目中的值
void MainWindow::updateSelection(const QItemSelection& selected, const QItemSelection& deselected)
{
    //给选择的项目填充新的值
    QModelIndexList list = selected.indexes();
    for(auto index : list)
    {
        QString text = QString("(%1, %2)").arg(index.row()).arg(index.column());
        m_tableView->model()->setData(index, text);
    }

    //清空上一次选择的项目的内容
    list = deselected.indexes();
    for(auto index : list)
    {
        m_tableView->model()->setData(index, "");
    }
}

//显示上次当前项目到当前项目的变化
void MainWindow::changeCurrent(const QModelIndex& current, const QModelIndex& previous)
{
    qDebug() << QString("move (%1, %2) to (%3, %4)").arg(previous.row()).arg(previous.column())
                                                    .arg(current.row()).arg(current.column());
}
View Code

  

 ⑤、共享选择

  当多个视图使用同一个模型数据时,使用setSelectionModel()为他们设置相同的选择模型,那么就可以共享选择:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    ......

    //获取视图的项目选择模型
    QItemSelectionModel* selectionModel = m_tableView->selectionModel();

    ......

    tableView2 = new QTableView;
    tableView2->setWindowTitle("tableView2");
    tableView2->resize(400, 300);
    tableView2->setModel(model);
    tableView2->setSelectionModel(selectionModel);
    tableView2->show();
}
View Code

  

4、委托

①、QStyledItemDelegate和QItemDelegate

  委托可以用来提供项目的特殊显示以及编辑功能,控制委托的标准接口在QAbstractItemDelegate类中定义。委托通过实现paint()和sizeHint()来使它们可以渲染自身的内容,QAbstractItemDelegate中的这两个方法都是纯虚函数,为了以自定义的方式渲染item,你必须实现paint()和sizeHint()函数,即如果想要实现自定义的渲染操作可以继承QAbstractItemDelegate来实现相关接口。QAbstractItemDelegate的子类有QStyledItemDelegate和QItemDelegate,Qt默认的委托是QStyledItemDelegate。QItemDelegate类提供了paint()和sizeHinit()函数的默认实现,如果不需要自定义渲染,而不必子类化此类。QStyledItemDelegate使用当前的样式来绘制其条目,所以如果要实现自定义的委托或要和Qt样式表一起应用时,建议使用QStyledItemDelegate,不过,除非自定义委托需要自己进行绘制,否则,二者的代码其实是一样的。

  可以使用视图的itemDelegate()获取视图中使用的委托,使用setItemDelegate()为视图安装一个自定义的委托。Qt中的标准视图QListView、QTableView、QTreeView都默认使用QItemDelegate实例来提供项目的编辑功能,标准视图中的默认委托会处理所有的标准角色来为每一个项目提供普通风格的渲染(具体可查看QItemDelegate的帮助文档)。

②、提供自定义的编辑

  委托的编辑器可以通过两种方式来实现,一种是使用部件管理编辑过程,为了实现这种效果必须重新实现createEditor()函数来提供一个etidor widget,用setEditorData()函数将model中的数据填充至editor,用setModelData函数以便委托能够从editor中更新model数据。另一种提供自定义的编辑的方法是直接处理事件,通过重新实现editorEvent()函数来处理事件,在paint()方法中进行控件的绘制。第一种方式适合用户双击项目的时候想显示自定义的部件的情况(如下A),第二种方式适合项目进行自定义的显示(如7自绘中的④)或者显示一个控件(如下B)的情况。

  A第一种方式:

  双击QTableView的项目默认会出现一个editor来对Item的显示值进行编辑,下面的示例通过子类化QItemDelegate实现了一个自定义委托SpinBoxDelegate,在委托中使用QSpinBox部件来替换Editor来管理项目的编辑。然后在一个基于QMainWindow窗口上创建了一个拥有7行4列数据的表格视图,为视图设置了这个自定义的委托。也可以参考Qt中的Spin Box Delegate示例程序,如果想要继承QAbstractItemDelegate来实现自定义的渲染操作可以参考Qt的Pixelator示例程序。还可以使用QStyledItemDelegate作为基类,这样可以自定义数据的显示,这个可以参考Star Delegate示例。 

  在完成项目的编辑后(比如输入回车)QItemDelegate会发射closeEditor(QWidget *, QAbstractItemDelegate::EndEditHint)信号,其第一个参数是编辑的部件,第二个参数为完成编辑后暗示的处理方式,默认为QAbstractItemDelegate::NoHint。QItemDelegate对回车等事件的处理实际上是在eventFilter()虚函数(QObject中虚函数)中进行的,因为QItemDelegate会自动调用编辑器的installEventFilter()把自己添加为编辑器的事件监视器。我们可以在自定义的委托中重写eventFilter()方法,在eventFilter()方法中对于回车事件发射第二个参数是QAbstractItemDelegate::EditNextItem的closeEditor()信号,这样按下回车后会关闭当前编辑器并自动转到下一项目并开启编辑器:

#include "spinboxdelegate.h"
#include <QSpinBox>

SpinBoxDelegate::SpinBoxDelegate(QObject *parent)
    :QItemDelegate(parent)
{

}

//创建编辑器(用户双击项目时),QAbstractItemDelegate的虚函数,QItemDelegate重写了该方法
QWidget *SpinBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                      const QModelIndex &index) const
{
    QSpinBox* editor = new QSpinBox(parent);
    editor->setMinimum(0);
    editor->setMaximum(100);
    return editor;
}

//为编辑器设置数据(编辑器显示的时候),QAbstractItemDelegate的虚函数,QItemDelegate重写了该方法
void SpinBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    int value = index.model()->data(index, Qt::EditRole).toInt();
    QSpinBox* spinBox = static_cast<QSpinBox*>(editor);
    spinBox->setValue(value);
}

//将数据写入到模型(编辑完成(编辑器隐藏)的时候),QAbstractItemDelegate的虚函数,QItemDelegate重写了该方法
void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    QSpinBox* spinBox = static_cast<QSpinBox*>(editor);
    spinBox->interpretText(); //确保获得的是QSpinBox中最近更新的数值
    int value = spinBox->value();
    model->setData(index, value, Qt::EditRole);


}

//设置编辑器的位置
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
                          const QModelIndex &index) const
{
    editor->setGeometry(option.rect);
}

//事件过滤器,QObject的虚函数,QItemDelegate重写了该方法
bool SpinBoxDelegate::eventFilter(QObject *obj, QEvent *event)
{
    QWidget *editor = qobject_cast<QWidget*>(obj);
    if (!editor)
        return false;

    if (event->type() == QEvent::KeyPress) {
        if (static_cast<QKeyEvent *>(event)->key() == Qt::Key_Enter) {
            emit commitData(editor); //更新编辑器中值
            emit closeEditor(editor, QAbstractItemDelegate::EditNextItem); //关闭编辑器并转到下一项目进行编辑
            return true;
        }
    }
    return QItemDelegate::eventFilter(obj, event);
}
View Code
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //创建标准项数据模型并添加7行4列的数据
    QStandardItemModel* model = new QStandardItemModel(7, 4, this);
    for(int row = 0; row < 7; ++row)
    {
        for(int column = 0; column < 4; ++column)
        {
            QStandardItem* item = new QStandardItem(QString("%1").arg(row * 4 + column));
            model->setItem(row, column, item);
        }
    }

    //创建表格视图并设置数据模型
    m_tableView = new QTableView;
    m_tableView->setModel(model);
    setCentralWidget(m_tableView);

    //创建自定义委托并
    SpinBoxDelegate* delegate = new SpinBoxDelegate(this);
    m_tableView->setItemDelegate(delegate);
}
View Code

  

  除了在QItemDelegate的eventFilter()中对编辑器上的事件进行处理,我们也可以在QItemDelegate中添加编辑器的editingFinished()信号的槽方法来处理编辑器的编辑完成事件,或者子类化编辑器QSpinBox,然后在自定义的类中添加编辑器相关事件的处理,比如重写QSpinBox的keyPressEvent(QKeyEvent *event)处理键盘按下事件(对于在QItemDelegate中的编辑器重写keyPressEven()的话发现其不能识别出回车按键,猜测为委托中对回车按键进行了截获处理)。

  B第二种方式:

  如下表格的第一列和第二列分别显示了CheckBox和进度条控件,CheckBoxDelegate类继承自QStyledItemDelegate:

#include "mydelegate.h"
#include <QDebug>
#include <QApplication>
#include <QMouseEvent>

//获得CheckBox控件的位置大小
QRect checkBoxRect(const QStyleOptionViewItem &viewItemStyleOptions)/*const*/
{
   //QStyleOptionButton checkBoxStyleOption;
   //QApplication::style()->subElementRect()可以按照给定的风格参数 返回元素子区域(这里就是获得Qt标准QCheckBox的大小?)
   //QRect rectCheckBox = QApplication::style()->subElementRect( QStyle::SE_CheckBoxIndicator,
                                                               //&checkBoxStyleOption);

   //QCheckBox大小
   QSize checkBoxSize(13/*rectCheckBox.width()*/, 13/*rectCheckBox.height()*/);

   //QCheckBox坐标
   QPoint checkBoxPoint(viewItemStyleOptions.rect.x() + viewItemStyleOptions.rect.width() / 2 - checkBoxSize.width() / 2,
                        viewItemStyleOptions.rect.y() + viewItemStyleOptions.rect.height() / 2 - checkBoxSize.height() / 2);
   //返回QCheckBox几何形状
   return QRect(checkBoxPoint, checkBoxSize);
}

//进行控件的显示绘制,根据模型数据显示控件的状态
void CheckBoxDelegate::paint(QPainter* painter,const QStyleOptionViewItem& option,const QModelIndex& index)const
{
    if(index.column() == 0)
    {
        bool checked = index.model()->data(index, Qt::CheckStateRole).toBool();
        qDebug() << checked;
        QStyleOptionButton checkBoxStyleOption;
        checkBoxStyleOption.state |= QStyle::State_Enabled;
        checkBoxStyleOption.state |= checked? QStyle::State_On : QStyle::State_Off;
        checkBoxStyleOption.rect = checkBoxRect(option);

        QApplication::style()->drawControl(QStyle::CE_CheckBox, &checkBoxStyleOption, painter);
    }
    else if (index.column() == 1)
    {
        int progress = index.data().toInt();

        QStyleOptionProgressBar progressBarOption;
        progressBarOption.rect = option.rect;
        progressBarOption.minimum = 0;
        progressBarOption.maximum = 100;
        progressBarOption.progress = progress;
        progressBarOption.text = QString::number(progress) + "%";
        progressBarOption.textVisible = true;

        QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter);
    }
    else
    {
        QStyledItemDelegate::paint(painter, option, index);
    }
}

//处理控件事件,更新模型中数据,返回true表示已经处理了该事件,返回false表示尚未处理事件
bool CheckBoxDelegate::editorEvent(QEvent *event,QAbstractItemModel *model,const QStyleOptionViewItem &option,
                 const QModelIndex &index)
{
    if(index.column() == 0)
    {
        if(event->type() == QEvent::MouseButtonRelease)
        {
            QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
            if(mouseEvent->button() != Qt::LeftButton || !checkBoxRect(option).contains(mouseEvent->pos()))
            {
                //非鼠标左键的释放 || 鼠标释放不在CheckBox区域内
                return true;
            }
        }
        else if(event->type() == QEvent::MouseButtonDblClick)
        {
            //鼠标双击
            return true;
        }
        else if(event->type() == QEvent::KeyPress)
        {
            if(static_cast<QKeyEvent*>(event)->key() != Qt::Key_Space &&
                    static_cast<QKeyEvent*>(event)->key() != Qt::Key_Select)
            {
                //非space键盘事件
                return false;
            }
        }
        else
        {
            return false;
        }

        //鼠标左键释放、space键盘事件
        bool checked = index.model()->data(index, Qt::CheckStateRole).toBool();
        return model->setData(index, !checked, Qt::CheckStateRole);
    }
    else if(index.column() == 1)
    {
        return true;
    }
    else
    {
        return QStyledItemDelegate::editorEvent(event, model, option, index);
    }
}
View Code
#include <QApplication>
#include <QStandardItemModel>
#include <QTableView>
#include "mydelegate.h"

int main(int argc, char**argv)
{
    QApplication app(argc, argv);

    QTableView tb;

    QStandardItemModel *md = new QStandardItemModel(/*&tb*/);
    md->setRowCount(5);
    md->setColumnCount(5);
    for(int i = 0; i<4; i++)
    {
        md->setData(md->index(1,i+1), QString("50"), Qt::EditRole);
    }
    md->setData(md->index(1, 0), true, Qt::CheckStateRole);
    tb.setModel(md);

    CheckBoxDelegate *ck = new CheckBoxDelegate(/*&tb*/);
    tb.setItemDelegate(ck);
    tb.show();

    return app.exec();
}
View Code

  

5、标准部件

  QListWidget、QTableWidget、QTreeWidget是标准部件,它们继承自对应的标准视图类,不能和任意的模型一起使用,没有视图类灵活,适合少量数据的显示,也不建议子类化它们。下面分别是它们的使用示例:

#include <QApplication>
#include <QDebug>
#include <QListWidget>
#include <QTableWidget>
#include <QTreeWidget>

int main(int argc, char**argv)
{
    QApplication app(argc, argv);

    QListWidget list;

    //一种向List部件添加项目的方法
    new QListWidgetItem("a", &list);

    //另一种向List部件添加项目的方法
    QListWidgetItem* listWidgetItem = new QListWidgetItem;
    listWidgetItem->setText("b");
    listWidgetItem->setIcon(QIcon("../modelView/yaf.png"));
    listWidgetItem->setToolTip("this is b");
    list.insertItem(1, listWidgetItem);

    new QListWidgetItem("c", &list);

    //设置按照项目文本倒序显示
    list.sortItems(Qt::DescendingOrder);

    list.show();

    //listWidgetItem->setHidden(true); //隐藏该项目

    return app.exec();
}
View Code
    QTreeWidget tree;

    //必须设置参数
    tree.setColumnCount(2);

    //设置表头
    QStringList headers;
    headers << "name" << "year";
    tree.setHeaderLabels(headers);

    //添加项目
    QTreeWidgetItem* grade1 = new QTreeWidgetItem(&tree);
    grade1->setText(0, "grade1");
    QTreeWidgetItem* student = new QTreeWidgetItem(grade1);
    student->setText(0, "Tom");
    student->setText(1, "1986");

    QTreeWidgetItem* grade2 = new QTreeWidgetItem(&tree, grade1);
    grade2->setText(0, "grade2");

    tree.show();

    //删除(插入)当前项目
    QTreeWidgetItem* current = tree.currentItem();
    QTreeWidgetItem* parent = current->parent();
    QTreeWidgetItem* newItem;
    int idx = 0;
    if(parent){//非顶层项目的删除(插入)
        idx = parent->indexOfChild(current);
        delete parent->takeChild(idx); //删除当前项目

        //newItem = new QTreeWidgetItem(parent, current); //向当前项目之后插入新的项目
    }else{ //顶层项目的删除(插入)
        idx = tree.indexOfTopLevelItem(current);
        delete tree.takeTopLevelItem(idx); //删除当前项目

        //newItem = new QTreeWidgetItem(&tree, current); //向当前项目之后插入新的项目
    }
View Code
    //创建表格部件并指定行数和列数
    QTableWidget table(3, 2);

    //创建表格项目,插入到指定单元
    QTableWidgetItem* tableWidgetItem = new QTableWidgetItem("Qt");
    table.setItem(1, 1, tableWidgetItem);

    //创建表格项目,将它们作为标头
    QTableWidgetItem* headH = new QTableWidgetItem("ID");
    table.setHorizontalHeaderItem(0, headH);
    QTableWidgetItem* headV = new QTableWidgetItem("first");
    table.setVerticalHeaderItem(0, headV);

    table.show();
View Code

 

6、视图启用拖放

  如下所示是在上面标准部件QListWidget示例中添加的代码,实现了列表项目之间的拖放移动操作:

    list.setSelectionMode(QAbstractItemView::SingleSelection); //设置选择模式为单选
    list.setDragEnabled(true); //启用拖动
    list.viewport()->setAcceptDrops(true); //接受拖放
    list.setDropIndicatorShown(true); //显示将要被放置的位置(被放置的位置所在的项目上方会有一条黑边)
    list.setDragDropMode(QAbstractItemView::InternalMove); //拖放模式为移动项目,默认为复制项目
View Code

  下面是上面的示例程序,拖动a到b的位置,a和b会互换:

  下面是在模型/视图架构中使用拖放操作的示例(该示例是在上面“模型”部分的示例程序基础上添加的代码),因为视图中显示的数据是由模型控制的,所以还要为使用的模型提供拖放操作的支持,如下所示向StringListModel中添加的supportedDropActions()、mimeTypes()、mimeData()重写方法都是项目开始拖动的时候被调用,而重写的dropMimeData()则是项目拖动结束的时候被调用该方法,还要调整原来的flags()方法来添加支持拖动和放入标志,最后还要像前面的QListWidget标准部件一样,添加视图的拖放属性设置。

  运行示例,将项目拖动到指定位置的时候会替换掉原来的项目(其文本),而如果是拖动多个项目的话则是会选择第一个选中的项目来将目的项目替换,我们也可以在dropMimeData()方法中调用insertRows()、removeRow()等方法插入、删除项目来实现拖放的其它效果。也可以参考Qt中Item View分类下的Puzzle示例程序。

//设置支持使用拖放进行复制和移动
Qt::DropActions StringListModel::supportedDropActions() const
{
    return Qt::CopyAction | Qt::MoveAction;
}

//设置在拖放操作中数据项被从模型导出时的编码格式,来对应一个或者多个MIME类型
QStringList StringListModel::mimeTypes() const
{
    QStringList types;
    //application/vnd.text.list是自定义的类型,仅支持纯文本类型,在后面的函数中要保持一致
    types << "application/vnd.text.list";
    return types;
}

//使用自定义的格式将所有拖拽的数据放入一个QMimeData中
QMimeData *StringListModel::mimeData(const QModelIndexList &indexes) const
{
    //获得所有被拖拽的数据到QByteArray
    QByteArray encodedData;
    QDataStream stream(&encodedData, QIODevice::WriteOnly);
    foreach(const QModelIndex &index, indexes){
        if(index.isValid()){
            QString text = data(index, Qt::DisplayRole).toString(); //获得项目显示的文本
            stream << text;
        }
    }

    //将数据放入到QMimeData中
    QMimeData* mimeData = new QMimeData();
    mimeData->setData("application/vnd.text.list", encodedData);
    return mimeData;
}

//将拖放的数据放入模型中
bool StringListModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
                          int row, int column, const QModelIndex &parent)
{
    //如果放入动作是Qt::IgnoreAction那么返回true
    if(action == Qt::IgnoreAction)
        return true;
    //如果数据的格式不是指定的格式,返回false
    if(!data->hasFormat("application/vnd.text.list"))
        return false;
    //这里是只用了一列的列表,如果列大于0返回false
    if(column > 0)
        return false;

    //设置开始插入的行
    int beginRow;
    if(row != -1) //拖动的目的位置为在一个项目之前或之后的位置插入
        beginRow = row;
    else if(parent.isValid()) //拖动目的位置为一个已经存在项目的位置(因为是列表而不是树,所以拖到已存在项目上为-1)
        beginRow = parent.row();
    else //拖动的目的位置超出项目位置
        beginRow = rowCount(QModelIndex());

    //将数据从QMimeData中读取出来
    QByteArray encodedData = data->data("application/vnd.text.list");
    QDataStream stream(&encodedData, QIODevice::ReadOnly);
    QStringList newItems;
    int rows = 0;
    while(!stream.atEnd()){ //如果选中了多个项目进行拖动的话就会有多条数据,而且数据的顺序是选中时候的顺序
        QString text;
        stream >> text;
        newItems << text;
        ++rows;
    }

    //将数据插入到模型中
    foreach(const QString &text, newItems){
        QModelIndex idx = index(beginRow, 0, QModelIndex());
        setData(idx, text);
        beginRow++;
    }

    return true;
}

//模型通过flags()方法提供合适的标志来向视图表明哪些项目可以被拖拽,哪些项目可以接受放入
Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
    //如果该索引无效,只支持放入操作
    if(!index.isValid())
        return Qt::ItemIsEnabled | Qt::ItemIsDropEnabled;

    //如果该索引有效,那么既支持拖拽操作,也支持放入操作
    return  QAbstractItemModel::flags(index) | Qt::ItemIsEditable
                | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
}
View Code
#include <QApplication>
#include <QTreeView>
#include <QListView>
#include <QFileSystemModel>
#include <QStandardItemModel>
#include "stringlistmodel.h"
#include <QTableView>

int main(int argc, char**argv)
{
    QApplication app(argc, argv);

    ......

    QListView listView; //列表视图

    listView.setSelectionMode(QAbstractItemView::ExtendedSelection);
    listView.setDragEnabled(true);
    listView.setAcceptDrops(true);
    listView.setDropIndicatorShown(true);

    listView.setModel(&model);
    listView.show();

    ......

    return app.exec();
}
View Code

  

7、自绘

  ①、如下的QListView通过使用子类化的代理实现了列表项的自绘,通过使用自定义的样式表实现了滚动条的自绘,如果想要只显示一个垂直滚动条的话可以注释掉padding-top和padding-bottom属性,并将add-line和sub-line的宽高设为0:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QListView>
#include <QStringListModel>
#include <QPainter>

#define LIST_WIDTH 150
#define LIST_HEIGHT 120
#define LIST_ITEM_HEIGHT 30

class MyDelegate : public QAbstractItemDelegate
{
public:
    virtual void paint(QPainter *painter,
                       const QStyleOptionViewItem &option,
                       const QModelIndex &index) const
    {
        auto r = option.rect;
        painter->fillRect(option.rect, index.row() & 1 ? Qt::green : Qt::red);

        painter->setPen(Qt::blue);
        painter->drawText(option.rect.x(), option.rect.y() + option.rect.height() - 1, index.data().toString());
    }
    virtual QSize sizeHint(const QStyleOptionViewItem &option,
                           const QModelIndex &index) const
    {
        return {LIST_WIDTH, LIST_ITEM_HEIGHT};
    }
};


MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);


    QStringList list;
    list << "yafei" << "yafeilinux" << "Qt" << "Qt Creator" << "test";
    QStringListModel* listModel = new QStringListModel(list, this);

    m_listView = new QListView(this);
    m_listView->setGeometry(QRect(10, 50, LIST_WIDTH, LIST_HEIGHT));
    m_listView->setModel(listModel);
    m_listView->setStyleSheet("QListView{border: 2px solid red;"
                              "border-top-width: 1px;"
                              "border-bottom-color: transparent;"
                              "border-radius:6px; "
                              "background: grey}");

    m_listView2 = new QListView(this);
    m_listView2->setGeometry(QRect(170, 50, LIST_WIDTH, LIST_HEIGHT));
    m_listView2->setModel(listModel);
    m_listView2->setFrameShape(QFrame::NoFrame);
    m_listView2->setStyleSheet(
    //垂直滚动条
    "QScrollBar:vertical{" //垂直状态
        "background: blue;"
        "width:15px;"
        "padding-top:20px;" //为上箭头区域留出20的距离
        "padding-bottom:20px;" //为下箭头区域留出20的距离
    "}"

    "QScrollBar::handle:vertical{" //滑块子控件
        "background: #FFFFFF;"
        "width:15px;"
        "border-radius:5px;"
        "min-height:20;}"

    "QScrollBar::handle:vertical:hover{" //鼠标悬浮时滑块
        "background:rgba(255,255,255,80%);"
        "width:15px;"
        "border-radius:5px;"
        "min-height:20;}"

    "QScrollBar::add-line:vertical{" //下箭头区域子控件
        "background: #32CC99;"
        "subcontrol-position:bottom;"
        "height:20px;"
        "width:15px;}"

    "QScrollBar::add-line:vertical:pressed{" //下箭头区域被点击的时候
        "background:url(:/image/aa.png);"
        "subcontrol-position:bottom;"
        "height:20px;"
        "width:15px;}"

    "QScrollBar::sub-line:vertical{" //上箭头区域子控件
        "background: #32CC99;"
        "subcontrol-position:top;"
        "height:20px;"
        "width:15px;}"

        "QScrollBar::up-arrow:vertical{" //上箭头子控件
                "border:0px;"
                    "width:4px;"
                   " height:4px;"
                   "border-image:url(:/image/arrow.png);"
        "}"

    "QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {" //上、下箭头与滑块之间区域子部件
        "background: blue;}"


     //水平滚动条
     "QScrollBar:horizontal {"
        "background-color: blue;"
        "border: 2px solid grey;"
        "height: 15px;"
        "margin: 0px 20px 0px 20px;}" //为左、右箭头区域留出20的距离

     "QScrollBar::handle:horizontal {"
         "background: white;"
         "min-width: 20px;}"

     "QScrollBar::add-line:horizontal {"
         "border: 2px solid grey;"
         "background: #32CC99;"
         "width: 20px;"
         "subcontrol-position: right;"
         "subcontrol-origin: margin;}"

     "QScrollBar::sub-line:horizontal {"
         "border: 2px solid grey;"
         "background: #32CC99;"
         "width: 20px;"
         "subcontrol-position: left;"
         "subcontrol-origin: margin;}"

     "QScrollBar::add-page:horizontal,QScrollBar::sub-page:horizontal{"
        "background:rgba(0,0,255,100%);}");

    MyDelegate* del = new MyDelegate;
    m_listView2->setItemDelegate(del);
}

MainWindow::~MainWindow()
{
    delete ui;
}
View Code

  

  ②、如下的QTableView实现了自绘并制定第一列为排序列,当点击第一列的时候仅第一列的数据进行排序,需要注意的是在设置QTableView的Item的样式表的时候"hover"状态要在"selected"状态之前,否则鼠标移动到选中的Item时其背景颜色会变成鼠标悬浮时的颜色:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QTableView>
#include <QStandardItemModel>
#include <QStandardItem>
#include <QHeaderView>
#include <QPainter>
#include <QDebug>
#include <QStringListModel>
#include <QSortFilterProxyModel>
#include <vector>

#define TABLE_SORT_COLUM 0
#define TABLE_ROW_HEIGHT 30
#define TABLE_COLUMN_WIDTH  80
#define TABLE_COLUMN_CNT  3
#define TABLE_ROW_CNT     4
#define TABLE_WIDTH (TABLE_COLUMN_CNT * TABLE_COLUMN_WIDTH)
#define TABLE_HEIGHT ((TABLE_ROW_CNT + 1) * TABLE_ROW_HEIGHT)

class MySortModel: public QSortFilterProxyModel
{
public:
    MySortModel(QObject *parent = 0):QSortFilterProxyModel(parent){}
    void sort(int column, Qt::SortOrder order)override
    {
        //只对一列的数据进行排序
        std::vector<QString> vc;
        for(int i = 0; i < rowCount(); ++i)
        {
            vc.push_back(index(i, column).data().toString());
        }

        std::sort(vc.begin(), vc.end(), [order](const QString& s1, const QString& s2){
            return order == Qt::SortOrder::AscendingOrder ? s1 < s2 : s1 >= s2;
        });

        for(int i = 0; i < vc.size(); ++i)
        {
            setData(index(i, column), vc[i]);
        }
    }
};

void MainWindow::listViewSortIndicatorChanged(int logicalIndex, Qt::SortOrder eSort)
{
    //只支持一列排序
    if(TABLE_SORT_COLUM != logicalIndex)
    {
        m_bFirstColumSort = false;
        m_tableView->horizontalHeader()->setSortIndicator(TABLE_SORT_COLUM, eSort);
    }
    else {
        if(!m_bFirstColumSort)
        {
            m_bFirstColumSort = true;
            return;
        }

        static bool b = true;
        m_sortModel->sort(TABLE_SORT_COLUM, b ? Qt::SortOrder::AscendingOrder : Qt::SortOrder::DescendingOrder);
        b = ! b;
    }
}

void MainWindow::horizontalHeaderPressed(int logicalIndex)
{
    //取消点击列表头选择该列所有cell
    QItemSelectionModel* selectionModel = m_tableView->selectionModel();
    QModelIndexList list = selectionModel->selectedIndexes();
    if(list.empty())
       return;

    selectionModel->select(selectionModel->selection(), QItemSelectionModel::Deselect);
}

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QStandardItemModel* model = new QStandardItemModel(TABLE_ROW_CNT, TABLE_COLUMN_CNT, this);
    for(size_t row = 0; row < TABLE_ROW_CNT; ++row)
    {
        for(size_t column = 0; column < TABLE_COLUMN_CNT; ++column)
        {
            QString str = QString("%1").arg(column + row * TABLE_COLUMN_CNT);
            QStandardItem* item = new QStandardItem(str);
            item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter); //文本居中
            //item->setEditable(false);//不可编辑
            model->setItem((int)row, (int)column, item);
        }
    }

    m_sortModel = new MySortModel(this);
    m_sortModel->setSourceModel(model);

    m_tableView = new QTableView(this);
    m_tableView->setFrameShape(QFrame::NoFrame);
    m_tableView->setGeometry(QRect(20, 30, TABLE_WIDTH, TABLE_HEIGHT));
    m_tableView->setModel(m_sortModel);
    m_tableView->setEditTriggers(QAbstractItemView::NoEditTriggers); //不可编辑
    m_tableView->setStyleSheet("QTableView{"
                               "background-color: white;"
                               "font : 15px \"宋体\";"
                               "outline: 0;" //不显示当前项目的边框
                               "gridline-color: grey;}" 

                               "QTableView::item:hover{color:red;background-color: rgba(255, 0, 0, 10%);}"

                               "QTableView::item:selected{color:red; background-color:green;}"

                               /*"QTableView::item:focus {background-color: blue}"*/
                               );
    //m_tableView->setGridStyle(Qt::PenStyle::DotLine); //设置网格线为虚线

    QHeaderView* pHHeader = m_tableView->horizontalHeader();
    QHeaderView* pVHeader = m_tableView->verticalHeader();
    connect(pHHeader, SIGNAL(sortIndicatorChanged(int , Qt::SortOrder)),
                     this, SLOT(listViewSortIndicatorChanged(int , Qt::SortOrder)));
    connect(pHHeader, SIGNAL(sectionPressed(int)), this, SLOT(horizontalHeaderPressed(int)));
    pVHeader->hide();
    pVHeader->setDefaultSectionSize(TABLE_ROW_HEIGHT); //行高
    pHHeader->setDefaultSectionSize(TABLE_COLUMN_WIDTH); //列宽
    pHHeader->setSortIndicatorShown(true);
    pHHeader->setSortIndicator(TABLE_SORT_COLUM, Qt::SortOrder::AscendingOrder);
    pHHeader->setStyleSheet(
                    "QHeaderView::section { "
                        "height: 30px;" //表头高
                        "background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1," //渐变色背景
                                                           "stop:0 #616161, stop: 0.5 #505050,"
                                                           "stop: 0.6 #434343, stop:1 #656565);"
                        "font : 15px \"宋体\";"
                        "color:white;"
                        "padding-left: 40px;"
                        "border: solid black;"
                        "border-width: 0px 1px 0px 0px;}"

                    "QHeaderView::section:checked{"
                         "height: 30px;"
                         "background-color: red;"
                         "font : bold 15px \"宋体\";}"

                    "QHeaderView::down-arrow {"
                         "image: url(:/image/arrow_down.png); position:absolute; right: 5px}"

                     "QHeaderView::up-arrow {"
                         "image: url(:/image/arrow_up.png); }"
                );


    QStandardItemModel *m=new QStandardItemModel();
    m->setHorizontalHeaderLabels({"a", "b", "c"});
    pHHeader->setModel(m);
}

MainWindow::~MainWindow()
{
    delete ui;
}
View Code

  

  下面的代码对上面进行了修改,使之点击后选中整行数据,并且点击第一列进行排序后整行数据会相对的进行排序:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QTableView>
#include <QStandardItemModel>
#include <QStandardItem>
#include <QHeaderView>
#include <QPainter>
#include <QDebug>
#include <QStringListModel>
#include <QSortFilterProxyModel>
#include <vector>

#define TABLE_SORT_COLUM 0
#define TABLE_ROW_HEIGHT 30
#define TABLE_COLUMN_WIDTH  80
#define TABLE_COLUMN_CNT  3
#define TABLE_ROW_CNT     4
#define TABLE_WIDTH (TABLE_COLUMN_CNT * TABLE_COLUMN_WIDTH + 2)
#define TABLE_HEIGHT ((TABLE_ROW_CNT + 1) * TABLE_ROW_HEIGHT + 2)

class MySortModel: public QSortFilterProxyModel
{
public:
    MySortModel(QObject *parent = 0):QSortFilterProxyModel(parent){}
    void sort(int column, Qt::SortOrder order)override
    {
        //根据排序列对整行的数据进行排序
        std::vector<std::vector<QString>> vc;
        for(int i = 0; i < rowCount(); ++i)
        {
            std::vector<QString> v;
            for(int j = 0; j < columnCount(); ++j)
            {
                v.push_back(index(i, j).data().toString());
            }
            vc.push_back(v);
        }

        std::sort(vc.begin(), vc.end(), [order, column](const std::vector<QString>& v1, const std::vector<QString>& v2){
            return order == Qt::SortOrder::AscendingOrder ? v1[column] < v2[column] : v1[column] >= v2[column];
        });

        for(int i = 0; i < vc.size(); ++i)
        {
            for(int j = 0; j < vc[i].size(); ++j)
            {
                setData(index(i, j), vc[i][j]);
            }
        }
    }
};

void MainWindow::listViewSortIndicatorChanged(int logicalIndex, Qt::SortOrder eSort)
{
    //只支持一列排序
    if(TABLE_SORT_COLUM != logicalIndex)
    {
        m_bFirstColumSort = false;
        m_tableView->horizontalHeader()->setSortIndicator(TABLE_SORT_COLUM, eSort);
    }
    else {
        if(!m_bFirstColumSort)
        {
            m_bFirstColumSort = true;
            return;
        }

        static bool b = true;
        m_sortModel->sort(TABLE_SORT_COLUM, b ? Qt::SortOrder::AscendingOrder : Qt::SortOrder::DescendingOrder);
        b = ! b;
    }
}

void MainWindow::horizontalHeaderPressed(int logicalIndex)
{
    //取消点击列表头选择该列所有cell
    QItemSelectionModel* selectionModel = m_tableView->selectionModel();
    QModelIndexList list = selectionModel->selectedIndexes();
    if(list.empty())
       return;

    selectionModel->select(selectionModel->selection(), QItemSelectionModel::Deselect);
}

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QStandardItemModel* model = new QStandardItemModel(TABLE_ROW_CNT, TABLE_COLUMN_CNT, this);
    for(size_t row = 0; row < TABLE_ROW_CNT; ++row)
    {
        for(size_t column = 0; column < TABLE_COLUMN_CNT; ++column)
        {
            QString str = QString("%1").arg(column + row * TABLE_COLUMN_CNT);
            QStandardItem* item = new QStandardItem(str);
            item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter); //文本居中
            //item->setEditable(false);//不可编辑
            model->setItem((int)row, (int)column, item);
        }
    }

    m_sortModel = new MySortModel(this);
    m_sortModel->setSourceModel(model);

    m_tableView = new QTableView(this);
    //m_tableView->setFrameShape(QFrame::NoFrame);
    m_tableView->setGeometry(QRect(20, 30, TABLE_WIDTH, TABLE_HEIGHT));
    m_tableView->setModel(m_sortModel);
    m_tableView->setEditTriggers(QAbstractItemView::NoEditTriggers); //不可编辑
    m_tableView->setStyleSheet("QTableView{"
                               "background-color: white;"
                               "font : 15px \"宋体\";"
                               "outline: 0;" //当前项目无边框显示
                               "gridline-color: grey;}"

                               "QTableView{border: 1px solid grey;}"

                               "QTableView::item{border-top: 1px solid grey;}"

                               "QTableView::item:selected{color:red; background-color:green;}"
                               );
    m_tableView->setShowGrid(false); //不显示网格线
    m_tableView->setSelectionBehavior(QTableView::SelectionBehavior::SelectRows); //选择整行

    QHeaderView* pHHeader = m_tableView->horizontalHeader();
    QHeaderView* pVHeader = m_tableView->verticalHeader();
    connect(pHHeader, SIGNAL(sortIndicatorChanged(int , Qt::SortOrder)),
                     this, SLOT(listViewSortIndicatorChanged(int , Qt::SortOrder)));
    connect(pHHeader, SIGNAL(sectionPressed(int)), this, SLOT(horizontalHeaderPressed(int)));
    pVHeader->hide();
    pVHeader->setDefaultSectionSize(TABLE_ROW_HEIGHT); //行高
    pHHeader->setDefaultSectionSize(TABLE_COLUMN_WIDTH); //列宽
    pHHeader->setSortIndicatorShown(true);
    pHHeader->setSortIndicator(TABLE_SORT_COLUM, Qt::SortOrder::AscendingOrder);
    pHHeader->setStyleSheet(
                    "QHeaderView::section { "
                        "height: 30px;" //表头高
                        "background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1," //渐变色背景
                                                           "stop:0 #616161, stop: 0.5 #505050,"
                                                           "stop: 0.6 #434343, stop:1 #656565);"
                        "font : 15px \"宋体\";"
                        "color:white;"
                        "padding-left: 40px;"
                        "border: solid black;"
                        "border-width: 0px;}"

                    "QHeaderView::section:checked{"
                         "height: 30px;"
                         "background-color: red;"
                         "font : bold 15px \"宋体\";}"

                    "QHeaderView::down-arrow {"
                         "image: url(:/image/arrow_down.png); position:absolute; right: 5px}"

                     "QHeaderView::up-arrow {"
                         "image: url(:/image/arrow_up.png); }"
                );


    QStandardItemModel *m=new QStandardItemModel();
    m->setHorizontalHeaderLabels({"a", "b", "c"});
    pHHeader->setModel(m);
}

MainWindow::~MainWindow()
{
    delete ui;
}
View Code

 

  ③、如下实现了QListBox的item使用自定义的QWidget来显示列表内容:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QStringListModel>
#include <QListView>
#include <QPainter>
#include <QRect>
#include <QPushButton>

#define LIST_WIDTH 150
#define LIST_HEIGHT 120
#define LIST_ITEM_HEIGHT 30
#define LLIST_ITEM_COUNT 4

class MyWidget: public QWidget
{
public:
    explicit MyWidget(QString str, QWidget* parent = 0, Qt::WindowFlags f = 0): QWidget(parent, f), m_str(str)
    {
        setAutoFillBackground(true);

        m_pBtn = new QPushButton(m_str, this);
        m_pBtn->setStyleSheet("QPushButton{border : 0px; background-color:blue}");
    }
    void paintEvent(QPaintEvent *paintEvent)override
    {
        QPainter painter(this);
        painter.fillRect(rect(), m_bMouseEnter ? QColor(255, 0, 0, 100) : Qt::red);

        painter.setPen(Qt::black);
        painter.drawLine(rect().bottomLeft(), rect().bottomRight());
    }
    void resizeEvent(QResizeEvent *)override
    {
        if(m_pBtn)
            m_pBtn->setGeometry(4, 4, 60, 20);
    }
    void enterEvent(QEvent *)override
    {
        m_bMouseEnter = true;
        repaint();
    }
    void leaveEvent(QEvent *)override
    {
        m_bMouseEnter = false;
        repaint();
    }
private:
    QPushButton* m_pBtn{nullptr};
    QString m_str;
    bool m_bMouseEnter{false};
};

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QListView* listView = new QListView(this);

    QStringListModel* listModel = new QStringListModel(listView);
    listModel->insertRows(0, LLIST_ITEM_COUNT);
    listView->setModel(listModel);

    listView->setFrameShape(QFrame::NoFrame);
    listView->setStyleSheet("QListView{background: grey} QListView::item{height:30px}");
    listView->show();
    listView->setGeometry(QRect(10, 50, LIST_WIDTH, LIST_HEIGHT));

    for(int i = 0; i < LLIST_ITEM_COUNT; ++i)
    {
        MyWidget* w = new MyWidget(QString::number(i, 10), listView);
        listView->setIndexWidget(listModel->index(i, 0), w);
    }
}

MainWindow::~MainWindow()
{
    delete ui;
}
View Code

   

  ④、下面使用自定义的委托实现了对列表中项目的自定义绘制:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QStringListModel>
#include <QListView>
#include <QPainter>
#include <QStyledItemDelegate>
#include <QDebug>

#define LIST_WIDTH 200
#define LIST_ITEM_HEIGHT 40
#define LLIST_ITEM_COUNT 6

class MyPaintDelegate: public QStyledItemDelegate
{
public:
    explicit MyPaintDelegate(QObject *parent = 0):QStyledItemDelegate(parent)
    {
        m_img.load("./image.png");
    }
    void paint(QPainter* painter,const QStyleOptionViewItem& option,
                                 const QModelIndex& index)const
    {
        QColor clr;
        if(option.state & QStyle::State_Selected) //item选中颜色
            clr = Qt::lightGray;
        else if(option.state & QStyle::State_MouseOver) //item鼠标悬浮颜色
            clr = QColor(255, 0, 0, 100);
        else //普通item背景色
            clr = index.row() & 1 ? Qt::red : QColor(255, 0, 0, 180);
        painter->fillRect(option.rect, clr);

        if(0 == index.row())
        {
            //绘制图片
            painter->drawPixmap(4, option.rect.y() + (option.rect.height() - m_img.height()) / 2, m_img);
        }

        painter->drawText(option.rect, Qt::AlignCenter, index.data().toString());
    }
    bool editorEvent(QEvent *event,QAbstractItemModel *model,
                                       const QStyleOptionViewItem &option, const QModelIndex &index)
    {
        //if(event->type() == QEvent::MouseButtonDblClick)
            //return true;


        return QStyledItemDelegate::editorEvent(event, model, option, index);
    }
private:
    QPixmap m_img;
};

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QListView* listView = new QListView(this);

    QStringListModel* listModel = new QStringListModel({"1", "2", "3", "4", "5", "6"}, listView);
    listView->setModel(listModel);

    listView->setEditTriggers(QAbstractItemView::NoEditTriggers); //项目不可编辑
    listView->setFrameShape(QFrame::NoFrame);
    listView->setStyleSheet("QListView{background: grey} QListView::item{height:40px}");
    listView->show();
    listView->setGeometry(QRect(50, 30, LIST_WIDTH, LIST_ITEM_HEIGHT * LLIST_ITEM_COUNT));

    MyPaintDelegate *ck = new MyPaintDelegate(listView);
    listView->setItemDelegate(ck);
    listView->show();
}

MainWindow::~MainWindow()
{
    delete ui;
}
View Code

   

  ⑤、下面使用样式表对QTreeView进行了自绘:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QStandardItemModel>
#include <QTreeView>

#define LIST_WIDTH 200
#define LIST_ITEM_HEIGHT 40
#define LLIST_ITEM_COUNT 6


MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);



    QTreeView* tree = new QTreeView(this);

    QStandardItemModel* model = new QStandardItemModel(tree);
    QStandardItem* rootItem = model->invisibleRootItem(); //获取模型的根项,根项是不可见的

    QPixmap pixmap0(50, 50);
    pixmap0.fill("red");
    QPixmap pixmap2(50, 50);
    pixmap2.fill("green");

    QStandardItem* item0 = new QStandardItem;
    item0->setText("A");
    item0->setIcon(QIcon(pixmap0));
    rootItem->appendRow(item0);

    QStandardItem* item1 = new QStandardItem;
    item1->setText("B");
    item1->setIcon(QIcon(pixmap0));
    rootItem->appendRow(item1);

    QStandardItem* subItem0 = new QStandardItem;
    subItem0->setData("a", Qt::EditRole);
    subItem0->setData(QIcon(pixmap2), Qt::DecorationRole);
    item1->appendRow(subItem0);

    QStandardItem* subItem1 = new QStandardItem;
    subItem1->setData("b", Qt::EditRole);
    subItem1->setData(QIcon(pixmap2), Qt::DecorationRole);
    item1->appendRow(subItem1);

    tree->setModel(model);
    tree->setIndentation(20); //展开/缩进按钮
    tree->setExpanded(model->index(1, 0, QModelIndex()), true); //展开指定项目
    tree->setHeaderHidden(true); //隐藏表头
    tree->show();
    tree->setGeometry(30, 30, 200, 200);
    tree->setFrameShape(QFrame::NoFrame);
    tree->setStyleSheet(" QTreeView {"
                        "background-color : lightgrey;"
                        "outline: 0;" //不显示当前项目边框
                        " show-decoration-selected: 1;}" //鼠标悬浮的时候左边的分枝也高亮显示

                        "QTreeView::item{background:lightgrey; height : 30}" //不加此行的话下面的icon和text不起作用

                        "QTreeView::icon{left: 5px;}" //图标右移5px

                        "QTreeView::text{left: 5px;}" //文本右移5px

                        "QTreeView::item:hover {" //鼠标悬停
                             "background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #e7effd, stop:1 #cbdaf1);}"

                         "QTreeView::item:selected:active{" //当前项目&&选中项目
                             "background: red; border: 0px;}"

                         "QTreeView::item:selected:!active {" //非当前项目&&选中项目
                             "background: blue; border: 0px;}"

                         "QTreeView::branch {" //展开/缩进区域
                             "background: grey;}"

                        "QTreeView::branch:adjoins-item:closed {" //关闭状态
                             "background: black;}"

                         "QTreeView::branch:has-children:adjoins-item:closed {" //有子项时的关闭状态
                              "background: blue;}"

                         "QTreeView::branch:has-children:adjoins-item:open {" //有子项时的打开状态
                              "background: yellow;}"

                        "QTreeView::branch:!has-children:adjoins-item:open {" //无子项时的打开状态(无子项时默认为关闭状态)
                             "background: black;"
                         "}"
                    );
}

MainWindow::~MainWindow()
{
    delete ui;
}
View Code

      

posted on 2020-09-29 17:33  整鬼专家  阅读(1152)  评论(0编辑  收藏  举报