/

/

Qt 学习之路 2(50):自定义可编辑模型

Qt 学习之路 2(50):自定义可编辑模型

上一章我们了解了如何自定义只读模型。顾名思义,只读模型只能够用于展示只读数据,用户不能对其进行修改。如果允许用户修改数据,则应该提供可编辑的模型。可编辑模型与只读模型非常相似,至少在展示数据方面几乎是完全一样的,所不同的是可编辑模型需要提供用户编辑数据后,应当如何将数据保存到实际存储值中。

我们还是利用上一章的CurrencyModel,在此基础上进行修改。相同的代码这里不再赘述,我们只列出增加以及修改的代码。相比只读模型,可编辑模型需要增加以下两个函数的实现:

 
 
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole);
1
2
3
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index, const QVariant &value,
             int role = Qt::EditRole);

还记得之前我们曾经介绍过,在 Qt 的 model/view 模型中,我们使用委托 delegate 来实现数据的编辑。在实际创建编辑器之前,委托需要检测这个数据项是不是允许编辑。模型必须让委托知道这一点,这是通过返回模型中每个数据项的标记 flag 来实现的,也就是这个 flags() 函数。这本例中,只有行和列的索引不一致的时候,我们才允许修改(因为对角线上面的值恒为 1.0000,不应该对其进行修改):

 
 
Qt::ItemFlags CurrencyModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags(index);
if (index.row() != index.column()) {
flags |= Qt::ItemIsEditable;
}
return flags;
}
1
2
3
4
5
6
7
8
Qt::ItemFlags CurrencyModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags flags = QAbstractItemModel::flags(index);
    if (index.row() != index.column()) {
        flags |= Qt::ItemIsEditable;
    }
    return flags;
}

注意,我们并不是在判断了index.row() != index.column()之后直接返回Qt::ItemIsEditable,而是返回QAbstractItemModel::flags(index) | Qt::ItemIsEditable。这是因为我们不希望丢弃原来已经存在的那些标记。

我们不需要知道在实际编辑的过程中,委托究竟做了什么,只需要提供一种方式,告诉 Qt 如何将委托获得的用户输入的新的数据保存到模型中。这一步骤是通过setData()函数实现的:

 
 
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
}

回忆一下我们的业务逻辑:我们的底层数据结构中保存的是各个币种相对美元的汇率,显示的时候,我们使用列与行的比值获取两两之间的汇率。例如,当我们修改currencyMap["CNY"]/currencyMap["HKD"]的值时,我们认为人民币相对美元的汇率发生了变化,而港币相对美元的汇率保持不变,因此新的数值应当是用户新输入的值与原来currencyMap["HKD"]的乘积。这正是上面的代码所实现的逻辑。另外注意,在实际修改之前,我们需要检查 index 是否有效,以及从业务来说,行列是否不等,最后还要检查角色是不是Qt::EditRole。这里为方便起见,我们使用了Qt::EditRole,也就是编辑时的角色。但是,对于布尔类型,我们也可以选择使用设置Qt::ItemIsUserCheckable标记的Qt::CheckStateRole,此时,Qt 会显示一个选择框而不是输入框。注意这里的底层数据都是一样的,只不过显示方式的区别。当数据重新设置是,模型必须通知视图,数据发生了变化。这要求我们必须发出dataChanged()信号。由于我们只有一个数据发生了改变,因此这个信号的两个参数是一致的(dataChanged()的两个参数是发生改变的数据区域的左上角和右下角的索引值,由于我们只改变了一个单元格,所以二者是相同的)。最后,如果数据修改成功就返回 true,否则返回 false。

当我们完成以上工作时,还需要修改一下data()函数:

 
 
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);
}
return QVariant();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
    }
    return QVariant();
}

我们的修改很简单:仅仅是增加了role == Qt::EditRole这么一行判断。这意味着,当是EditRole时,Qt 会提供一个默认值。我们可以试着删除这个判断来看看其产生的效果。

最后运行一下程序,修改一下数据就会发现,当我们修改过一个单元格后,Qt 会自动刷新所有受影响的数据的值。这也正是 model / view 模型的强大之处:对数据模型的修改会直接反映到视图上。

