堆栈和缓存
现在,我们将深入了解另一个对用户有益的功能,即查看上一个窗体,它与浏览器中的“后退”按钮功能类似。这就意味着,应用程序必须“记住”用户的位置。
在某些应用程序中实现此操作比较简单,因为窗体的“目标”和“起始”路径很有限,可通过硬编码来实现。对于我们的应用程序,您可以看到这些窗体显示给用户的顺序几乎是随机的(参见图 1)。
因此,我们现在需要了解基本要求。首先,我们希望尽量减少内存中保留的窗体数;其次,我们希望记录用户查看过的窗体以及查看顺序。事实上,我们可能还希望跟踪在每个窗体中查看的数据,但是完成架构后,它只是一个简单的新增部分,请读者自己练习。
那么,到底什么解决方案能够满足这些要求呢?要尽量减少窗体数,可以使用所谓的缓存。缓存只是一种存储机制,可用于保存已加载窗体的单个副本,并在可用时重复使用缓存的窗体。这就意味着当用户要查看窗体时,应首先检查它是否位于缓存中。如果不在缓存中,则按正常方式加载。但如果不是简单地显示缓存的窗体,而是要在内存中保存一个新副本,则还需要考虑实际创建窗体的时间。 可以使用堆栈跟踪已查看的窗体的历史记录。假设查看的每个屏幕都可以表示为一张卡片,而这些卡片可以插入到堆栈中。用户查看每个屏幕时,可以将卡片插入(压入)堆栈中。也就是说,位于顶部的卡片是最后查看的窗体;要查看历史记录,只需从堆栈中删除(或弹出)卡片即可。使用这种方法,应用程序可以“撤消”由用户执行的任意数量、任何顺序的浏览操作。
设计 FormStack
我们已经了解了需要满足的要求和基本方法,现在让我们看看如何实现它们。首先,让我们仔细考虑完成这项工作需要执行的操作。 我们将需要 FormStack,它可以作为类来实现。它需要一种压入和弹出窗体的机制。还需要一个用于存放实际窗体的缓存和一个堆栈,鉴于前面介绍的资源问题,缓存不应该保存窗体的实际副本,而应该只保存一个标识符。我们将窗体类的类型用做字符串。
加载窗体并非瞬间即可完成,尤其是当窗体包含许多控件时。必须创建窗体的所有控件,通常可能需要为列表和下拉列表提取数据。提取数据后,则需要填充控件。如果在应用程序进程的主线程中执行以上所有操作,则在加载窗体的过程中,用户必须等待很长时间。 为了减少等待时间,需要添加两个功能。首先,应该使用辅助线程提取所有需要的数据;然后,添加一个方法,允许应用程序在缓存中“预先加载”一个窗体。这就使应用程序能够在后台或在空闲时(例如启动画面过程中)加载“重型”窗体。
实现缓存和堆栈时还需要考虑其他一些问题,但是我们先定义所需的核心内容。由 FormStack 类完成的实际操作几乎都会在“压入”或“预先加载”过程中启动,因此,我们先看一下其中的逻辑。

图 2:执行压入
图 2 显示了“压入”的执行流程图。正如您看到的,我们只检查缓存是否存在,如果不存在,则调用“预先加载”;否则,只显示窗体并将其添加到堆栈中。

图 3:执行预先加载
尽管预先加载(参见图 3)执行的步骤略多一些,但还是比较直观。首先,必须创建 Form 类。在调用标准 InitializeComponent 方法创建窗体的所有控件之前,启动数据查询线程。
一个重要的步骤是,创建所有控件后,必须等待数据查询线程完成才能填充控件,因此需要处理一些基本的线程同步。
填充控件后,最后一步是将控件添加到缓存中。