codecamp

第 10 章 出题及答题

第8章的“总统测验”可以被定制成各种测验,但这种定制只对App Inventor程序员有用。只有程序员可以修改问题和答案,而对于父母、老师或其他用户来说,他们无法创建一个测验或变换问题(除非他们也学App Inventor!)。

本章将构建一个“出题”应用,“老师”可以在输入表单中创建试题。试题和答案将被存储在Web数据库中,以便“学生”可以单独访问“答题”应用并参加考试。通过创建这两个应用,你会在概念上产生更大的飞跃,并学习如何创建一个应用,让用户自行生成数据,并实现用户之间跨应用的数据共享。

{%}

“出题”与“答题”这两个应用协同工作,让“老师”可以为“学生”出题。父母可以在长途旅行中做一些旅行花絮类的应用,以增加孩子们的乐趣;小学教师可以创建“数学突击”一类的小测验;而大学生们可以创建一系列的测验,帮助他们的学习小组来准备期末考试。本章建立在第8章“总统测验”的基础上,如果你还没学过,在继续本章之前,请先学习第8章。

本章将设计两个应用:针对“老师”的“出题”应用(见图10-1)以及针对“学生”的“答题”应用。在“出题”应用中:

  • 用户在输入表单中输入问题及答案;

  • 显示输入的一对问答;

  • 将问题及答案存储在数据库中。

{%}

图 10-1 出题应用

“答题”应用的功能与之前的“总统测验”类似。事实上,是以“总统测验”为起点创建“答题”应用,不同的是,这里的问题是使用“出题”应用输入并保存在数据库中的。

学习要点

“总统测验”是一个使用静态数据的应用范例:不管用户做多少次测验,问题都是一样的,因为问题被写在程序中(称为“硬编码”)。新闻应用、博客以及像Facebook和Twitter这类的社交网络应用采用的是动态数据,这意味着数据随时在改变。通常这种动态信息由用户生成,这类应用允许用户输入、修改并共享信息。在“出题”与“答题”应用中,将学习创建一个应用,来处理用户生成的数据。

在第9章“木琴”应用中,我们首次引入动态列表概念:用户输入的音符被记录在列表中。由用户生成数据的应用更为复杂,而且使用的块也更抽象,因为没有预设的静态数据可供参照。尽管可以定义列表变量,但不能设置具体的项。在编写程序的同时,需要设想最终用户输入的数据被添加到列表中。

本章涵盖了App Inventor中的如下内容:

  • 输入表单:允许用户输入信息;

  • 显示来自多个列表的数据项;

  • 永久保存数据:“出题”应用将问题和答案保存到网络数据库中,“答题”应用将从同一个数据库中加载它们;

  • 数据共享:使用TinyWebDB组件(而不是之前的TinyDB)将数据存储在Web数据库中。

准备开始

登陆App Inventor网站,创建新项目“MakeQuiz”,屏幕标题设为“出题”,并连接到测试手机或模拟器。

设计组件

使用组件设计器来创建用户界面,如图10-2所示(图的后面有更详细的说明),组件清单列于表10-1中。从Palette中拖出组件,将名称改为表中的命名。注意,标题Label的名称(Label1 – Label3)不必改,就用它们的默认值(因为在编辑器中不会使用这些名称)。

表10-1 “出题”应用中的所有组件

组件类型 面板中分组 命名 作用
TableArrangement Layout TableArrangement1 格式化表单,包括问题及答案
Label User Interface Label1 提示“问题:”
TextBox User Interface QuestionText 用户在此输入问题
Label User Interface Label2 提示“答案:”
TextBox User Interface AnswerText 用户在此输入答案
Button User Interface SubmitButton 用户点击提交问题-答案对儿
Label User Interface Label3 显示“测验的问题及答案。”
Label User Interface QuestionAnswersLabel 显示之前输入的成对的问题答案
TinyWebDB Storage TinyWebDB1 用数据库保存并提取数据

{%}

图 10-2 组件设计器中的“出题”应用

按以下方式设置组件属性:

1. 设置Text属性:Label1为“问题:”,Label2为“答案:”,Label3为“试题及答案”;

2. 设置Label3的字号为18,并勾选FontBold属性;

3. 设置QuestionText的Hint属性为“输入问题”,AnswerText的Hint属性为“输入回答”;

4. 设置SubmitButton的Text属性为“提交”;

