第十一章 简易家庭帐本_支出记录

在前两章我们实现了收入记录及系统设置功能,如果说这两章在编程的思路上有什么明显差异的话,那应该是在数据的保存策略上:收入记录采取单条输入、批量保存的方式,而系统设置则是随时变化,随时保存。之所以有这样的差别,是考虑到数据量的差异。就系统设置而言,每一个预设项的数据量(对应于列表的长度)是有限的,我们可以将它们全部显示在列表显示框一类的组件中,并且可以很方便地进行新增、修改及删除操作;然而对于收入记录而言,它的数据量会与日俱增,难以想象,假如将100条收入记录显示在列表组件中,对用户来说,要找到一条记录是多么的困难。本章的支出记录仍然沿用收入记录的实现思路,并保持与收入记录一致的页面设置及操作方法,以便用户形成固定的操作习惯。

第一节 功能描述

除数据项不同外,其余均与收入记录功能相同:

  • 入口:用户在导航菜单页面点击“支出记录”,进入支出记录页面;
  • 出口:用户在支出记录页面点击“返回主菜单”按钮,可以返回到导航菜单页面;
  • 新增:屏幕的上方为信息输入区——输入表单,用户根据表单中的提示信息,选择或输入相关的数据项,并提交数据;
  • 显示:屏幕的下方是显示区,已经提交的数据将显示在列表中;
  • 恢复组件初始状态:数据提交之后,将输入表单恢复到初始状态,等待输入下一条记录;
  • 修改与删除:当用户在列表中选中了某项数据后,可以修改或删除该项数据;
  • 永久保存:当用户确认已提交的信息准确无误后,将数据永久保存在数据库中;
  • 当数据保存到数据库之后,清空屏幕下方的列表,等待新一批数据的输入;
  • 数据项及其来源(10项):
    • 日期:默认系统当前日期,用户可以选择其他日期;
    • 支出一级分类:系统预设选项,包括吃喝、穿戴等等,用户从中选择一项;
    • 支出二级分类:系统预设选项(用户可增删改),是一级分类的子类,如吃喝类中包括粮油、蔬菜、水果等,用户从中选择一项;
    • 支出专项:系统预设选项(用户可增删改),是支出信息的另一种分类方式,用于标记某个特殊事项的支出。例如,用户策划了一次沿运河的徒步旅行,他(她)想要记录下与此项活动相关的支出,就可以创建一个“徒步运河”专项,对支出进行管理;支出专项有两种状态——激活状态与非激活状态,只有激活状态的支出专项才能显示在输入表单的下拉框中;默认设置为“非专项支出”;
    • 受益人:从系统预设的“家庭成员”选项中选择,默认设置为“全体”;
    • 名称:支出货币所换取的对象或相关事项的名称,界面输入,必填项;
    • 数量:界面输入,非必填项;
    • 单位:界面输入,非必填项;
    • 金额:界面输入,必填项;
    • 备忘:界面输入,非必填项。

虽然从数据项的类型上看,支出记录与收入记录非常相近,但是对应到现实生活中,这两类信息的含义却是完全不同的。相对于收入行为来说,支出行为具有更多的自主性,是一种可控的行为。帐本应用考虑到支出行为的这种特性,为其设定了更为多样的分类方式。与收入信息的分类相比,支出信息的分类有两点不同。首先,支出信息有两级分类,一级分类为大类,二级分类隶属于一级分类;其次,支出信息可以归属于某个专项,例如用户计划作一次徒步旅行,沿京杭大运河从北京步行至杭州,所有与这次旅行相关的支出,都记录在这个专项的下面,当项目结束时,可以准确地核算出这次旅行的花销。此外,与收入信息中“收入者”相对应的,在支出信息中有“受益人”选项,用户也许有兴趣统计养育孩子的花费,他可以将与孩子相关的支出信息归属到某个“受益人”名下。总之,对于支出信息的描述有更多的维度,这些分类的作用将体现在信息的查询以及分类汇总功能中,希望借此可以帮助用户理性地评价自己的支出行为,为今后的支出决策提供参考。当然,以上对用户需求的猜测,完全基于笔者个人的主观臆想,是否真的能够满足用户的需求,还要经过实践的检验。

第二节 数据模型

一、对象模型

与收入记录中采用的方法相同,我们用表格的方式来描述支出信息的对象模型,如表11- 1所示。

