一、基本概念
在MV架构中,视图包含了模型中的数据项,并将它们呈现给用户。数据项的表示方法,可能和数据项在存储时用的数据结构完全不同。
这种内容与表现分离之所以能够实现,是因为使用了
QAbstractItemModel
提供的一个标准模型接口;- 一个标准视图接口;
- 模型索引所提供的一种通用的方法;
来表示数据。
视图通常管理从模型获取数据的整体布局。视图可以自己渲染独立的数据项,也可以使用委托来处理渲染和编辑。
1. 项目导航和选择行为
除了呈现数据,视图还处理项目间的导航,以及项目选择的某些方面。
表1和表2分别罗列了视图中的选择行为(QAbstractItemView::SelectionBehaviour
)和选择模式(QAbstractItemView::SelectionMode
)
表1 视图类的选择行为(QAbstractItemView::SelectionBehaviour
)
常量 | 描述 |
---|---|
QAbstractItemView::SelectItems | 选择单个项目 |
QAbstractItemView::SelectRows | 只选择行 |
QAbstractItemView::SelectColumns | 只选择列 |
表2 视图类的选择模式
常量 | 描述 |
---|---|
QAbstractItemView::SingleSelection | 当用户选择一个项目,则索所有已选择的项目将成为未选择态,而且用户无法在已经选择的项目上单击来取消选择。 |
QAbstractItemView::ContiguousSelection | 如果用户在单击一个项目的同时按着Shift 键,则所有在当前和单击项目之间的项目都将被选择或者取消选择,这依赖于被单击项目的状态。 |
QAbstractItemView::ExtendedSelection | 具有ConiguousSelection 的特性,而且还可以按着Ctrl 键进行不连续的选择。 |
QAbstractItemView::MultiSelection | 用户选择一个项目时,不影响其他已经选择的项目。 |
QAbstractItemView::NoSelection | 项目无法被选择。 |
对于一些视图,例如QTreeView
和QTreeView
,在显示项目的同时还可以显示表头。这是通过QHeaderView
类来实现的,它们使用QAbstractItemModel::headerData()
从模型中获取数据,然后一般使用一个标签来显示表头信息。可以通过子类化QHeaderView
来设置标签的显示。
Qt中已经提供了QListView
,QTableView
和QTreeView
这三个现成的视图,不过都是使用规范的格式显示数据。
如果想要实现条形图、饼状图等特殊显示方式,需要重新实现视图。
二、项目选择
MV架构对项目的选择提供了非常方便的处理方法。
视图中被选择的项目的信息,存储在一个QItemSelectionModel
实例中,这样被选择的项目模型索引便保持在一个独立的模型中,与所有的视图都是独立的。
当在一个模型上设置多个视图时,就可以实现在多个视图之间共享选择。
选择由选择范围指定,只需要记录每一个选择范围开始和结束的模型索引即可,非连续的选择可以使用多个选择范围来描述。
选择可以看作是在选择模型中保存的一个模型索引集合,最近的项目选择被称为当前选择。
1. 当前项目、被选择项目
视图中总是有一个当前项目和一个被选择的项目,两者是独立的状态。
在同一时间,一个项目可以既是当前项目,同时也是被选择项目。视图负责确保总是有一个项目作为当前项目来实现键盘导航。
表3 当前项目和被选择的项目的区别
当前项目 | 被选择的项目 |
---|---|
只能有一个当前项目 | 被选择的项目 |
使用键盘导航键或者鼠标按键可以改变当前项目 | 项目是否处于被选择状态,取决于几个预先定义好的模式,例如单项选择、多重选择等。 |
如果按下F2键或者双击都可以编辑当前项目 | 当前项目可以通过指定一个范围来一起被使用 |
当前项目会显示焦点矩形 | 被选择的项目会使用选择矩形来表示 |
当操作选择时,可以将QItemnSelectionModel
看作一个项目模型中所有项目的选择状态的一个记录。
一旦设置了一个选择模型,所有的项目集合都可以被选择、取消选择或者切换选择状态,而不需要知道哪一个项目已经被选择了。所有被选择项目的索引都可以被随时进行检索,其他的组件也可以通过信号和槽机制来获取选择模型的改变信息。
2. 选择模型
标准的视图类中提供了默认的选择模型,可以在大多数的应用中直接使用。
属于一个视图的选择模型可以使用这个视图的selectionModel()
函数获得,而且还可以在多个视图之间使用setSelectionModel()
函数来共享该选择模型,所以一般是不需要重新构建一个选择模型的。
三、代码实例
实现两个视图共享数据模型和选择模型。
MainWindow.h
#pragma once
#include <QMainWindow>
class QTableView;
class QItemSelection;
class QModelIndex;
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override;
public slots:
void getCurrentItemData();
void toggleSelection();
void updateSelection(const QItemSelection &selected, const QItemSelection &deselected);
void changeCurrent(const QModelIndex ¤t, const QModelIndex &previous);
private:
Ui::MainWindow *ui;
QTableView* tableView;
QTableView* tableView2;
};
MainWindow.cpp
#include "mainwindow.h"
#include "ui_MainWindow.h"
#include <QStandardItemModel>
#include <QTableView>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent), ui(new Ui::MainWindow) {
ui->setupUi(this);
auto model = new QStandardItemModel(7, 4, this);
for (int row = 0; row < 7; ++row) {
for (int column = 0; column < 4; ++column) {
auto item = new QStandardItem(QString("%1").arg(row * 4 + column));
model->setItem(row, column, item);
}
}
tableView = new QTableView;
tableView->setModel(model);
setCentralWidget(tableView);
QItemSelectionModel* selectionModel = tableView->selectionModel();
QModelIndex topLeft;
QModelIndex bottomRight;
topLeft = model->index(1,1,QModelIndex());
bottomRight = model->index(5,2,QModelIndex());
QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Toggle);
ui->menubar->addAction(tr("当前项目"), this, &MainWindow::getCurrentItemData);
ui->menubar->addAction(tr("切换选择"), this, &MainWindow::toggleSelection);
connect(selectionModel, &QItemSelectionModel::selectionChanged,this, &MainWindow::updateSelection);
connect(selectionModel, &QItemSelectionModel::currentChanged,this,&MainWindow::changeCurrent);
{
tableView2 = new QTableView;
tableView2->setWindowTitle("tableView2");
tableView2->resize(400,300);
tableView2->setModel(tableView->model());
tableView2->setSelectionModel(tableView->selectionModel());
tableView2->show();
}
}
MainWindow::~MainWindow() {
delete ui;
delete tableView2;
}
void
MainWindow::getCurrentItemData()
{
qDebug() << tr("当前项目内容")
<< tableView->selectionModel()->currentIndex().data().toString();
}
void
MainWindow::toggleSelection()
{
QModelIndex topLeft = tableView->model()->index(0,0,QModelIndex());
QModelIndex bottomRight = tableView->model()->index(
tableView->model()->rowCount(QModelIndex()) - 1,
tableView->model()->columnCount(QModelIndex()) - 1,
QModelIndex());
QItemSelection curSelection(topLeft, bottomRight);
tableView->selectionModel()->select(curSelection, QItemSelectionModel::Toggle);
}
void
MainWindow::updateSelection(const QItemSelection &selected, const QItemSelection &deselected)
{
QModelIndex index;
QModelIndexList list = selected.indexes();
foreach(index, list)
{
QString text = QString("%1,%2").arg(index.row()).arg(index.column());
tableView->model()->setData(index, text);
}
list = deselected.indexes();
foreach(index, list)
{
tableView->model()->setData(index, "");
}
}
void
MainWindow::changeCurrent(const QModelIndex ¤t, const QModelIndex &previous)
{
qDebug() << tr("move(%1,%2) to (%3,%4)").
arg(previous.row()).arg(previous.column()).
arg(current.row()).arg(current.column());
}
参考资料: Qt Creator快速入门第2版 (霍亚飞 著)