在本文中,我们关心的是BlueBill Mobile类,尤其是管理所有Search Species屏幕之后逻辑的控制器;因此本文有助于你了解JavaFX的语言性能。而且我们会举出一些实例来阐述要介绍的技巧和典型JavaFX结构的陷阱。
笔者想应用程序中嵌入了更新的屏播。视频播放要求使用QucikTime。
这里的概念是在搜索框中键入查询时,英文函数或科学名称函数会对清单过滤。此外,当这些生效的时候,BlueBill Mobile还可以执行自动完成输入。例如,如果在键入查询的时候你仔细查看视频会发现只输入了"a-r-d-a-c"来选择"Ardea Cinerea";或用于"Pied Avocet"的"p-i-e-< space>-a"。BlueBill Mobile 会自动会剩余部分进行补充因为在某些情况下,不存在其他选择。这是用来改善移动设备性能的重要功能:你可以以较少的输入达到相同目的。
按照MVC模式,就非常有必要在单独的控制器中概括这种模式;此外,也很容易对这种模式进行单元测试。
首先,让我们看一下代表了分类群的模式类:
- package it.tidalwave.bluebillmfx.taxon.model;
- import java.lang.Comparable;
- public class Taxon extends Comparable
- {
- public-read protected var displayName : String;
- public-read protected var scientificName : String;
- public-read protected var id : String;
- override function compareTo (other : Object)
- {
- return displayName.compareTo((other as Taxon).displayName);
- }
- override function toString()
- {
- return "{displayName} ({scientificName}) ({id})"
- }
- }
- public function displayNameGetter (taxon : Taxon): String
- {
- return taxon.displayName;
- }
- public function scientificNameGetter (taxon : Taxon): String
- {
- return taxon.scientificName;
- }
- public def namePropertyGetters = [displayNameGetter, scientificNameGetter];
类托架外面定义的函数和变量相当于Java静态分析。
这里我们省略了一些不相关的实际项目。基本上,该模式暴露了三个属性,其中有意思的两个分别是displayName和scientificName。我们也可以定义两个函数来处理这两个问题,我们会把这些函数放在namePropertyGetters序列中。
- package it.tidalwave.bluebillmfx.taxon.controller;
- import it.tidalwave.bluebillmfx.taxon.model.Taxon;
- public class TaxonSearchController
- {
- public var selectedTaxon = bind if (selectedTaxonIndex < 0) then null else filteredTaxons[selectedTaxonIndex];
- public var selectedTaxonIndex : Integer = -1;
- public var taxons: Taxon[];
- public var filter = "" on replace
- {
- filteredTaxons = taxons[taxon | matches(taxon, filter)];
- update();
- }
- public-read var autoCompleted = "";
- public var filteredTaxons: Taxon[];
- protected function matches (taxon : Taxon, string: String) : Boolean
- {
- if (string == "")
- {
- return true;
- }
- for (propertyGetter in Taxon.namePropertyGetters)
- {
- if (propertyGetter(taxon).toLowerCase().startsWith(filter.toLowerCase()))
- {
- return true;
- }
- }
- return false;
- }
- protected function update(): Void
- {
- def autoCompletedTry = commonLeadingSubstring(filteredTaxons, findMatchingPropertyGetter());
- //
- // Sometimes it can't find a better auto-completion than the current filter, since it searches the displayName
- // and the scientificName at the same time. In this case, we just ignore the new value.
- //
- if (autoCompletedTry.length() > filter.length())
- {
- autoCompleted = autoCompletedTry;
- }
- selectedTaxonIndex = if (sizeof filteredTaxons == 1) then 0 else -1;
- println("selectedTaxonIndex: {selectedTaxonIndex}")
- }
- protected function findMatchingPropertyGetter(): function (:Taxon): String
- {
- for (taxon in filteredTaxons)
- {
- for (propertyGetter in Taxon.namePropertyGetters)
- {
- if (propertyGetter(taxon).toLowerCase().startsWith(filter.toLowerCase()))
- {
- return propertyGetter;
- }
- }
- }
- return null;
- }
- // some stuff later
- }
这个类揭示了以下的属性:
◆taxons:你需要用完整的鸟类列表来填充
◆filter: 字符串包括需要输入到搜索栏中的文本
◆filteredTaxons: 种类由filter字符串过滤
◆autoCompleted: 控制器猜测的自动完成输入字符串
◆selectedTaxon: 如果filter向下细分种类,它就会分配到这个变量
◆selectedTaxonIndex: -1如果无法获取时,selectedTaxon的索引。
最新的四种属性由客户代码来绑定,这样做可以获取更改提示。#p#
Filter获取了一个触发事件,也就是变量值更改时所执行的代码。触发器用JavaFX运算符 ︳执行了过滤操作:我们可以将触发事件的第一行当作分配到taxons序列中的filteredTaxons来读取,在这一序列中,matches()函数返回值为true。第二行的代码调用了接下来要介绍的update()函数。
出于某些原因,这种方法并不一定奏效,因为filteredTaxons通常会被整体扫描。有多种方法可用来加速选择过程,但是本文不会在这一方法真正应用到手机前前作出过早的优化。在笔记本上,它可以加快1000个项目的速度。
Matches()函数在所有属性上执行了一次迭代以获取函数并检查看相关属性是否以过滤值启动。
创建获得属性值函数的序列的一大好处是我们可以通过定义新的函数轻松添加新的匹配标准:例如,其他语言中的本地化名称。控制器可能会使用在搜索过程中使用这些名称,而我们则不需要再做多余修改。
Update()函数运算出了自动完成输入提示。它会提取filteredTaxons序列以及用于当前选择的获取属性函数,还会调用刚刚在字符串属性的序列中找到了通用子字符串的commonLeadingSubstring()。它不是每次都会作出很好的自动完成输入猜想,因此有时建议甚至比当前过滤器还短,而这种情况我们大可忽略不计。请不要忽视指定临时变量的重要性:由于自动完成输入可能被绑定,因此我们不想为其指定一个会迅速失效的值。
要明白这一点的重要性,这不仅仅是避免无用更新,还能避免程序被破坏。在实际程序中,自动完成输入更改时,TextBox会更新,因此过滤器也会随之更新:已经输入了"cal"后,再输入一个"i",那么TextBox暂时会显示"cali",然后自动输入完成的猜测失败,它会返回一个"cal",TextBox中的字符串会变为"cal":这时候你要坚持自己的想法!绑定确实很强大,但是它也同时具有负面效应。
最后一步操作中,代码会检查看我们是否获取单独的已选定鸟类。
或许,你对于自动完成输入失败的原因仍然感到很困惑。毕竟,我们正在逐步缩小项目列表。因此,如果你已经输入了"cali",那么所有经过过滤的种类会以"cali"开头,对吗?如果你过滤的是一套单一名称,情况就应该是这样;但是我们是同时对两套名称执行搜索,那么就会产生矛盾。看看下例由"cali"过滤器选取的名称组(英语,科学的):("Calandra Lark", "Melanocorypha calandra"), ("Dunlin", "Calidris alpina"), ("California Quail", "Callipepla californica")
另一个有意思的地方是findMatchPropertyGetter()。它必须猜测当前过滤器是否是以"英语"或"科学"名称运行,而且它还会返回相关的属性获取函数。基本上,控制器已经获取了matches()函数中的这一信息,但是我们会将其移走。可能会有人思考让matches()函数返回一个以上的布林值,但是这是不可能的,因为它是由运算符 ︳过滤序列的时候使用的:该运算符需要一个布林值。或许我们可以为稍后调用信息的操作指定一个成员变量,不过此时的代码应该会更具可读性。
为了对文章进一步作补充说明,这里给大家列出了最后两个忽略的函数:
- protected function commonLeadingSubstring (taxons: Taxon[], propertyGetter: function (:Taxon): String): String
- {
- if (sizeof taxons == 0)
- {
- return "";
- }
- if (sizeof taxons == 1)
- {
- return propertyGetter(taxons[0]);
- }
- var common = propertyGetter(taxons[0]);
- for (other in taxons[1..])
- {
- common = commonLeadingSubstring(common, propertyGetter(other));
- if (common == "")
- {
- break; // don't waste time in further iterations, "" it's for sure the final result
- }
- }
- return root;
- }
- function commonLeadingSubstring (string1 : String, string2 : String): String
- {
- return if (string1.length() > string2.length())
- {
- commonLeadingSubstring(string2, string1);
- }
- else if (string1 == "")
- {
- "";
- }
- else if (string2.startsWith(string1))
- {
- string1;
- }
- else
- {
- commonLeadingSubstring(string1.substring(0, string1.length() - 1), string2);
- }
- }
这里的逻辑很简单。通常主要的字符串搜索被分解成了临近字符串对;而对于单一对的搜索则有递归执行。
这里显示了视图类是如何绑定到控制器的:
- package it.tidalwave.bluebillmfx.taxon.view;
- public class TaxonSearchScreen
- {
- public var taxons : Taxon[];
- var filter = "";
- public-read def controller = TaxonSearchController
- {
- taxons: bind taxons
- filter: bind filter
- }
- def autoCompleted = bind controller.autoCompleted on replace
- {
- if (autoCompleted != "")
- {
- filter = autoCompleted;
- }
- }
- def list = ListBox
- {
- items: bind controller.filteredTaxons
- };
- def searchBox = TextBox
- {
- text: bind filter with inverse
- };
- }
你必须用所有可得的种类加载taxon;ListBox会随着过滤的种类自动更新,TextBox与过滤器是双重指令型绑定。之所以需要双重绑定是因为向搜索栏中输入时,一个指令用于给控制器发出新的选择命令,另一个则是自动完成输入时的更新。
【编辑推荐】