表11- 1 支出信息的数据模型

二、变量模型

(1) 支出全集:用于保存全部的支出记录,列表结构如图11- 1所示;

图11-1 支出全集列表的结构

(2) 临时支出列表:用于保存本批次输入的支出记录,列表结构与支出全集完全相同; (3) 支出字串列表:临时支出列表所对应的字串表示方式; (4) 二级分类:用于保存从数据库中读取的支出二级分类,为二级列表,列表的结构如图11- 2所示。

图11-2 支出二级分类的列表结构

三、预设项列表

除了支出二级分类需要用全局变量来保存外,其余预设项从数据库中读出后,有两项(一级分类、家庭成员)可以直接设置为下拉框的列表属性,而支出专项需要筛选出激活状态的专项,并提取专项名称组成列表,再将列表设置为下拉框的列表属性,它们的数据结构为:

  • 支出一级分类:一级列表
  • 家庭成员:一级列表
  • 支出专项:三级列表,列表结构如图11- 3所示。

图11-3 支出专项的列表结构

第二节 界面设计

在制作手机上的应用时,有一件事是需要格外加以考虑的,即,让用户尽可能减少使用键盘输入信息,当键盘输入不可避免时,也要尽可能将输入框放在屏幕的上半部分,以避免弹出的输入法界面挡住输入框,使用户无法随时查看输入的内容。我们在上一章的收入记录屏幕中,界面设计就是基于这样的考虑,这样的思路也同样适用于支出记录屏幕(OUT_INPUT)。界面设计如图11- 4所示,图中组件的属性设置如表11- 2所示。

图11-4 支出记录屏幕的界面设计

表11- 2 支出记录屏幕中组件的属性设置

第四节 界面逻辑

1、声明全局变量:

  • 支出全集:用于保存从数据库中读取的全部支出记录;新输入的记录需要先追加到支出全集中,再保存到数据库中;
  • 临时支出列表:保存用户本批次输入的若干条支出记录,当输入完成后,将该列表追加到收入全集中,保存至数据库,然后清空本列表;
  • 支出字串列表:将临时支出列表中的列表项逐条拼成字串,保存在该列表中,该列表是支出显示框的列表属性来源;
  • 二级分类列表:保存从数据库中读取的支出二级分类信息,当用户在输入表单中改变一级分类时,将该列表中与一级分类对应的项设置为二级分类选框的列表属性;

2、屏幕(OUT_INPUT)初始化

  • 从数据库中读取全部支出记录,保存在全局变量支出全集中;
  • 从数据库中读取支出一级分类,并将其设置为一级分类选框的列表属性;
  • 从数据库中读取支出二级分类,保存在全局变量二级分类列表中,并将列表中的第1项设置为二级分类选框的列表属性;
  • 从数据库中读取家庭成员,在列表首位添加一项“全体”,并将其设置为受益人选框的列表属性;
  • 从数据库中读取支出专项,并筛选出处于激活状态的专项组成列表,在列表首位添加一项“非专项支出”,并将其设置为专项选框的列表属性;
  • 设置界面组件的初始状态:清空输入表单,设置所有下拉框的选中项索引值为1,设日期选框的选中日期及显示文本为系统当前日期;
  • 设支出显示框的列表属性为支出字串列表,设置其选中项索引值为0(没有选中项)。

3、支出一级、二级选框的选择关联

  • 屏幕初始化时,一级、二级分类选框的选中项索引值均为1;
  • 当用户改变了一级分类选框的选中项时,根据其选中项索引值,设置二级分类选框的列表属性;

4、新增支出记录

  • 当用户在输入表单中输入并选择了相关信息,点击提交按钮时,检查表单信息是否完整;
  • 如果用户填写了所有必填项,则采集用户输入的信息,以键值对列表的方式将数据组织起来,添加到临时支出列表中,
  • 恢复输入表单的初始状态,等待输入下一条信息;
  • 如果用户填写的信息不完整,则用对话框显示相应的提醒信息;

5、显示输入的支出记录

  • 用户每次点击提交按钮,将新输入的记录拼成字串,添加到支出字串列表中;
  • 将支出字串列表设置为支出显示框的列表属性;

