简介 模型过滤是这样一种技术它在 Swing 组件体系结构中提供附加的功能与灵活性 Swing 体系结构的重要创新之一在于采用了模型/视图/控制器 (MVC) 原理这样就可将组件的不同角色分离开当一种体系结构具备 MVC 分离特性时即可对组件的数据与状态作不同的解释这允许程序员在组件及其模型之间插入过滤器对象模型过滤可以在模型内修改数据的表示还也可以改变模型所封装数据的外在数目和顺序 模型过滤器的另外两种重要特性是 模型过滤操作不会改变底层的模型数据这使得多个组件可以共享一组数据而且每个组件都可能以不同的方式解释这组数据 过滤器可以叠用这样就可以依次用几个不同的过滤器对象来解释模型数据 已定义的代理 为了最大限度地利用 Java 平台对面向对象的支持可以简单地认为组件由若干对象构成这些对象可以由一个通用术语 ― 代理 ― 来描述代理是实现一个公共 Java 接口并与某个特定组件相关联的对象代理实现的接口定义代理在 MVC 体系结构中充当的角色 对于刚刚接触 Swing 的程序员而言代理的概念似乎有些难以理解但是它们也是 AWT 组件的一种共同特征例如如果想更改 javaawtLabel 组件上的字体只需创建或获取 javaawtFont 类的一个实例并且调用 getFont() 使该实例与组件相关联Font 对象的内部运作细节可能很有趣但是组件只要有 Font 类型对象的一个引用即可适当地显示自己甚至像标签前景颜色这种简单概念也是通过代理实现的;javaawtColor 类提供一种适合作组件前景颜色的对象作为一般规则值为非基本数据类型的各种组件属性都可看作是代理 Swing 中的 MVC 实现就是这些概念的体现对象不仅用于表示组件的属性值也用于表示组件行为的诸多方面这种方案相当灵活足以支持 Swing 的可插接外观 (PLAF) 功能的实现该功能使应用程序既可模拟本地平台的外观也可用一种与平台无关的方案显示组件PLAF 既可使应用程序看起来就像 Microsoft Windows Mac OS 和 X/Motif 等平台的本地应用程序一样也可使应用程序具有一种中立的外观称为 Java LAF 或 Metal LAF PLAF 功能与组件的外观密切相关本文主要讨论这一体系结构的模型部分它与组件的外观的无关 作为一种模型(或类似一种模型) 每种支持数据与状态的 Swing 组件都有一种与之相关的模型接口无论接口感兴趣的是封装于该模型的数据还是状态它都会包含允许组件以编程方式查询模型内容的若干方法 每个模型接口都提供两类方法一类方法提供对数据与状态的访问而另一类方法允许组件或者其他对象注册或取消注册事件监听程序监听程序的类型及其提供的事件对象都由这些方法定义 Swing 模型接口可以有不同类型的类实现在许多情况下为模型提供的是一种抽象实现;除了为了触发模型所表示的各种事件方法而提供的 protected 方法之外这通常是一种不完全的正则实现所有模型都有一个缺省实现并且是一个具体类 既好又简单 ― ListModel 接口 在开始讨论过滤之前对典型的模型接口作一回顾不失为明智之举 ListModel 接口代表 JList 组件中的数据这是三种集合模型中最简单的一种(另外两种分别是 JTree 和 JTable) ListModel 有两个方法用于检索列表中的元素个数以及各个元素另外还有两个方法用于维护感兴趣的监听程序列表以便监听列表模型的变化 ListModel 的简化源代码 package javaxswing; import javaxswingeventListDataListener; public interface ListModel { int getSize(); Object getElementAt(int index); void addListDataListener(ListDataListener listener); void removeListDataListener(ListDataListener listener); }
在 ListModel 接口中 getSize() 与 getElementAt() 方法用于遍历模型中的元素而其他两个方法用于建立与感兴趣的监听程序之间的关联以便监听模型的变化 ListDataListener 接口支持三个方法当模型监听到其底层数据发生变化时就会调用这三个方法这三个方法是 intervalAdded() intervalRemoved() 和 contentsChanged() 每个方法都接受单个 ListDataEvent 作为参数根据模型所发生变化的复杂程度之不同模型实现可以使用其中的任一个方法来描述这些变化通常 intervalAdded() 和 intervalRemoved() 用于描述变化的时间间隔;当变化过于复杂无法作为一个闭合间隔进行描述时就会用到 contentsChanged() 为了理解模型过滤如何运作请记住这一点JList 组件只对 ListModel API 的实现感兴趣该组件并不关心数据驻留何处以及数据是如何组织的无论该模型是一个缺省类抽象类的扩展还是 ListModel 接口的一种直接实现都不影响 JList 组件的行为 模型过滤的基本概念利用了 Swing 组件对模型类的底层实现缺乏了解这一事实下图说明了这种典型的关系 模型过滤器是实现了模型接口但并不真正包含数据的类模型过滤器在组件与其模型之间进行协调模型过滤器可以重新解释模型所提供的信息并且可以更改所提供的数据元素个数数据的顺序以及数据本身 在本例中过滤器类是将一个现有模型类作为其数据源来实例化的在模型过滤器的一般实现中对 API 方法的调用将委托给源模型 由于此 API 是统一实现的因此完全可以在组件与其模型之间叠放多个过滤器注意每个过滤层都要求每个 API 调用穿过一个附加的间接层;如果过滤层过于复杂则很可能造成性能瓶颈 基本过滤器 下面显示的抽象类是作用于 JList 组件之上的模型过滤器的基类其唯一的构造函数要求模型过滤器的每个实例都要引用某个底层的模型数据该数据既可以是另一个模型过滤器也可以不是;在这两种情况下过滤器的行为是相同的 模型过滤器基类 package comketherwaremodels; import javaxswing*; public abstract class AbstractListModelFilter extends AbstractListModel { // 用来保存被过滤模型的引用 protected ListModel delegate; // 构造函数 ― 接受单个参数其中包含被过滤模型的引用 public AbstractListModelFilter(ListModel delegate) { thisdelegate = delegate; } public ListModel getDelegate() { return thisdelegate; } public int getSize() { // 委托给过滤器目标 return delegategetSize(); } public Object getElementAt(int index) { // 委托给过滤器目标 return delegategetElementAt(index); } public void addListDataListener(ListDataListener listener) { // 委托给过滤器目标 delegateaddListDataListener(listener); } public void removeListDataListener(ListDataListener listener) { // 委托给过滤器目标 delegateremoveListDataListener(listener); } }
该类相当于一种空过滤器它不更改任何底层数据因此它没有什么特别的意义ListModel 过滤器类的实际实现将覆盖该抽象类的方法以便以不同的方式呈现底层数据 您可以通过实现过滤器来改变底层数据事件的特性为了使对模型过滤器的讨论更易于理解本文的示例都只针对不可变的数据模型即不触发任何模型事件的类 缺省模型适合于要求不高的一般应用但是您应该了解这些缺省类都是为通用目的而设计的因此在对性能有严格要求的情况下它们通常表现不佳同样许多常用的模型都是作为可变模型来实现的即模型的数据可随时间变化当已知数据为静态数据时这些额外的行为可能是多余的因此您可能想另外构建模型类去掉由事件传播所导致的额外开销 不可变模型 在许多情况下根据模型的底层数据是否可变对模型进行分类很有用在数据不会变化的情况下可以实现不可变的数据模型这种模型不实现用于监听数据变化的监听程序Swing 模型接口的缺省实现假定数据是可变的 不可变模型的创建过程相当简单您可以创建一个具体类该类可提供模型接口但为与事件相关的活动所提供的所有方法都不执行任何操作根据模型要作为一般模型使用还是作为专用模型使用您既可将此不可变模型实现为一个抽象类也可将其实现为一个具体类 下面的示例是一个不可变的列表模型我设计它时希望它非常通用并且允许将支持 javautilList 集合接口的任何对象用作数据源返回的数据是一个笼统的 Object 类型;如何显示对象留待 JList 及其相关绘制程序解释 不可变模型的示例 package comketherwaremodels; import javautil*; import javaxswing*; public abstract class ImmutableListModelFilter extends AbstractListModel { // 用来保存被过滤模型的引用 protected List collection; // 构造函数 ― 接受单个参数其中包含被过滤模型的引用 public AbstractListModelFilter(List collection) { llection = collection; } public List getCollection() { return llection; } public int getSize() { // 委托给集合 return collectionsize(); } public Object getElementAt(int index) { // 委托给过滤器目标 return collectionget(index); } public void addListDataListener(ListDataListener listener) { // 覆盖为空操作 } public void removeListDataListener(ListDataListener listener) { // 覆盖为空操作 } }
下面将讨论四种类型的过滤器替换排序排除和包含 替换过滤的目的在于重新解释模型数据并且通过改变返回的对象元素来表示它这种类型的过滤器不改变数据元素的顺序它既不删除数据也不创建额外的数据 下面是一个替换过滤器的示例它为底层模型中的每个数据项添加一个数字索引唯一的变化是覆盖了单个方法 替换过滤器的示例 package comketherwaremodels; import javaxswing*; public abstract class IndexingListModelFilter extends AbstractListModelFilter { public Object getElementAt(int index) { // 委托给过滤器目标 String element = delegategetElementAt(index)toString(); return IntegertoString(index) + ? ?+ element; } }
在许多情况下在绘制程序中引入补充的特性可能更合适比如填加一个行索引您可以提供一个过滤器它通过与绘制程序交互来提供额外的图形表示使用过滤器代替绘制程序的优点在于可用一个组件显示经过索引的数据而无须与绘制程序相关联 替换过滤器通常不覆盖 getSize() 而且不改变所返回元素的顺序 排序过滤器 排序过滤器代表了另一层面的复杂性它们不改变所表示元素的个数在这一点上与替换过滤器类似排序过滤器改变模型中经过索引的元素顺序其基本技术在于创建模型元素的一种替代索引用于代替实际的顺序 排序过滤器的一种常见类型是分类过滤器它基于某个明确的排序顺序重新索引数据下面的示例按字母顺序排列任一个 ListModel 实现的内容 排序过滤器的示例 package comketherwaremodels; import javautil*; import javaxswing*; public abstract class AlphaSortingListModelFilter extends AbstractListModel { // 已排序的索引数组 protected ArrayList sortedIndex; public AlphaSortingListModelFilter(ListModel delegate) { thisdelegate = delegate; resort(); } // 该算法称为插入排序适合于处理元素个数少于几百个的数据 // 它是一种无堆栈排序 protected synchronized void resort() { sortedIndex = new ArrayList(); nextElement: for (int x=; x < delegategetSize(); x++) { for (int y=; y < x; y++) { String current = delegategetElementAt(x)toString(); int compareIndex = ((Integer) sortedIndexget(y))intValue(); String compare = sortedIndexget(compareIndex)toString(); if (pareTo(compare) < ) { sortedListadd(new Integer(x) y); continue nextElement; } } sortedListadd(new Integer(x)); } } public Object getElementAt(int index) { // 委托给过滤器目标但使用已排序的索引 return delegategetElementAt(sortedIndex[index]); } }
可以将一种排序过滤器用于 JTable 组件以便对表数据执行面向列的排序;这种过滤器的代码类似于上面的示例通过修改 JTable 的表头和表的模型组件该过滤器可以得到进一步的增强 请注意上面的示例只对不可变列表模型有效如果数据在动态变化为了修改在事件被触发时由 ListDataEvent 对象传递的索引必须提供一些附加支持这将显着增加过滤器的复杂性我将它的实现作为一个练习留给读者 排序过滤器的主要特征在于他们不增加或者减少模型的可见元素个数因此 getSize() 将委托给被过滤的模型他们通常将不改变数据元素而只是按照某种替代顺序解释数据的索引 排除过滤器 最后两种类型的过滤器非常相似但是拥有完全不同的目的排除过滤器与包含过滤器都允许对模型的数据元素进行限制或者补充额外的元素 排除过滤器使模型中的某些元素看似不存在在只有单一数据源可用并且实现方案只要求显示数据的一个子集的情况下这些过滤器相当有效 关于典型的排除过滤器的示例请参考 TerritoryListModelFilterjava该示例给出了一个销售区域列表其中每个区域都与一个销售人员相关联当选定一个销售人员的姓名时过滤器只显示与该销售人员相关联的那些区域 这个示例的优点非常明显如果不进行过滤则每次选定一个不同的销售人员都需要重新加载数据模型或者在高速缓存中保存大量的模型实例过滤器甚至允许两个不同的组件用两种不同的解释方案查看同一个基本模型 包含过滤器 包含过滤器尽管不像排除过滤器那样广泛适用但它们可用来向模型中添加信息由于这种类型的过滤器可用于进行总计或者小计这些过滤器的最佳用途是报表应用程序 执行总计操作的过滤器创建一个虚拟元素并将其显示在列表模型的尾部为了实现这一功能过滤器将模型大小的值加 并将对除最后一个元素之外的所有元素的请求发送至代理 SalesTotalListModelFilterjava 中的示例假定列表数据是不可变的;过滤器将列表数据事件忽略这里再一次用到前一个示例中的 TerritoryListModel 小结 这些示例已经显示了模型过滤的某些应用过滤是一种应用相当广泛的概念远远不止本文这些相对比较简单的应用当您开始实现过滤器时请记住下列几点 过滤可以向不同组件提供不同的视图并且可以减少应用程序必须支持的完整模型实例的个数 过滤可以应用于 Swing 支持的其他模型包括选择模型 您可以为处理可变模型或者动态模型构造非常复杂的过滤方案为了实现这一点可以用一个过滤器来处理由该代理模型传递的事件 您可以无限地嵌套(或叠用)过滤器但是当每次修改或者查询模型时每个过滤层都会增加一些额外的处理负担 |