Qt开发总结(15)——Model/View架构使用

本篇是上篇Qt开发总结(14)——Model/View 框架的延续,从这篇总结可以很具体的实现一些Model/View控件和效果,大部分都是干货,在最后还给出了官方的一些示例供学习参考。

一个简单的只读表

  //main.cpp
  #include <QtWidgets/QApplication>
  #include <QtWidgets/QTableView>
  #include "mymodel.h"

  int main(int argc, char *argv[])
  {
      QApplication a(argc, argv);
      QTableView tableView;
      MyModel myModel(0);
      tableView.setModel( &myModel );
      tableView.show();
      return a.exec();
  }

我们从上面的代码看到,一个表格的显示只需要为它set model,表格的数据显示变成了莫model的编写。Model可以用一些现有的model,比如上面提到的QStandardItemModel,这里我们新建一个自己的model,并用一个新建类MyModel实现它。

  // mymodel.h
  #include <QAbstractTableModel>

  class MyModel : public QAbstractTableModel
  {
      Q_OBJECT
  public:
      MyModel(QObject *parent);
      int rowCount(const QModelIndex &parent = QModelIndex()) const override ;
      int columnCount(const QModelIndex &parent = QModelIndex()) const override;
      QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
  };

 

这里继承QAbstractTableModel抽象基类创建一个MyModel类,该类要实现3个虚函数:

 

  // mymodel.cpp
  #include "mymodel.h"

  MyModel::MyModel(QObject *parent)
      :QAbstractTableModel(parent)
  {
  }

  int MyModel::rowCount(const QModelIndex & /*parent*/) const
  {
     return 2;
  }

  int MyModel::columnCount(const QModelIndex & /*parent*/) const
  {
      return 3;
  }

  QVariant MyModel::data(const QModelIndex &index, int role) const
  {
      if (role == Qt::DisplayRole)
      {
         return QString("Row%1, Column%2")
                     .arg(index.row() + 1)
                     .arg(index.column() +1);
      }
      return QVariant();
  }

 

设置基本属性

行和列的数量由MyModel :: rowCount()和MyModel :: columnCount()提供。 当视图必须知道单元格的文本是什么时,它会调用方法MyModel :: data()。 使用参数索引指定行和列信息,并将角色设置为Qt :: DisplayRole。 在我们的示例中,生成了应显示的数据。 在实际的应用程序中,MyModel将有一个名为MyData的成员,它作为所有读写操作的目标。

除了控制视图显示的文本外,模型还控制文本的外观。事实上,除了data()方法外,不需要更改任何内容来设置字体、背景颜色、对齐方式和复选框。下面是产生上面所示结果的data()方法。不同的是,这次我们使用参数int role根据其值返回不同的信息片段。

 

  // mymodel.cpp
  QVariant MyModel::data(const QModelIndex &index, int role) const
  {
      int row = index.row();
      int col = index.column();
      // generate a log message when this method gets called
      qDebug() << QString("row %1, col%2, role %3")
              .arg(row).arg(col).arg(role);

      switch(role){
      case Qt::DisplayRole:
          if (row == 0 && col == 1) return QString("<--left");
          if (row == 1 && col == 1) return QString("right-->");

          return QString("Row%1, Column%2")
                  .arg(row + 1)
                  .arg(col +1);
          break;
      case Qt::FontRole:
          if (row == 0 && col == 0) //change font only for cell(0,0)
          {
              QFont boldFont;
              boldFont.setBold(true);
              return boldFont;
          }
          break;
      case Qt::BackgroundRole:

          if (row == 1 && col == 2)  //change background only for cell(1,2)
          {
              QBrush redBackground(Qt::red);
              return redBackground;
          }
          break;
      case Qt::TextAlignmentRole:

          if (row == 1 && col == 1) //change text alignment only for cell(1,1)
          {
              return Qt::AlignRight + Qt::AlignVCenter;
          }
          break;
      case Qt::CheckStateRole:

          if (row == 1 && col == 0) //add a checkbox to cell(1,0)
          {
              return Qt::Checked;
          }
      }
      return QVariant();
  }

 