6、选择已输入的支出记录

  • 当用户从支出显示框中选中某项时,弹出选择对话框,提供三个选项:修改、删除、返回;
  • 当用户选择了“修改”时,将选中项内容填写在输入表单中,等待用户修改;
  • 当用户选择了“删除”时,分别从临时支出列表及支出字串列表中删除选中项,并设支出显示框的列表属性为支出字串列表,设选中项索引值为0;
  • 当用户选择了“返回”时,关闭对话框,设支出显示框的选中项索引值为0;

7、修改支出记录

  • 当用户在输入表单中完成了信息的修改,点击提交按钮时,检查表单信息是否完整;
  • 如果表单信息完整,则采集表单中的信息,以键值对的方式将数据组织起来,替换临时支出列表中原有的选中项,并将键值对列表拼成字串,替换支出字串列表中的选中项;
  • 设支出显示框的列表属性为支出字串列表;
  • 设支出显示框的选中项索引值为0;
  • 恢复输入表单的初始状态,等待输入下一条信息;
  • 如果表单信息不完整,则用对话框显示相应的提醒信息;

8、输入信息的永久保存

  • 当用户输入了一条或若干条支出记录后点击保存按钮时,将临时支出列表追加到支出全集中,并将支出全集保存到数据库中;
  • 当支出全集保存完成后,将临时支出列表及支出字串列表设为空列表;
  • 设支出显示框的列表属性为支出字串列表;
  • 恢复输入表单的初始状态,等待输入下一条信息;

9、返回主菜单:

  • 当用户点击返回按钮时,检查用户是否已经保存了已输入的数据;
  • 如果临时支出记录列表长度为零(数据已经保存),则关闭当前屏幕;
  • 否则,弹出选择对话框,提供两个选项——保存与放弃:
  • 如果用户选择“保存”,则将数据永久保存到数据库中,并关闭当前屏幕,返回导航菜单页;
  • 如果用户选择“放弃”,则直接关闭当前屏幕,返回导航菜单页。

注意在测试阶段,如果连接AI伴侣时不曾打开过导航菜单,则这项功能的测试无法完成。

第五节 编写程序

一、 编写过程

在实现收入记录功能时,我们尝试从页面逻辑中寻找过程,本章我们依然以发现并编写过程作为编程任务开端。有了上一次的经验,这里我们直接开始创建过程,首先是组件初始化过程。

1、组件初始化

在第九章“收入记录”中,有一个同名过程,并在测试与改进一节(第七节)中,对其进行了完善(见图9-30),这里我们直接利用此前的成果,除了设置输入表单中的10个组件的初始状态外,还设定了支出显示框的列表以及选中项索引值属性,代码如图11- 5所示。

图11-5 定义组件初始化过程

2、毫秒转日期

“毫秒转日期”过程是专为下面的“列表转字串”过程而编写的,目的是为了减少该过程的代码量。“毫秒转日期”过程的参数为“毫秒数”,返回值为该毫秒数所对应的日期字串,可以设定日期的显示格式,代码如图11- 6所示。

图11-6 毫秒转日期过程

3、列表转字串

列表转字串过程为有返回值过程,该过程的参数为键值对列表,参数的数据格式如图11- 7所示。

图11-7 列表转字串过程参数的数据格式

该过程的返回值为字串,按照上图中数据的格式,我们编写列表转字串过程,该过程调用了毫秒转日期过程,代码如图11- 8所示。

图11-8 列表转字串过程

拼字串就像小学生的造句一样,结果并不是唯一的,这里的拼写方式并不是最好的,读者可以尝试按照自己喜欢的表达方式,来修改这一过程。

4、采集表单信息

采集表单信息过程没有参数,返回值为键值对列表,代码如图11- 8所示。

图11-9 采集表单信息过程

5、填写表单

当用户从支出显示框中选择某项时,将弹出选择对话框,包含“修改”、“删除”及“返回”三个选项,如果用户选择“修改”,则将选中项的内容填写到输入表单中。为了减少后续环节的代码量,我们在这里创建一个“填写表单”过程,来完成表单的填写。该过程的参数为“键值对列表”,格式参见图11- 7,填写表单过程的代码如图11- 10所示。

图11-10 填写表单过程

6、保存到数据库

根据“收入记录”中的经验,应用中共有两个环节可以将本次输入的数据保存到数据库中,一是用户点击保存按钮时,另一个是用户在尚未保存数据的情况下点击返回按钮时。因此这里先行定义“保存到数据库”过程,以备后续编程中调用。代码如图11- 11所示。