5. 设置QuestionsAnswersLabel的Text属性为“试题及答案”;

6. 将QuestionText、AnswerText以及与它们相关的Label移入TableArrangement1。

为组件添加行为

在“总统测验”中,首先定义了两个全局列表变量QuestionList和AnswerList,本章中无需为这两个变量提供预设的问题和答案,如图10-3所示。

{%}

图 10-3 列表变量初始化

需要注意,与“总统测验“不同的是,这两个列表没有定义列表项,因为“出题”及“答题”应用中,所有数据都将由用户创建(即动态的、用户生成的数据)。

记录用户的输入

首先来处理用户的输入行为。具体来说,当用户输入问题和答案并点击提交时,程序要向列表中添加数据项来更新QuestionList和AnswerList,如下图所示:

{%}

图 10-4 向列表中添加新项

块的作用

向列表中添加项,意味着向列表的末尾追加新项。如图10-4,程序从QuestionText和AnswerText文本框中获取用户输入的内容,并分别被追加到相应的列表中。

向列表中添加的项更新了列表变量QuestionList和AnswerList,但用户看不到任何变化。第三行的块用来显示这个变化:用冒号将两个列表的内容连接起来。默认情况下,App Inventor用小括号来包围列表内容,列表项之间用空格间隔,像这样:(item1 item2 item3)。当然,这不是显示列表的理想方式,只是暂时用来测试程序的行为。稍后我们将用更高级的方式来显示列表,即,每对问题答案各占一行。

清空问题及答案

回忆一下在“总统测验”中,当移动到下一题时,要清空上一题的回答结果。在本应用中,当用户提交了一对问题-答案后,同样要清空QuestionText及AnswerText文本框,以便准备下一题的输入,如下图所示:

{%}

图 10-5 提交问题-答案之后清空文本框

块的作用

用户提交的问题-答案,将分别被添加到各自的列表中,并显示出来,这时QuestionText和AnswerText中的文本被清空,如图10-5所示。请注意,可以复制一个有内容的文本块(如上图中的“:”块),通过删除块中的文本,来获得一个空的文本块。

用多行文本显示问题-回答

现在是以App Inventor的默认格式来显示问题及答案。假如有一个有关州首府的测验,已经输入了两对问题-答案,则显示成: (加州首府在哪? 纽约州首府在哪?):(萨克拉门托 奥尔巴尼)。

可以想像,如果测验中的问题很多,结果会显得非常混乱。理想的显示方式,应该是每行只显示一对问题-答案:

加州首府在哪? 萨克拉门托

纽约州首府在哪? 奥尔巴尼

第20章讲述了单个列表中项的逐行显示技术,在继续学习之前,可以去阅读一下。

这里的任务稍显复杂,因为涉及到两个列表。为了应对这种复杂性,需要创建过程displayQAs,并从SubmitButton.Click事件处理程序中调用该过程。

逐行显示问题-答案,需要做到以下几点:

  • 使用foreach块遍历QuestionList中的每个问题;

  • 使用变量answerIndex,在遍历问题的同时,获取与问题对应的答案;

  • 使用join块连接每对问题-答案,并用换行符(\n)来分开每对问题-答案,如下图所示:

块的作用

过程displayQAs封装了所有用于显示数据的块,如图10-6所示,在需要显示列表时,可直接调用displayQAs,而不必再重复使用过程内部的块。

{%}

图 10-6 创建displayQAs过程

由于foreach块只能遍历一个列表,而本应用中有两个列表,因此要求在遍历问题列表的同时,为每个问题选择对应的答案。这需要定义一个索引变量,就像第8章“总统测试”中的currentQuestionIndex一样,这里定义了answerIndex,当foreach遍历QuestionList时,用来跟踪对应的答案在列表AnswerList中的位置。

在foreach开始遍历之前,设answerIndex的值为1;在foreach遍历过程中,answerIndex用来从AnswerList中选择当前问题的答案,然后递增1。在foreach的每次迭代中,当前的问题-答案被添加到QuestionsAnswersLabel的最后一行,问题与答案之间以冒号分隔。

调用新建的过程

已经创建了显示问题-答案的过程displayQAs,但在调用它之前,它起不到任何作用。修改SubmitButton.Click事件处理程序,用displayQAs替代对QuestionsAnswersLabel.Text的简单设置,来显示所有的问题-答案。更新后的块如图10-7所示。

第 9 章 木琴
第 11 章 广播中心
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }