Qt八股-04

模型视图架构知识列表

  1. Qt 的模型视图架构(MVC)是什么?它的组成部分有哪些?
  2. 什么是 QAbstractItemModel?如何实现一个自定义模型?
  3. Qt 中的 QListViewQTableView 有什么区别?
  4. 如何使用 QStandardItemModel
  5. 如何实现一个自定义视图?
  6. 什么是代理(Delegate)?如何使用?
  7. 如何实现模型与视图的分离?
  8. 如何使用 QSortFilterProxyModel 进行数据过滤和排序?
  9. 如何处理模型中的数据更改?
  10. 如何实现多列排序?
  11. 如何使用 QAbstractListModelQAbstractTableModel
  12. 如何在模型中实现数据的增删改查?
  13. 如何使用 QItemSelectionModel 进行选择管理?
  14. 如何实现自定义的模型视图交互?
  15. 如何使用 QItemDelegate 自定义视图项的显示?

1. Qt 的模型视图架构(MVC)是什么?它的组成部分有哪些?

模型-视图-控制器(MVC)架构是一种设计模式,旨在分离应用程序的数据(模型)与用户界面(视图),从而提高程序的可维护性和可扩展性。Qt 的模型视图架构主要由以下三个部分组成:

  • 模型(Model)
    • 负责存储和管理数据。
    • 提供数据访问、修改和通知改变的接口。
    • Qt 提供了多种模型类,如 QAbstractItemModelQStandardItemModelQAbstractListModelQAbstractTableModel
  • 视图(View)
    • 负责显示模型中的数据。
    • 通过模型提供的数据更新自己的显示。
    • Qt 提供了多种视图类,如 QListViewQTableViewQTreeView 等。
  • 代理(Delegate)
    • 用于定制视图项的显示和编辑行为。
    • 代理可以控制单个视图项的外观和编辑方式,而不需要修改模型。
    • 常用的代理类有 QItemDelegateQStyledItemDelegate

2. 什么是 QAbstractItemModel?如何实现一个自定义模型?

QAbstractItemModel 是所有模型的基类,提供了模型的基本接口。要实现一个自定义模型,通常需要继承 QAbstractItemModel,并实现以下关键方法:

  • rowCount(const QModelIndex &parent):返回指定父项的子项数量。
  • columnCount(const QModelIndex &parent):返回指定父项的列数。
  • data(const QModelIndex &index, int role):返回指定索引的数据,role 用于指定数据的类型(如显示、编辑等)。
  • setData(const QModelIndex &index, const QVariant &value, int role):设置指定索引的数据。
  • index(int row, int column, const QModelIndex &parent):根据行和列返回模型索引。
  • parent(const QModelIndex &index):返回指定索引的父索引。

示例代码:

class MyModel : public QAbstractItemModel {
   Q_OBJECT
public:
   MyModel(QObject *parent = nullptr) : QAbstractItemModel(parent) {
       // 初始化数据
  }

   int rowCount(const QModelIndex &parent = QModelIndex()) const override {
       // 返回行数
  }

   int columnCount(const QModelIndex &parent = QModelIndex()) const override {
       // 返回列数
  }

   QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override {
       // 返回索引
  }

   QModelIndex parent(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 {
       // 设置数据
       emit dataChanged(index, index); // 通知视图数据已更改
       return true;
  }
};

3. Qt 中的 QListViewQTableView 有什么区别?

  • QListView
    • 用于显示一维数据,通常以列表形式呈现。
    • 每个项可以是单行或多行文本,可以通过设置代理来自定义显示。
    • 通常适用于简单的列表数据展示。
  • QTableView
    • 用于显示二维数据,以表格形式呈现。
    • 每个单元格可以单独控制内容和样式。
    • 支持多列排序和筛选,适合用于复杂数据展示。

4. 如何使用 QStandardItemModel

QStandardItemModel 是 Qt 提供的一个标准数据模型,适用于简单的树形或表格数据。使用 QStandardItemModel 非常简单,通常的步骤如下:

  1. 创建模型实例。
  2. 设置表头标签(可选)。
  3. 添加数据项。
  4. 将模型与视图关联。

示例代码:

QStandardItemModel *model = new QStandardItemModel();
model->setHorizontalHeaderLabels(QStringList() << "Name" << "Age");

QStandardItem *item1 = new QStandardItem("Alice");
QStandardItem *item2 = new QStandardItem("30");
model->appendRow(QList<QStandardItem*>() << item1 << item2);

QTableView *view = new QTableView();
view->setModel(model);

5. 如何实现一个自定义视图?

要实现自定义视图,可以继承现有的视图类(如 QListViewQTableView),并重写其方法来实现自定义的绘制或交互逻辑。

示例代码:

class MyCustomView : public QListView {
   Q_OBJECT
protected:
   void paintEvent(QPaintEvent *event) override {
       QListView::paintEvent(event); // 调用基类的绘制方法
       // 自定义绘制逻辑
  }

   void mousePressEvent(QMouseEvent *event) override {
       // 自定义鼠标点击处理
       QListView::mousePressEvent(event); // 调用基类处理
  }
};

6. 什么是代理(Delegate)?如何使用?

代理(Delegate)用于定制视图项的显示和编辑行为。通过实现代理,您可以控制视图中每个单元格的外观和编辑方式,而无需更改模型。

使用代理的步骤如下:

  1. 继承 QStyledItemDelegateQItemDelegate
  2. 重写 paint() 方法以自定义绘制逻辑。
  3. 重写 createEditor() 方法以提供自定义编辑器。
  4. 在视图中设置代理。

示例代码:

class MyDelegate : public QStyledItemDelegate {
public:
   void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
       // 自定义绘制逻辑
       painter->drawText(option.rect, Qt::AlignLeft, index.data().toString());
  }

   QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
       // 返回自定义编辑器
       QLineEdit *editor = new QLineEdit(parent);
       return editor;
  }
};

// 使用代理
QTableView *view = new QTableView();
view->setItemDelegate(new MyDelegate());

7. 如何实现模型与视图的分离?

模型与视图的分离通过使用 MVC 模式来实现。具体来说:

  • 确保视图只通过模型的公共接口访问数据,而不直接操作数据。
  • 当模型中的数据发生变化时,模型应发出信号(如 dataChanged()),以通知视图更新。
  • 视图根据模型的状态更新自己的显示。

这种分离使得模型和视图可以独立开发和维护,提高了代码的可复用性。

8. 如何使用 QSortFilterProxyModel 进行数据过滤和排序?

QSortFilterProxyModel 是一个代理模型,允许对数据进行排序和过滤。使用步骤如下:

  1. 创建 QSortFilterProxyModel 实例。
  2. 设置源模型。
  3. 使用 setFilterRegExp() 方法设置过滤条件。
  4. 使用 sort() 方法对数据进行排序。

示例代码:

QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel();
proxyModel->setSourceModel(sourceModel);
proxyModel->setFilterRegExp(QRegExp("filterText", Qt::CaseInsensitive)); // 设置过滤条件
proxyModel->sort(0); // 根据第一列排序

QTableView *view = new QTableView();
view->setModel(proxyModel);

9. 如何处理模型中的数据更改?

在模型中处理数据更改的关键在于发出相应的信号,以通知视图更新。常用的信号包括:

  • dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight):当数据更改时发出。
  • rowsInserted(const QModelIndex &parent, int first, int last):当插入行时发出。
  • rowsRemoved(const QModelIndex &parent, int first, int last):当删除行时发出。

示例代码:

bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) {
   if (index.isValid() && role == Qt::EditRole) {
       // 更新数据
       dataList[index.row()] = value; // 假设 dataList 是存储数据的容器
       emit dataChanged(index, index); // 通知视图数据已更改
       return true;
  }
   return false;
}

10. 如何实现多列排序?

要实现多列排序,可以使用 QSortFilterProxyModel,并重写 lessThan() 方法来自定义排序逻辑。您可以根据需要对多个列进行排序。

示例代码:

class MySortFilterProxyModel : public QSortFilterProxyModel {
protected:
   bool lessThan(const QModelIndex &left, const QModelIndex &right) const override {
       // 自定义多列排序逻辑
       QVariant leftData = left.data();
       QVariant rightData = right.data();

       // 根据需要比较不同的列
       if (left.column() == 0) {
           return leftData.toString() < rightData.toString(); // 第一列按字符串排序
      } else {
           return leftData.toInt() < rightData.toInt(); // 其他列按整数排序
      }
  }
};