每个格式化属性都将通过对data()方法的单独调用从模型中请求。角色参数用于让模型知道正在请求哪个属性:

enum Qt::ItemDataRole

Meaning

Type

Qt::DisplayRole

文本

QString

Qt::FontRole

字体

QFont

BackgroundRole

单元格北京画笔

QBrush

Qt::TextAlignmentRole

对其方式

enum Qt::AlignmentFlag

Qt::CheckStateRole

复选框

enum Qt::ItemDataRole

更新数据显示

这个小例子展示了模型的被动性。 该模型不知道何时使用或需要哪些数据。 它只是在每次视图请求时提供数据。当模型的数据需要更改时会发生什么? 视图如何实现数据已更改并需要再次读取? 该模型必须发出一个信号,指示哪些单元格范围已经改变。 

//we identify the top left cell
QModelIndex topLeft = createIndex(0,0);
//emit a signal to make the view reread identified data
emit dataChanged(topLeft, topLeft);

 

 

我们要求视图通过发出dataChanged()信号再次读取左上角单元格中的数据。注意,我们没有显式地将dataChanged()信号连接到视图。这在我们调用setModel()时自动发生。

设置标题

可以通过该方法隐藏标题:tableView->verticalHeader()->hide();如果要显示标题,则需要重写headerdata()方法。

 

  QVariant MyModel::headerData(int section, Qt::Orientation orientation, int role) const
  {
      if (role == Qt::DisplayRole)
      {
          if (orientation == Qt::Horizontal) {
              switch (section)
              {
              case 0:
                  return QString("first");
              case 1:
                  return QString("second");
              case 2:
                  return QString("third");
              }
          }
      }
      return QVariant();
  }

 

表格内容编辑

模型决定了编辑功能是否可用。我们只需要修改模型就可以启用可用的编辑功能。这是通过重新实现以下虚函数来完成的:setdata()和flags()。

 

  bool MyModel::setData(const QModelIndex & index, const QVariant & value, int role)
  {
      if (role == Qt::EditRole)
      {
          //save value from editor to member m_gridData
          m_gridData[index.row()][index.column()] = value.toString();
          //for presentation purposes only: build and emit a joined string
          QString result;
          for (int row= 0; row < ROWS; row++)
          {
              for(int col= 0; col < COLS; col++)
              {
                  result += m_gridData[row][col] + ' ';
              }
          }
          emit editCompleted( result );
      }
      return true;
  }

 

每次用户编辑单元格时都会调用setdata()。index参数告诉我们哪个字段已被编辑,value提供编辑过程的结果。角色将始终设置为qt::editRole,因为我们的单元格只包含文本。如果存在复选框,并且用户权限设置为允许选中该复选框,则还将使用设置为qt::checkStateRole的角色进行调用。我们使用二维数组qstring m_griddata来存储数据。这使得m_griddata成为mymodel的核心。MyModel的其余部分就像一个包装器,将m_GridData适配到QabstratemModel接口。我们还引入了editCompleted()信号,这使得将修改后的文本传输到窗口标题成为可能。

可以使用flags()调整单元格的各种属性。返回Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled足以显示可以选择单元格的编辑器。如果编辑一个单元格修改的数据多于该特定单元格中的数据,则模型必须发出dataChanged()信号,以便读取已更改的数据。

建立树形表

QListView, QTableView和QTreeView使用列表、表和树的抽象模型。这使得可以使用同一模型中的几种不同类型的视图类。

建立一个树形表,我们使用QStandardItemModel,它是一个层次数据容器,也实现了QAbstractItemModel。若要显示树,QStandardItemModel必须填充 QStandardItems,它能够保存文本、字体、复选框或画笔等项的所有标准属性。

    