图11-11 保存到数据库过程

首先将临时支出记录追加到支出全集列表中,然后调用本地数据库的保存数据过程,保存完成后,用对话框提示用户“保存完成”,清空临时支出记录及支出字串列表。在保存数据之后,还应该设支出显示框的列表属性为临时支出列表,不过这个语句已经添加到“组件初始化”过程里。

二、屏幕初始化

与收入记录相比,支出记录中的屏幕初始化有更多的设置,尤其要关注支出专项,我们需要从原始数据中提取激活状态的专项,为此,我们先创建一个有返回值的过程——激活专项列表,返回值为一级列表,列表项为专项名称,代码如图11- 12所示。

图11-12 激活专项列表过程

然后,再创建一个过程——数据绑定,集中设置所有下拉框组件的列表属性,代码如图11- 13所示。

图11-13 数据绑定过程

最后编写屏幕初始化程序,同时声明三个全局变量,代码如图11- 14所示。

图11-14 声明全局变量并编写屏幕初始化程序

这样的屏幕初始化程序,看起来简洁明了。

这里的“激活专项列表”及“数据绑定”过程仅在此处调用一次,也就是说,有些过程的存在并不是为了提高复用性,而是为了优化代码的结构,并提高代码的可读性。对于App Inventor这样的图形化编程工具来说,这一点尤其重要。

三、选择一级分类

一级分类与二级分类之间存在着关联关系,例如,当一级分类为“吃喝”时,二级分类的备选项应该是“粮油,肉蛋,蔬菜,水果”等,当一级分类为“穿戴”时,二级分类的备选项应该是“冬,夏,春秋,饰品”。为此,我们需要在一级分类选框的完成选择事件中,设置二级分类选框的列表属性,代码如图11- 15所示。

图11-15 一级分类选项更新时,为二级分类选框设置列表属性

四、新增及修改支出记录

当用户点击提交按钮时,可能有两种情况——新增记录或修改记录,在收入记录一章中,我们利用“收入显示框”的选中项索引值来识别这两种情况,这里我们依然沿用此法,在提交按钮点击事件中,一并处理新增与修改操作,并将操作结果显示在支出显示框中。代码如图11- 16所示。

图11-16 一并处理支出记录的新增与修改操作

五、选择已输入项及删除选中项

当用户在支出显示框中选中某项时,弹出选择对话框,并提供“修改”、“删除”及“返回”三个选项,我们首先来处理支出显示框的完成选择事件,代码如图11- 17所示。

图11-17 支出显示框的完成选择程序

下面来处理对话框的完成选择事件,针对用户不同的选择,分别执行不同的操作,代码如图11- 18所示。

图11-18 对话框完成选择程序

这一步操作应该在修改操作之前完成,不过考虑到我们已经有了“收入记录”的开发经验,本章在功能实现的顺序上没有恪守开发的实际顺序。比较一下图9-18及图9-21,你会发现这里的代码变得异常简洁,因为我们将事先创建了“填写表单”过程,并且将设置支出显示框的列表及选中项索引值属性的语句添加到组件初始化过程里。希望你在这里做一个标记,待整个应用开发完成后,你可以回过头去,对“IN_INPUT”屏幕中的程序作进一步的完善。

六、输入信息的永久保存

当用户点击保存按钮时,将已输入的数据保存到数据库,并调用组件初始化过程,使应用恢复到屏幕的初始状态,代码如图11- 19所示。

图11-19 永久保存数据

之所以要调用组件初始化过程,是考虑到用户可能刚刚从支出显示框中选择了某一项,并在选择对话框中点击了修改按钮,此时,选中项的内容已经被填写到输入表单中,如果恰好在这个时候,用户点击了保存按钮,我们必须保证在保存成功之后,输入表单恢复到初始状态。

七、返回主菜单

用户可能在尚未保存数据的情况下,点击返回按钮,鉴于这种情况,我们首先检查临时支出列表的长度,如果列表长度为0,则关闭屏幕,否则,弹出对话框,提示用户“尚未数据保存”,并提供“保存”及“放弃”两个选项,代码如图11- 20所示。

图11-20 返回按钮的点击程序