11. 如何使用 QAbstractListModelQAbstractTableModel

  • QAbstractListModel:用于实现一维数据模型,适合于 QListView。实现时需要重写 rowCount()data() 方法。

示例代码:

class MyListModel : public QAbstractListModel {
   Q_OBJECT
public:
   MyListModel(QObject *parent = nullptr) : QAbstractListModel(parent) {
       // 初始化数据
  }

   int rowCount(const QModelIndex &parent = QModelIndex()) const override {
       return dataList.size(); // 返回数据项数量
  }

   QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
       if (index.isValid() && role == Qt::DisplayRole) {
           return dataList.at(index.row()); // 返回数据
      }
       return QVariant();
  }
};
  • QAbstractTableModel:用于实现二维数据模型,适合于 QTableView。实现时需要重写 rowCount()columnCount()data() 方法。

示例代码:

class MyTableModel : public QAbstractTableModel {
   Q_OBJECT
public:
   MyTableModel(QObject *parent = nullptr) : QAbstractTableModel(parent) {
       // 初始化数据
  }

   int rowCount(const QModelIndex &parent = QModelIndex()) const override {
       return dataList.size(); // 返回行数
  }

   int columnCount(const QModelIndex &parent = QModelIndex()) const override {
       return columnCount; // 返回列数
  }

   QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
       if (index.isValid() && role == Qt::DisplayRole) {
           return dataList.at(index.row()).at(index.column()); // 返回数据
      }
       return QVariant();
  }
};

12. 如何在模型中实现数据的增删改查?

在自定义模型中实现增删改查的方法通常包括:

  • insertRows(int row, int count, const QModelIndex &parent):插入行。
  • removeRows(int row, int count, const QModelIndex &parent):删除行。
  • setData(const QModelIndex &index, const QVariant &value, int role):更新数据。
  • data(const QModelIndex &index, int role):获取数据。

示例代码:

bool MyModel::insertRows(int row, int count, const QModelIndex &parent) {
   beginInsertRows(parent, row, row + count - 1);
   // 插入数据逻辑
   endInsertRows();
   return true;
}

bool MyModel::removeRows(int row, int count, const QModelIndex &parent) {
   beginRemoveRows(parent, row, row + count - 1);
   // 删除数据逻辑
   endRemoveRows();
   return true;
}

13. 如何使用 QItemSelectionModel 进行选择管理?

QItemSelectionModel 用于管理视图中选中的项。可以通过模型与视图的 setSelectionModel() 方法来设置选择模型。选择模型提供了选中、取消选中、获取选择状态等功能。

示例代码:

QItemSelectionModel *selectionModel = new QItemSelectionModel(model);
view->setSelectionModel(selectionModel);

// 获取选中的索引
QModelIndexList selectedIndexes = selectionModel->selectedIndexes();

14. 如何实现自定义的模型视图交互?

要实现自定义的模型视图交互,可以通过重写视图的事件处理函数(如 mousePressEvent()keyPressEvent() 等)来实现自定义的用户交互逻辑。可以根据用户的输入更新模型数据,并发出相应的信号以通知视图更新。

示例代码:

class MyCustomView : public QListView {
   Q_OBJECT
protected:
   void mousePressEvent(QMouseEvent *event) override {
       QModelIndex index = indexAt(event->pos());
       if (index.isValid()) {
           // 自定义交互逻辑,例如编辑数据
           emit itemClicked(index);
      }
       QListView::mousePressEvent(event); // 调用基类处理
  }
};

15. 如何使用 QItemDelegate 自定义视图项的显示?

QItemDelegate 可以用来定制视图项的显示和编辑。通过实现代理,您可以控制视图中每个单元格的外观和编辑方式,而无需更改模型。

使用步骤如下:

  1. 继承 QItemDelegateQStyledItemDelegate
  2. 重写 paint() 方法以自定义绘制逻辑。
  3. 重写 createEditor() 方法以提供自定义编辑器。
  4. 在视图中设置代理。

示例代码:

class MyItemDelegate : public QItemDelegate {
public:
   void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
       // 自定义绘制逻辑
       painter->drawText(option.rect, Qt::AlignLeft, index.data().toString());
  }

   QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
       // 返回自定义编辑器
       QLineEdit *editor = new QLineEdit(parent);
       return editor;
  }
};

// 使用代理
QTableView *view = new QTableView();
view->setItemDelegate(new MyItemDelegate());