深入理解模型视图、自定义模型
2017-06-30 11:27
771 查看
一、深入理解模型
在 model/view 架构中,model 提供一种标准接口,供视图和委托访问数据。在 Qt 中,这个接口由QAbstractItemModel类进行定义。不管底层数据是如何存储的,只要是QAbstractItemModel的子类,都提供一种表格形式的层次结构。视图利用统一的转换来访问模型中的数据。但是,需要提供的是,尽管模型内部是这样组织数据的,但是并不要求也得这样子向用户展示数据。
下面是各种 model 的组织示意图。我们利用此图来理解什么叫“一种表格形式的层次结构”。
如上图所示,List Model 虽然是线性的列表,也有一个 Root Item(根节点),之下才是呈线性的一个个数据,而这些数据实际可以看作是一个只有一列的表格,但是它是有层次的,因为有一个根节点。Table Model 就比较容易理解,只是也存在一个根节点。Tree Model 主要面向层次数据,而每一层次都可以都很多列,因此也是一个带有层次的表格。
为了能够使得数据的显示同存储分离,我们引入模型索引(model index)的概念。通过索引,我们可以访问模型的特定元素的特定部分。视图和委托使用索引来请求所需要的数据。由此可以看出,只有模型自己需要知道如何获得数据,模型所管理的数据类型可以使用通用的方式进行定义。索引保存有创建的它的那个模型的指针,这使得同时操作多个模型成为可能。
QAbstractItemModel *model = index.model();
模型索引提供了所需要的信息的临时索引,可以用于通过模型取回或者修改数据。由于模型随时可能重新组织其内部的结构,因此模型索引很可能变成不可用的,此时,就不应该保存这些数据。如果你需要长期有效的数据片段,必须创建持久索引。持久索引保证其引用的数据及时更新。临时索引(也就是通常使用的索引)由
为了定位模型中的数据,我们需要三个属性:行号、列号以及父索引。下面我们对其一一进行解释。
我们前面介绍过模型的基本形式:数据以二维表的形式进行存储。此时,一个数据可以由行号和列号进行定位。注意,我们仅仅是使用“二维表”这个名词,并不意味着模型内部真的是以二维数组的形式进行存储;所谓“行号”“列号”,也仅仅是为方便描述这种对应关系,并不真的是有行列之分。通过指定行号和列号,我们可以定位一个元素项,取出其信息。此时,我们获得的是一个索引对象(回忆一下,通过索引我们可以获取具体信息):
QModelIndex index = model->index(row, column, ...);
模型提供了一个简单的接口,用于列表以及表格这种非层次视图的数据获取。不过,正如上面的代码暗示的那样,实际接口并不是那么简单。我们可以通过文档查看这个函数的原型:
QModelIndex QAbstractItemModel::index(int row,int column,const QModelIndex &parent=QModelIndex()) const
这里,我们仅仅使用了前两个参数。通过下图来理解一下:
在一个简单的表格中,每一个项都可以由行号和列号确定。因此,我们只需提供两个参数即可获取到表格中的某一个数据项:
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexB = model->index(1, 1, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
函数的最后一个参数始终是 QModelIndex(),接下来我们就要讨论这个参数的含义。
在类似表格的视图中,比如列表和表格,行号和列号足以定位一个数据项。但是,对于树型结构,仅有两个参数就不足够了。这是因为树型结构是一个层次结构,而层次结构中每一个节点都有可能是另外一个表格。所以,每一个项需要指明其父节点。前面说过,在模型外部只能用过索引访问内部数据,因此,
parent 参数:
QModelIndex index = model->index(row, column, parent);
图中,A 和 C 都是模型中的顶级项:
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
A 还有自己的子项。那么,我们就应该使用下面的代码获取 B 的索引:
QModelIndex indexB = model->index(1, 0, indexA);
由此我们看到,如果只有行号和列号两个参数,B 的行号是 1,列号是 0,这同与 A 同级的行号是 1,列号是 0 的项相同,所以我们通过 parent 属性区别开来。
以上我们讨论了有关索引的定位。现在我们来看看模型的另外一个部分:数据角色。模型可以针对不同的组件(或者组件的不同部分,比如按钮的提示以及显示的文本等)提供不同的数据。例如,
我们可以通过指定索引以及角色来获得模型所提供的数据:
QVariant value = model->data(index, role);
通过为每一个角色提供恰当的数据,模型可以告诉视图和委托如何向用户显示内容。不同类型的视图可以选择忽略自己不需要的数据。当然,我们也可以添加我们所需要的额外数据。
总结一下:
模型使用索引来提供给视图和委托有关数据项的位置的信息,这样做的好处是,模型之外的对象无需知道底层的数据存储方式;
数据项通过行号、列号以及父项三个坐标进行定位;
模型索引由模型在其它组件(视图和委托)请求时才会被创建;
如果使用
角色用于区分数据项的不同类型的数据。
另外
模型的数目信息可以通过
索引用于访问模型中的数据。我们需要利用行号、列号以及父项三个参数来获得该索引;
当我们使用
数据项包含了不同角色的数据。为获取特定角色的数据,必须指定这个角色。
二、深入理解视图
前面我们介绍了模型的概念。下面则是另外一个基本元素:视图。在 model/view 架构中,视图是数据从模型到最终用户的途径。数据通过视图向用户进行显示。此时,这种显示方式不必须同模型的存储结构相一致。实际上,很多情况下,数据的显示同底层数据的存储是完全不同的。
我们使用
视图不仅仅用于展示数据,还用于在数据项之间的导航以及数据项的选择。另外,视图也需要支持很多基本的用户界面的特性,例如右键菜单以及拖放。视图可以提供数据编辑功能,也可以将这种编辑功能交由某个委托完成。视图可以脱离模型创建,但是在其进行显示之前,必须存在一个模型。也就是说,视图的显示是完全基于模型的,这是不能脱离模型存在的。对于用户的选择,多个视图可以相互独立,也可以进行共享。
某些视图,例如
三、自定义模型
model/view 模型将数据与视图分割开来,也就是说,我们可以为不同的视图,
类似
在开始自定义模型之前,我们首先需要思考这样一个问题:我们的数据结构适合于哪种视图的显示方式?是列表,还是表格,还是树?如果我们的数据仅仅用于列表或表格的显示,那么
现在,我们开始自定义一个模型。这个例子修改自《C++ GUI Programming with Qt4, 2nd Edition》。首先描述一下需求。我们想要实现的是一个货币汇率表,就像银行营业厅墙上挂着的那种电子公告牌。当然,你可以选择
100 种货币的汇率(我们把自己对自己的汇率也包含在内,只不过这个汇率永远是 1.0000)。现在,按照我们的设计,这张表要有 100 x 100 = 10000 个数据项。我们希望减少存储空间,有没有更好的方式?于是我们想,如果我们的数据不是直接向用户显示的数据,而是这种货币相对于美元的汇率,那么其它货币的汇率都可以根据这个汇率计算出来了。比如,我存储人民币相对美元的汇率,日元相对美元的汇率,那么人民币相对日元的汇率只要作一下比就可以得到了。这种数据结构就没有必要存储 10000 个数据项,只要存储 100
个就够了(实际情况中这可能是不现实的,因为两次运算会带来更大的误差,但这不在我们现在的考虑范畴中)。
于是我们设计了
double 是不精确的,不过这一点显然不在我们的考虑中。)
在 model/view 架构中,model 提供一种标准接口,供视图和委托访问数据。在 Qt 中,这个接口由QAbstractItemModel类进行定义。不管底层数据是如何存储的,只要是QAbstractItemModel的子类,都提供一种表格形式的层次结构。视图利用统一的转换来访问模型中的数据。但是,需要提供的是,尽管模型内部是这样组织数据的,但是并不要求也得这样子向用户展示数据。
下面是各种 model 的组织示意图。我们利用此图来理解什么叫“一种表格形式的层次结构”。
如上图所示,List Model 虽然是线性的列表,也有一个 Root Item(根节点),之下才是呈线性的一个个数据,而这些数据实际可以看作是一个只有一列的表格,但是它是有层次的,因为有一个根节点。Table Model 就比较容易理解,只是也存在一个根节点。Tree Model 主要面向层次数据,而每一层次都可以都很多列,因此也是一个带有层次的表格。
为了能够使得数据的显示同存储分离,我们引入模型索引(model index)的概念。通过索引,我们可以访问模型的特定元素的特定部分。视图和委托使用索引来请求所需要的数据。由此可以看出,只有模型自己需要知道如何获得数据,模型所管理的数据类型可以使用通用的方式进行定义。索引保存有创建的它的那个模型的指针,这使得同时操作多个模型成为可能。
QAbstractItemModel *model = index.model();
模型索引提供了所需要的信息的临时索引,可以用于通过模型取回或者修改数据。由于模型随时可能重新组织其内部的结构,因此模型索引很可能变成不可用的,此时,就不应该保存这些数据。如果你需要长期有效的数据片段,必须创建持久索引。持久索引保证其引用的数据及时更新。临时索引(也就是通常使用的索引)由
QModelIndex类提供,持久索引则是
QPersistentModelIndex类。
为了定位模型中的数据,我们需要三个属性:行号、列号以及父索引。下面我们对其一一进行解释。
我们前面介绍过模型的基本形式:数据以二维表的形式进行存储。此时,一个数据可以由行号和列号进行定位。注意,我们仅仅是使用“二维表”这个名词,并不意味着模型内部真的是以二维数组的形式进行存储;所谓“行号”“列号”,也仅仅是为方便描述这种对应关系,并不真的是有行列之分。通过指定行号和列号,我们可以定位一个元素项,取出其信息。此时,我们获得的是一个索引对象(回忆一下,通过索引我们可以获取具体信息):
QModelIndex index = model->index(row, column, ...);
模型提供了一个简单的接口,用于列表以及表格这种非层次视图的数据获取。不过,正如上面的代码暗示的那样,实际接口并不是那么简单。我们可以通过文档查看这个函数的原型:
QModelIndex QAbstractItemModel::index(int row,int column,const QModelIndex &parent=QModelIndex()) const
这里,我们仅仅使用了前两个参数。通过下图来理解一下:
在一个简单的表格中,每一个项都可以由行号和列号确定。因此,我们只需提供两个参数即可获取到表格中的某一个数据项:
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexB = model->index(1, 1, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
函数的最后一个参数始终是 QModelIndex(),接下来我们就要讨论这个参数的含义。
在类似表格的视图中,比如列表和表格,行号和列号足以定位一个数据项。但是,对于树型结构,仅有两个参数就不足够了。这是因为树型结构是一个层次结构,而层次结构中每一个节点都有可能是另外一个表格。所以,每一个项需要指明其父节点。前面说过,在模型外部只能用过索引访问内部数据,因此,
index()函数还需要一个
parent 参数:
QModelIndex index = model->index(row, column, parent);
图中,A 和 C 都是模型中的顶级项:
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
A 还有自己的子项。那么,我们就应该使用下面的代码获取 B 的索引:
QModelIndex indexB = model->index(1, 0, indexA);
由此我们看到,如果只有行号和列号两个参数,B 的行号是 1,列号是 0,这同与 A 同级的行号是 1,列号是 0 的项相同,所以我们通过 parent 属性区别开来。
以上我们讨论了有关索引的定位。现在我们来看看模型的另外一个部分:数据角色。模型可以针对不同的组件(或者组件的不同部分,比如按钮的提示以及显示的文本等)提供不同的数据。例如,
Qt::DisplayRole用于视图的文本显示。通常来说,数据项包含一系列不同的数据角色,这些角色定义在
Qt::ItemDataRole枚举中。
我们可以通过指定索引以及角色来获得模型所提供的数据:
QVariant value = model->data(index, role);
通过为每一个角色提供恰当的数据,模型可以告诉视图和委托如何向用户显示内容。不同类型的视图可以选择忽略自己不需要的数据。当然,我们也可以添加我们所需要的额外数据。
总结一下:
模型使用索引来提供给视图和委托有关数据项的位置的信息,这样做的好处是,模型之外的对象无需知道底层的数据存储方式;
数据项通过行号、列号以及父项三个坐标进行定位;
模型索引由模型在其它组件(视图和委托)请求时才会被创建;
如果使用
index()函数请求获得一个父项的可用索引,该索引会指向模型中这个父项下面的数据项。这个索引指向该项的一个子项;如果使用
index()函数请求获得一个父项的不可用索引,该索引指向模型的最顶级项;
角色用于区分数据项的不同类型的数据。
另外
模型的数目信息可以通过
rowCount()和
columnCount()获得。这些函数需要制定父项;
索引用于访问模型中的数据。我们需要利用行号、列号以及父项三个参数来获得该索引;
当我们使用
QModelIndex()创建一个空索引使用时,我们获得的就是模型中最顶级项;
数据项包含了不同角色的数据。为获取特定角色的数据,必须指定这个角色。
二、深入理解视图
前面我们介绍了模型的概念。下面则是另外一个基本元素:视图。在 model/view 架构中,视图是数据从模型到最终用户的途径。数据通过视图向用户进行显示。此时,这种显示方式不必须同模型的存储结构相一致。实际上,很多情况下,数据的显示同底层数据的存储是完全不同的。
我们使用
QAbstractItemModel提供标准的模型接口,使用
QAbstractItemView提供标准的视图接口,而结合这两者,就可以将数据同表现层分离,在视图中利用前面所说的模型索引。视图管理来自模型的数据的布局:既可以直接渲染数据本身,也可以通过委托渲染和编辑数据。
视图不仅仅用于展示数据,还用于在数据项之间的导航以及数据项的选择。另外,视图也需要支持很多基本的用户界面的特性,例如右键菜单以及拖放。视图可以提供数据编辑功能,也可以将这种编辑功能交由某个委托完成。视图可以脱离模型创建,但是在其进行显示之前,必须存在一个模型。也就是说,视图的显示是完全基于模型的,这是不能脱离模型存在的。对于用户的选择,多个视图可以相互独立,也可以进行共享。
某些视图,例如
QTableView和
QTreeView,不仅显示数据,还会显示列头或者表头。这些是由
QHeaderView视图类提供的,表头通常访问视图所包含的同一模型。它们使用
QAbstractItemModel::headerData()函数从模型中获取数据,然后将其以标签 label 的形式显示出来。我们可以通过继承
QHeaderView类,实现某些更特殊的功能。
#include "widget.h" #include <QStringList> #include <QVBoxLayout> #include <QHBoxLayout> #include <QSpinBox> Widget::Widget(QWidget *parent) : QWidget(parent) { this->setWindowTitle("视图和委托"); this->resize(300,300); QStringList data; data<<"0"<<"1"<<"2"; model=new QStringListModel(this); model->setStringList(data); listview=new QListView(this); listview->setModel(model); btm=new QPushButton(tr("show model"),this); connect(btm,&QPushButton::clicked,this,&Widget::showmodel); QHBoxLayout *hl=new QHBoxLayout; hl->addWidget(btm); QVBoxLayout *vl=new QVBoxLayout; vl->addWidget(listview); vl->addLayout(hl); setLayout(vl); //将这个委托设置为QListView所使用的委托 listview->setItemDelegate(new SpinBoxDelegate(listview)); } Widget::~Widget() { } void Widget::showmodel(){ } /* * createEditor()返回一个组件。该组件会被作为用户编辑数据时所使用的编辑器, * 从模型中接受数据,返回用户修改的数据。在createEditor()函数中, * parent 参数会作为新的编辑器的父组件。 */ 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; } /* * setEditorData()函数从模型中获取需要编辑的数据(具有 Qt::EditRole 角色)。由于我们 * 知道它就是一个整型,因此可以放心地调用 toInt()函数。 editor 就是所生成的编辑器实例, * 我们将其强制转换成 QSpinBox 实例,设置其数据作为默认值。 */ 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); } /* * 在用户编辑完数据后,委托会调用setModelData()函数将新的数据保存到模型中。 * 因此,在这里我们首先获取QSpinBox实例,得到用户输入值,然后设置到模型相应的位置。 * 标准的QStyledItemDelegate类会在完成编辑时发出closeEditor()信号,视图会保证编辑 * 器已经关闭,但是并不会销毁,因此需要另外对内存进行管理。由于我们的处理很简单, * 无需发出closeEditor()信号,但是在复杂的实现中,记得可以在这里发出这个信号。 * 针对数据的任何操作都必须提交给QAbstractItemModel,这使得委托独立于特定的视图。 * 当然,在真实应用中,我们需要检测用户的输入是否合法,是否能够存入模型。 */ void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QSpinBox *spinBox = static_cast<QSpinBox*>(editor); spinBox->interpretText(); int value = spinBox->value(); model->setData(index, value, Qt::EditRole); } /* * 最后,由于我们的编辑器只有一个数字输入框,所以只是简单将这个输入框的大小 * 设置为单元格的大小(由option.rect提供)。如果是复杂的编辑器,我们需要根据 * 单元格参数(由option提供)、数据(由index提供)结合编辑器(由editor提供) * 计算编辑器的显示位置和大小。 */ void SpinBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { editor->setGeometry(option.rect); }
三、自定义模型
model/view 模型将数据与视图分割开来,也就是说,我们可以为不同的视图,
QListView、
QTableView和
QTreeView提供一个数据模型,这样我们可以从不同角度来展示数据的方方面面。但是,面对变化万千的需求,Qt 预定义的几个模型是远远不能满足需要的。因此,我们还必须自定义模型。
类似
QAbstractView类之于自定义视图,
QAbstractItemModel为自定义模型提供了一个足够灵活的接口。它能够支持数据源的层次结构,能够对数据进行增删改操作,还能够支持拖放。不过,有时候一个灵活的类往往显得过于复杂,所以,Qt 又提供了
QAbstarctListModel和
QAbstractTableModel两个类来简化非层次数据模型的开发。顾名思义,这两个类更适合于结合列表和表格使用。
在开始自定义模型之前,我们首先需要思考这样一个问题:我们的数据结构适合于哪种视图的显示方式?是列表,还是表格,还是树?如果我们的数据仅仅用于列表或表格的显示,那么
QAbstractListModel或者
QAbstractTableModel已经足够,它们为我们实现了很多默认函数。但是,如果我们的数据具有层次结构,并且必须向用户显示这种层次,我们只能选择
QAbstractItemModel。不管底层数据结构是怎样的格式,最好都要直接考虑适应于标准的
QAbstractItemModel的接口,这样就可以让更多视图能够轻松访问到这个模型。
现在,我们开始自定义一个模型。这个例子修改自《C++ GUI Programming with Qt4, 2nd Edition》。首先描述一下需求。我们想要实现的是一个货币汇率表,就像银行营业厅墙上挂着的那种电子公告牌。当然,你可以选择
QTableWidget。的确,直接使用
QTableWidget确实很方便。但是,试想一个包含了 100 种货币的汇率表。显然,这是一个二维表,并且对于每一种货币,都需要给出相对于其他
100 种货币的汇率(我们把自己对自己的汇率也包含在内,只不过这个汇率永远是 1.0000)。现在,按照我们的设计,这张表要有 100 x 100 = 10000 个数据项。我们希望减少存储空间,有没有更好的方式?于是我们想,如果我们的数据不是直接向用户显示的数据,而是这种货币相对于美元的汇率,那么其它货币的汇率都可以根据这个汇率计算出来了。比如,我存储人民币相对美元的汇率,日元相对美元的汇率,那么人民币相对日元的汇率只要作一下比就可以得到了。这种数据结构就没有必要存储 10000 个数据项,只要存储 100
个就够了(实际情况中这可能是不现实的,因为两次运算会带来更大的误差,但这不在我们现在的考虑范畴中)。
于是我们设计了
CurrencyModel类。它底层使用
QMap<QString, double>数据结构进行存储,
QString类型的键是货币名字,
double类型的值是这种货币相对美元的汇率。(这里提一点,实际应用中,永远不要使用 double 处理金额敏感的数据!因为
double 是不精确的,不过这一点显然不在我们的考虑中。)
#include "currencymodel.h" CurrencyModel::CurrencyModel(QObject *parent) : QAbstractTableModel(parent){ } /* * rowCount()和 columnCount()用于返回行和列的数目。 */ int CurrencyModel::rowCount(const QModelIndex & parent) const{ return currencyMap.count(); } int CurrencyModel::columnCount(const QModelIndex & parent) const{ return currencyMap.count(); } /* * 这里我们首先判断这个角色是不是用于显示的,如果是,则调用 currencyAt()函数 * 返回第 section 列的键值;如果不是则返回一个空白的QVariant 对象 */ QVariant CurrencyModel::headerData(int section, Qt::Orientation, int role) const{ if (role != Qt::DisplayRole) { return QVariant(); } return currencyAt(section); } /* * Qt 提供了 QVariant 类型,你可以把很多类型存放进去,到需要使用的时候使用一 * 系列的 to 函数取出来即可。 */ QString CurrencyModel::currencyAt(int offset) const{ return (currencyMap.begin() + offset).key(); } /* * 我们当然可以直接设置 currencyMap,但是我们依然添加了 beginResetModel()和 * endResetModel()两个函数调用。这将告诉关心这个模型的其它类,现在要重置内部 * 数据,大家要做好准备。这是一种契约式的编程方式。 */ void CurrencyModel::setCurrencyMap(const QMap<QString, double> &map){ beginResetModel(); currencyMap = map; endResetModel(); } /* * data()函数返回一个单元格的数据。它有两个参数:第一个是 QModelIndex,也就是单元格 * 的位置;第二个是 role,也就是这个数据的角色。这个函数的返回值是 QVariant 类型。 * 我们首先判断传入的 index 是不是合法,如果不合法直接返回一个空白的 QVariant。然后 * 如果 role 是 Qt::TextAlignmentRole,也就是文本的对齐方式,返回 int(Qt::AlignRight | * Qt::AlignVCenter);如果是 Qt::DisplayRole,就按照逻辑进行计算,然后以字符串的格式返回。 */ QVariant CurrencyModel::data(const QModelIndex &index, int role) const{ if (!index.isValid()) { return QVariant(); } if (role == Qt::TextAlignmentRole) { return int(Qt::AlignRight | Qt::AlignVCenter); } else if (role == Qt::DisplayRole|| role == Qt::EditRole) { QString rowCurrency = currencyAt(index.row()); QString columnCurrency = currencyAt(index.column()); if (currencyMap.value(rowCurrency) == 0.0) { return "####"; } double amount = currencyMap.value(columnCurrency) / currencyMap.value(rowCurrency); return QString("%1").arg(amount, 0, 'f', 4);//用arg中的内容替换%1 } return QVariant(); } /* * 在 Qt 的 model/view 模型中,我们使用委托 delegate 来实现数据的编辑。 * 在实际创建编辑器之前,委托需要检测这个数据项是不是允许编辑。模型必须 * 让委托知道这一点,这是通过返回模型中每个数据项的标记 flag 来实现的, * 也就是这个 flags() 函数。这本例中,只有行和列的索引不一致的时候,我们 * 才允许修改(因为对角线上面的值恒为 1.0000,不应该对其进行修改) * 注意,我们并不是在判断了index.row() != index.column()之后直接返回 * Qt::ItemIsEditable,而是返回QAbstractItemModel::flags(index) | * Qt::ItemIsEditable。这是因为我们不希望丢弃原来已经存在的那些标记。 */ Qt::ItemFlags CurrencyModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = QAbstractItemModel::flags(index); if (index.row() != index.column()) { flags |= Qt::ItemIsEditable; } return flags; } /* * 当数据重新设置时,模型必须通知视图,数据发生了变化。这要求我们必须发出 dataChanged()信号。 * 由于我们只有一个数据发生了改变,因此这个信号的两个参数是一致的(dataChanged()的两个参数是 * 发生改变的数据区域的左上角和右下角的索引值,由于我们只改变了一个单元格,所以二者是相同的)。 */ bool CurrencyModel::setData(const QModelIndex &index,const QVariant &value, int role){ if (index.isValid()&& index.row() != index.column()&& role == Qt::EditRole) { QString columnCurrency = headerData(index.column(),Qt::Horizontal, Qt::DisplayRole).toString(); QString rowCurrency = headerData(index.row(),Qt::Vertical, Qt::DisplayRole).toString(); currencyMap.insert(columnCurrency,value.toDouble() * currencyMap.value(rowCurrency)); emit dataChanged(index, index); return true; } return false; } CurrencyModel::~CurrencyModel(){ }
相关文章推荐
- 深入理解模型,视图和控制器(C#)
- 理解OpenGL中的模型视图矩阵变换
- 深入理解盒子模型(2)
- 深入理解C++对象模型之类型转换:ReinterpretCast
- magento 开发 -- 深入理解Magento第七章 – 自定义Magento系统配置
- magento 开发 -- 深入理解Magento第六章 – 高级Magento模型
- 学习《深入理解C++对象模型》小结
- 深入理解Apache Mina(5)---- 配置Mina的 线程模型
- ASP.NET MVC:理解模型、视图和控制器
- 理解模型、视图、控制器
- 深入理解Magento – 第四章 – 模型和ORM基础.doc
- Asp.Net MVC(理解模型、视图和控制器) - Part.2
- ASP.NET MVC教程:理解模型、视图和控制器(1)
- Asp.Net MVC(理解模型、视图和控制器) - Part.2
- 理解模型,视图和控制器(C#)
- 深入理解盒子模型(1)
- 学习《深入理解C++对象模型》小结
- ASP.NET MVC教程:理解模型、视图和控制器(3)
- 深入理解C++对象模型-成员函数的本质以及虚函数的实现(非虚继承)
- 深入理解Magento – 第六章 – 高级Magento模型.doc