treeView = new QTreeView(this);
standardModel = new QStandardItemModel ;
QList<QStandardItem *> preparedRow =prepareRow("first", "second", "third");
QStandardItem *item = standardModel->invisibleRootItem();
// adding a row to the invisible root item produces a root element
item->appendRow(preparedRow);	
QList<QStandardItem *> secondRow =prepareRow("111", "222", "333");
// adding a row to an item starts a subtree
preparedRow.first()->appendRow(secondRow);
treeView->setModel(standardModel);
treeView->expandAll();

 

其中prepareRow是自己写的一个返回QStandardItem对象的接口函数:

 

  QList<QStandardItem *> MainWindow::prepareRow(const QString &first,
                                                  const QString &second,
                                                  const QString &third)
  {
      QList<QStandardItem *> rowItems;
      rowItems << new QStandardItem(first);
      rowItems << new QStandardItem(second);
      rowItems << new QStandardItem(third);
      return rowItems;
  }

 

树形表检索

视图管理单独选择模型中的选项,可以使用selectionModel()方法检索。 我们检索选择模型,以便将槽函数连接到其selectionChanged()信号。

  

  void MainWindow::selectionChangedSlot(const QItemSelection & /*newSelection*/, const QItemSelection & /*oldSelection*/)
  {
      //get the text of the selected item
      const QModelIndex index = treeView->selectionModel()->currentIndex();
      QString selectedText = index.data(Qt::DisplayRole).toString();
      //find out the hierarchy level of the selected item
      int hierarchyLevel=1;
      QModelIndex seekRoot = index;
      while(seekRoot.parent() != QModelIndex())
      {
          seekRoot = seekRoot.parent();
          hierarchyLevel++;
      }
      QString showString = QString("%1, Level %2").arg(selectedText)
                           .arg(hierarchyLevel);
      setWindowTitle(showString);
  }

 

  我们通过调用treeView->selectionModel()->currentIndex()获得与选择相对应的模型索引,并通过使用模型索引获得字段的字符串。然后我们只计算项目的层次级别。顶级项没有父级,parent()方法将返回默认构造的QModelIndex()。这就是为什么我们使用parent()方法迭代到顶层,同时计算迭代期间执行的步骤。

代理

在目前为止的所有示例中,数据在单元格中显示为文本或复选框,并作为文本或复选框进行编辑。 提供这些演示和编辑服务的组件称为代理。 我们才刚刚开始使用代理,因为视图使用默认代理。 但是想象一下,我们想要一个不同的编辑器(例如,滑块或下拉列表)或想象我们想要将数据呈现为图形。 让我们来看一个名为Star Delegate的例子,其中星星用于显示评级:

视图有一个setItemDelegat()方法,该方法替换默认代理并安装自定义代理。可以通过创建继承自QStyledItemDelegate的类来编写新代理。为了编写一个显示星型并且没有输入功能的代理,我们只需要重写2个方法。

 

  class StarDelegate : public QStyledItemDelegate
  {
      Q_OBJECT
  public:
      StarDelegate(QWidget *parent = 0);
      void paint(QPainter *painter, const QStyleOptionViewItem &option,
                 const QModelIndex &index) const;
      QSize sizeHint(const QStyleOptionViewItem &option,
                     const QModelIndex &index) const;
  };

 

 paint() 根据基础数据的内容绘制星号。可以通过调用index.data()来查找数据。代理的sizeHint()方法用于获取每个星的尺寸,因此单元将提供足够的高度和宽度来容纳星。如果要在视图类的网格中使用自定义图形表示来显示数据,则编写自定义代理是正确的选择。如果您想离开网格,您将不会使用自定义代理,而是使用自定义视图类。

源码示例

请移步本人下载频道下载典型应用实例:editabletreemodel https://download.csdn.net/download/bjtuwayne/12017958

发布了76 篇原创文章 · 获赞 63 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/bjtuwayne/article/details/103481217
今日推荐