用TableModelFree框架简化Swing开发
来源:优易学  2011-11-29 15:47:31   【优易学:中国教育考试门户网】   资料下载   IT书店

  Java™ Desktop 的再介绍”强调了今年的 JavaOne 大会。对于那些抱怨 Swing 太慢、太难使用、界面太难看的开发人员来说,Swing 和 GUI 开发所做的更新努力,并没有带来什么受人欢迎的好消息。如果您最近没有用过 Swing,那么您会很高兴听到其中的许多问题已经得到解决。Swing 被重新设计,它能执行得更好,并能更好地利用 Java 2D API。Swing 的开发者在 1.4 版甚至最新发布的 5.0 版中提高了外观支持。Swing 从没像现在这么好过。
  如果以前曾经用过 JTable,那么您可能也同时被迫使用了 TableModel。您可能还注意到,每个 TableModel 中的所有代码,与其他 TableModel 中的代码几乎是一样的,在编译的 Java 类中,有差异的代码实际上是不存在的。本文将分析 TableModel/JTable 目前的设计方法,说明这种设计的不足,展示为什么它没有实现模型-视图-控制器(MVC)模式的真正目标。您将看到框架和构成 TMF 框架的代码 —— 我以前编写的代码与最常用的开放源代码项目的组合。使用该框架,开发人员可以把 TableModel 的大小从数百行代码减少到只有区区一行,并把重要的表信息放在外部 XML 文件中。在读完本文之后,只使用如下所示的一行代码,您就可以管理您的 JTable 数据:
  1 TableUtilities.setViewToModel("tableconfig.xml", "My Table",
  2 myJTable, CollectionUtilities.observableList(myData));
  3
  JTable 和 TableModel 存在的 MVC 问题
  MVC 已经成为非常流行的 UI 设计模式,因为它把业务逻辑清晰地从数据的视图中分离了出来。Struts 是 MVC 在 Web 上应用的一个非常好的例子。最初,Swing 最大的一个卖点是它采用了 MVC,将视图从模型中分离了出来,代码背后的想法是:代码的模块化程度足够高,所以,不用修改模型中的任何代码,就可以分离出视图。我想,任何用过 JTables 和 TableModels 的人都会发笑,告诉您这是绝对不可能的。使用 MVC 设计模式的理想情况是,在开发人员用 JList 或 JComboBox 替换 JTable 时,可以不用修改表示数据的模式中的代码。但是,在 Swing 中做不到这点。Swing 使得把 JTable、 JList 和 JComboBox 热交换到应用程序中成为不可能,即使所有这三个组件都是用来为相同的数据模型提供视图。对于 Swing 中的 MVC 设计,这是一个严重的不足。如果您想为 JTable 交换 JList,就必须重写视图背后的全部代码,才能实现该交换。
  JTable/TableModel 的另一个 MVC 缺陷是:模型变化的时候,视图不会更新自身。开发人员必须保持对模型的引用,并调用一个函数,这样模型才会告诉视图对自身进行更新;但是,理想的情况应当是:不需要任何额外的代码,就能实现自动更新。
  最后,JTable 和 TableModel 组件设计的问题是,它们彼此之间缠杂得过于密切。如果您修改了 JTable 中的代码,那么您需要确保您没有破坏负责处理的 TableModel,反之亦然。对于一个被认为是在模块化基础上建立的设计模式来说,目前的实现显然是一种存在过多依赖关系的设计。
  TMF 框架更好地遵循了 MVC 的目标,它把 JTable 中视图和模型的工作更加清晰地分离开来。虽然它还没有达到让组件能够热切换的更高目标,但是它已经在正确方向上迈出了一步。
  让我们来检视 TMF 框架,看看它是如何让传统 TableModel 过时的。设计该框架的第一部分是学习 JTable 的使用 —— 开发人员如何使用它,它显示了什么内容,以便了理解哪些东西可以内化、通用化,哪些应当保留可配置状态,以便开发人员配置。对于 TableModel,也要进行同样的思考,我必须确定哪些东西可以从代码中移出,哪些必须留在代码中。一旦找出这些问题,接下来要做的就是确定能够让代码足够通用的最佳技术,以便所有人都能使用它,但是,还要让代码具备足够的可配置性,这也是为了让每个人都能使用它。
  该框架分成三个基本部分:一个能够处理任何类型数据的通用 TableModel、一个外部 XML 文件(负责对不同表中不同的表内容进行配置),以及模型与视图之间的桥。
  在本文中,您可以在 src 文件夹中找到文中介绍的所有源代码。特定于 TMF 的代码位于 com.ibm.j2x.swing.table 包中。
  com.ibm.j2x.swing.table.BeanTableModel
  BeanTableModel 是框架的第一部分。它充当的是通用 TableModel ,您可以用它来处理任何类型的数据。我知道,您可能会说,“您怎么这么肯定它适用于所有的数据呢?”确实,很明显,我不能这么肯定,而且实际上,我确信有一些它不适用的例子。但是从我使用 JTables 的经验来说,我愿意打赌(即使看起来我有点抬杠),实际使用中的 JTables,99% 都是用来显示数据对象列表(也就是说,JavaBeans 组件的 ArrayList)。基于这个假设,我建立了一个通用表模型,它可以显示任何数据对象列表,它就是 BeanTableModel。
  BeanTableModel 大量使用了 Java 的内省机制,来检查 bean 中的字段,显示正确的数据。它还使用了来自 Jakarta Commons Collections 框架的两个类来辅助设计。
  在我深入研究代码之前,请让我解释来自类的几个概念。因为我可以在 bean 上使用内省机制,所以我需要了解 bean 本身的信息,主要是了解字段的名称是什么。我可以通过普通的内省机制来完成这项工作:我可以检查 bean ,找出其字段。但是,对于表来说,这还不够好,因为多数开发人员想让他们的表按照指定顺序显示字段。除此之外,还有一项表需要的信息,我无法通过内省机制从 bean 中获得,即列名消息。所以,为了获得正确显示,对于表中的每个列,您需要两条信息:列名和将要显示的 bean 中的字段。我用键-值对的格式表示该信息,其中,将列名用作键,字段作为值。
  正因为如此,我在这里使用了来自 Collections 框架的适合这项工作的两个类。 BeanMap 用作实用工具类,负责处理内省机制,它接手了内省机制的所有繁琐工作。普通的内省机制开发需要大量的 try / catch 块,对于表来说,这是没有必要的。 BeanMap 把 bean 作为输入,像处理 HashMap 那样来处理它,在这里,键是 bean 中的字段(例如, firstName ),值是 get 方法(例如, getFirstName() )的结果。BeanTableModel 广泛地运用 BeanMap ,消除了操作内省机制的麻烦,也使得访问 bean 中的信息更加容易。
  LinkedMap 是另外一个在 BeanTableModel 中全面应用的类。我们还是回到为列名-字段映射所进行的键-值数据设置,对于数据对象来说,很明显应当选择 HashMap。但是,HashPap 没有保留插入的顺序,对于表来说,这是非常重要的一部分,开发人员希望在每次显示表的时候,都能以指定的顺序显示列。这样,插入的顺序就必须保留。解决方案是 LinkedMap ,它是 LinkedList 与 HashMap 的组合,它既保留了列,也保留了列的顺序信息。参见清单 1,可以查看我是如何用 LinkedMap 和 BeanMap 来设置表的信息的。
  清单1. 用 LinkedMap 和 BeanMap 设置表信息
  1 protected List mapValues = new ArrayList();
  2 protected LinkedMap columnInfo = new LinkedMap();
  3
  4 protected void initializeValues(Collection values)
  5 {
  6 List listValues = new ArrayList(values);
  7 mapValues.clear();
  8 for (Iterator i=listValues.iterator(); i.hasNext();)
  9 {
  10 mapValues.add(new BeanMap(i.next()));
  11 }
  12 }
  在 BeanTableModel 中比较有趣的检查代码实际上是通用 TableModel 的那一部分,这部分代码扩展了 AbstractTableModel 。将清单 2 中的代码与您通常用来建立传统 TableModel 的代码进行比较,您可以看到一些类似之处。
  清单 2. BeanTableModel 中的通用 TableModel 代码
  1 /**
  2 * Returns the number of BeanMaps, therefore the number of JavaBeans
  3 */
  4 public int getRowCount()
  5 {
  6 return mapValues.size();
  7 }
  8 /**
  9 * Returns the number of key-value pairings in the column LinkedMap
  10 */
  11 public int getColumnCount()
  12 {
  13 return columnInfo.size();
  14 }
  15
  16 /**
  17 * Gets the key from the LinkedMap at the specified index (and a
  18 * good example of why a LinkedMap is needed instead of a HashMap)
  19 */
  20 public String getColumnName(int col)
  21 {
  22 return columnInfo.get(col).toString();
  23 }
  24 /**
  25 * Gets the class of the column. A lot of developers wonder what
  26 * this is even used for. It is used by the JTable to use custom
  27 * cell renderers, some of which are built into JTables already
  28 * (Boolean, Integer, String for example). If you write a custom cell
  29 * renderer it would get loaded by the JTable for use in display if that
  30 * specified class were returned here.
  31 * The function uses the BeanMap to get the actual value out of the
  32 * JavaBean and determine its class. However, because the BeanMap
  33 * autoboxes things -- it converts the primitives to Objects for you
  34 * (e.g. ints to Integers) -- the code needs to unautobox it, since the
  35 * function must return a Class Object. Thus, it recognizes any primitives
  36 * and converts them to their respective Object class.
  37 */
  38 public Class getColumnClass(int col)
  39 {
  40 BeanMap map = (BeanMap)mapValues.get(0);
  41 Class c = map.getType(columnInfo.getValue(col).toString());
  42 if (c == null)
  43 return Object.class;
  44 else if (c.isPrimitive())
  45 return ClassUtilities.convertPrimitiveToObject(c);
  46 else
  47 return c;
  48 }

[1] [2] [3] 下一页

责任编辑:小草

文章搜索:
 相关文章
热点资讯
热门课程培训