Qt 学习之路 2(50):自定义可编辑模型的更多相关文章

  1. Qt 学习之路 2(45):模型

    Home / Qt 学习之路 2 / Qt 学习之路 2(45):模型 Qt 学习之路 2(45):模型  豆子  2013年2月26日  Qt 学习之路 2  23条评论 在前面两章的基础之上,我们 ...

  2. Qt 学习之路 2(41):model/view 架构

    Qt 学习之路 2(41):model/view 架构 豆子 2013年1月23日 Qt 学习之路 2 50条评论 有时,我们的系统需要显示大量数据,比如从数据库中读取数据,以自己的方式显示在自己的应 ...

  3. Qt 学习之路 2(53):自定义拖放数据

    Qt 学习之路 2(53):自定义拖放数据 豆子  2013年5月26日  Qt 学习之路 2  13条评论上一章中,我们的例子使用系统提供的拖放对象QMimeData进行拖放数据的存储.比如使用QM ...

  4. Qt 学习之路 2(5):自定义信号槽

    Home / Qt 学习之路 2 / Qt 学习之路 2(5):自定义信号槽 Qt 学习之路 2(5):自定义信号槽  豆子  2012年8月24日  Qt 学习之路 2  131条评论 上一节我们详 ...

  5. Qt 学习之路 2(49):自定义只读模型

    Qt 学习之路 2(49):自定义只读模型 豆子 2013年5月5日 Qt 学习之路 2 18条评论 model/view 模型将数据与视图分割开来,也就是说,我们可以为不同的视图,QListView ...

  6. Qt 学习之路 2(23):自定义事件

    Qt 学习之路 2(23):自定义事件  豆子  2012年10月23日  Qt 学习之路 2  21条评论 尽管 Qt 已经提供了很多事件,但对于更加千变万化的需求来说,有限的事件都是不够的.例如, ...

  7. Qt 学习之路 2(51):布尔表达式树模型

    Qt 学习之路 2(51):布尔表达式树模型 豆子 2013年5月15日 Qt 学习之路 2 17条评论 本章将会是自定义模型的最后一部分.原本打算结束这部分内容,不过实在不忍心放弃这个示例.来自于 ...

  8. Qt 学习之路 2(25):画刷和画笔

    Home / Qt 学习之路 2 / Qt 学习之路 2(25):画刷和画笔 Qt 学习之路 2(25):画刷和画笔  豆子  2012年11月5日  Qt 学习之路 2  17条评论 前面一章我们提 ...

  9. Qt 学习之路 2(24):Qt 绘制系统简介

    Qt 学习之路 2(24):Qt 绘制系统简介 豆子 2012年10月30日 Qt 学习之路 2 77条评论 Qt 的绘图系统允许使用相同的 API 在屏幕和其它打印设备上进行绘制.整个绘图系统基于Q ...

随机推荐

  1. 对象序列化中transient关键字的用途

  2. 专利系统数据库连接出现 base-64字符串中的无效字符 错误

    错误提示如图: 解决方法: 1.进注册表修改如下 2.进入系统配置页面http://10.10.0.70/eaf/init 对数据库进行重新配置 3.若不行再将如下密码修改一下 重启IIS生效

  3. 【原创】10. MYSQL++ 之 DbDriver

    1. 综述 DbDriver只是对于MYSQL C API的一个非常简单的封装,作者原句是This class does as little as possible to adapt between ...

  4. VS快捷键小收集

    1. Ctrl-M-O 折叠所有方法      Ctrl-M-M 折叠或展开当前方法     Ctrl-M-L 展开所有方法 2. 行编辑(复制,剪切,删除,交换) 当你在光标停留行使用快捷键Ctrl ...

  5. Chrome,firefox,ie等浏览器空格 宽度不一样

    方案一:使用其他字符代替空格 使用(&nbsp:)空格浏览器之间,显示的不一样,对不齐等现象. 解决方案: 用半角空格&ensp:或者全角空格&emsp:就可以了,&e ...

  6. 453D Little Pony and Elements of Harmony

    传送门 分析 我们可以将所有的b[i^j]直接对应到b[f(i^j)]上 于是显然可以fwt 我们对b进行t次fwt之后直接将答案与e0卷起来即可 注意由于模数不确定,我们可以将模数扩大$2^m$然后 ...

  7. PostgreSQL9.3+PostGIS2.1安装配置

    Postgresql——Postgresql是一种对象关系型数据库.下载地址:http://www.postgresql.org/download/ postgis (可选)——是一个空间数据库,它扩 ...

  8. 高级软件测试技术(测试管理工具实践day4)

    今天是截止日期,有胡俊辉的指导下小组成员都了解使用了Bugzilla的基本使用.大家都在晚上之前把各自的文档汇总给汪鸿,由他撰写了操作手册.并且在下午杨瑞丰完成了视频的录制工作.但是在转化为MP4 格 ...

  9. System.Web.UI.Page事件执行顺序

    #region OnPreInit 第一步(显式重写,文章下面有隐式重写) protected override void OnPreInit(EventArgs e) { //检查 IsPostBa ...

  10. Java学习第六篇:集合类

    一.Java集合类框架 Java集合大致可分为Set.List和Map三种体系,其中Set代表无序.不可重复的集合:List代表有序.重复的集合:而Map则代表具有映射关系的集合:从Java5以后,J ...