此前我们已经编写了对话框的完成选择程序,即,当用户从支出显示框中选中某项时,询问用户要执行哪种操作(修改、删除或返回),现在,我们用同一个对话框组件提示用户保存数据,因此,用户的可选项增加为5个,针对这种情况,我们需要改写对话框的完成选择事件,代码如图11- 21所示。

图11-21 对话框中的可选项增加到5个

实际上我们也可以避免在一个对话框中处理这么多的选择,方法是再添加一个对话框组件(对话框2),在返回按钮点击事件中,调用对话框2的“显示选择对话框”过程,这样可以降低程序的复杂度。不过,在帐本应用中,我们有8个屏幕,大部分的屏幕中都包含许多组件和许多代码,为了降低开发工具的负荷,我们尽可能地少添加组件。

至此已经实现了支出记录的全部功能,下面进入测试环节。

第六节 测试与改进

由于有了“收入记录”的经验,本章我们一口气写完了所有代码,将测试环节留到最后。

一、 屏幕初始化

连接手机中的AI伴侣,查看屏幕初始化之后的用户界面。测试结果如图11- 22所示。

图11-22 测试——屏幕初始化

注意看左图中输入表单的第三行,名称的输入框被受益人下拉框挤压得只剩下两个字符的宽度,打开受益人下拉框,发现只有两个数据项,如右图所示。这个错误的原因很容易想到是数据绑定的问题,我们回头查看图11- 13的数据绑定过程,发现了问题所在。如图11- 23所示。

图11-23 数据绑定过程里的错误代码

从数据库中读出的家庭成员数据本身就是一个列表,图中这样设置列表项的结果是生成了一个二级列表,如图11- 24所示。

图11-24 错误代码生成的二级列表数据

我们需要的是一级列表,为此,先将读出的列表保存在局部变量“受益人”中,然后在受益人列表的首位插入一个列表项“全体”,再将“受益人”列表设置为受益人选框的列表属性,代码如图11- 25所示,测试结果如图11- 26所示。

图11-25 受益人列表的正确设置方法
图11-26 代码改正后的测试结果

二、新增支出记录

接下来测试新增记录功能,同时检查一、二级分类下拉框之间的联动,测试结果如图所示,在新增第二条记录时,一级分类选择了“日用”,此时,支出二级选框中的数据项自动更新。两条记录的测试结果正常。

图11-27 测试——新增支出记录

三、修改及删除

在上一项测试的基础上,再添加一条记录,如图11- 28所示(左一)。此时选中第2条,应用弹出选择对话框(左二图),点击“修改”,选中项填写在输入表单中(左三图),将金额35改为45后,点击提交按钮,屏幕下方列表中的数据实现更新(右二图);再次选择第2条记录,然后点击“删除”,支出显示框中的记录变为2条(右一图)。

图11-28 修改与删除操作的测试结果

测试成功,暂时没有发现问题。不过在支出显示框中,对于选中项的标记并不明显,稍候我们加以改进。

四、保存与返回

输入3条记录,点击保存按钮,屏幕显示“保存完成”;再输入1条记录,点击返回按钮,此时弹出对话框,询问是否保存,点击“保存”,屏幕显示“保存完成”。两次保存完成后,支出显示框被清空,测试结果如图11- 29所示。

图11-29 测试——保存与返回

由于连接AI伴侣时,开发环境正在处理的是OUT_INPUT屏幕,因此,保存完成后,或直接点击“放弃”后,应用并不能返回到导航菜单页。

另,我们保存数据的结果暂时还无法测试,我们将在查询功能中看到这些已经保存的数据。

五、改进

1、改进列表转字串过程

测试过程中发现,当选择支出专项时,字串的拼接不够通顺,如图11- 30所示,左图中的“吃喝-酒类自酿酒品专项”有些费解,应该在专项名称前面添加一个破折号,这样读起来就顺了,如右图。对代码的修改见图11- 31。

图11-30 对代码稍加改进

图11-31 在专项名称前添加破折号

2、设置支出显示框的选中项背景色

在App Inventor的设计视图中,选中支出显示框,将其“选中项颜色”属性修改为深灰色(或蓝色),如图11- 32示。测试结果如图11- 33所示。

图11-32 修改支出显示框的选中项颜色
图11-33 改进选中项颜色的测试结果

至此,本章内容全部结束,下一章我们将实现对收入及支出记录的查询,届时,可以测试本章对数据的保存结果。