用BeanTableModel简化Swing

开发 后端
本文介绍用BeanTableModel简化Swing,您可以在 src 文件夹中找到文中介绍的所有源代码。特定于 TMF 的代码位于 com.ibm.j2x.swing.table 包中。

让我们来检视Swing TMF 框架,看看它是如何让传统 TableModel 过时的。设计该框架的第一部分是学习 JTable 的使用 —— 开发人员如何使用它,它显示了什么内容,以便了理解哪些东西可以内化、通用化,哪些应当保留可配置状态,以便开发人员配置。对于 TableModel,也要进行同样的思考,我必须确定哪些东西可以从代码中移出,哪些必须留在代码中。一旦找出这些问题,接下来要做的就是确定能够让代码足够通用的最佳技术,以便所有人都能使用它,但是,还要让代码具备足够的可配置性,这也是为了让每个人都能使用它。

该框架分成三个基本部分:一个能够处理任何类型数据的通用 TableModel、一个外部 XML 文件(负责对不同表中不同的表内容进行配置),以及模型与视图之间的桥。

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. protected void initializeValues(Collection values)  
  4. {  
  5. List listValues = new ArrayList(values);  
  6. mapValues.clear();  
  7. for (Iterator i=listValues.iterator(); i.hasNext();)  
  8. {  
  9. mapValues.add(new BeanMap(i.next()));  
  10. }  

在 BeanTableModel 中比较有趣的检查代码实际上是通用 TableModel 的那一部分,这部分代码扩展了 AbstractTableModel 。将清单 2 中的代码与您通常用来建立传统 TableModel 的代码进行比较,您可以看到一些类似之处。

清单 2. BeanTableModel 中的通用 TableModel 代码

  1. /**  
  2. *ReturnsthenumberofBeanMaps,thereforethenumberofJavaBeans  
  3. */  
  4. publicintgetRowCount()  
  5. {  
  6. returnmapValues.size();  
  7. }  
  8. /**  
  9. *Returnsthenumberofkey-valuepairingsinthecolumnLinkedMap  
  10. */  
  11. publicintgetColumnCount()  
  12. {  
  13. returncolumnInfo.size();  
  14. }  
  15.  
  16. /**  
  17. *GetsthekeyfromtheLinkedMapatthespecifiedindex(anda  
  18. *goodexampleofwhyaLinkedMapisneededinsteadofaHashMap)  
  19. */  
  20. publicStringgetColumnName(intcol)  
  21. {  
  22. returncolumnInfo.get(col).toString();  
  23. }  
  24. /**  
  25. *Getstheclassofthecolumn.Alotofdeveloperswonderwhat  
  26. *thisisevenusedfor.ItisusedbytheJTabletousecustom  
  27. *cellrenderers,someofwhicharebuiltintoJTablesalready  
  28. *(Boolean,Integer,Stringforexample).Ifyouwriteacustomcell  
  29. *rendereritwouldgetloadedbytheJTableforuseindisplayifthat  
  30. *specifiedclasswerereturnedhere.  
  31. *ThefunctionusestheBeanMaptogettheactualvalueoutofthe  
  32. *JavaBeananddetermineitsclass.However,becausetheBeanMap  
  33. *autoboxesthings--itconvertstheprimitivestoObjectsforyou  
  34. *(e.g.intstoIntegers)--thecodeneedstounautoboxit,sincethe  
  35. *functionmustreturnaClassObject.Thus,itrecognizesanyprimitives  
  36. *andconvertsthemtotheirrespectiveObjectclass.  
  37. */publicClassgetColumnClass(intcol)  
  38. {  
  39. BeanMapmap=(BeanMap)mapValues.get(0);  
  40. Classc=map.getType(columnInfo.getValue(col).toString());  
  41. if(c==null)  
  42. returnObject.class;  
  43. elseif(c.isPrimitive())  
  44. returnClassUtilities.convertPrimitiveToObject(c);  
  45. else  
  46. returnc;  
  47. }  
  48. /**  
  49. *TheBeanTableModelautomaticallyreturnsfalse,andifyou  
  50. *needtomakeaneditabletable,you'llhavetosubclass  
  51. *BeanTableModelandoverridethisfunction.  
  52. */  
  53. publicbooleanisCellEditable(introw,intcol)  
  54. {  
  55. returnfalse;  
  56. }  
  57. /**  
  58. *ThefunctionthatreturnsthevaluethatyouseeintheJTable.Itgets  
  59. *theBeanMapwrappingtheJavaBeanbasedontherow,itusesthe  
  60. *columnnumbertogetthefieldfromthecolumninformationLinkedMap,  
  61. *andthenusesthefieldtoretrievethevalueoutoftheBeanMap.  
  62. */  
  63. publicObjectgetValueAt(introw,intcol)  
  64. {  
  65. BeanMapmap=(BeanMap)mapValues.get(row);  
  66. returnmap.get(columnInfo.getValue(col));  
  67. }  
  68. /**  
  69. *TheoppositefunctionofthegetValueAt--itduplicatestheworkofthe  
  70. *getValueAt,butinsteadputstheObjectvalueintotheBeanMapinstead  
  71. *ofretrievingitsvalue.  
  72. */  
  73. publicvoidsetValueAt(Objectvalue,introw,intcol)  
  74. {  
  75. BeanMapmap=(BeanMap)mapValues.get(row);  
  76. map.put(columnInfo.getValue(col),value);  
  77. super.fireTableRowsUpdated(row,row);  
  78. }  
  79.  
  80. /**  
  81. *TheBeanTableModelimplementstheCollectionListenerinterface  
  82. *(1ofthe3partsoftheframework)andthuslistensforchangesinthe  
  83. *dataitismodelingandautomaticallyupdatestheJTableandthe  
  84. *modelwhenachangeoccurstothedata.  
  85. */  
  86. publicvoidcollectionChanged(CollectionEvente)  
  87. {  
  88. initializeValues((Collection)e.getSource());  
  89. super.fireTableDataChanged();  

正如您所看到的,BeanTableModel 的整个 TableModel 足够通用化,可以在任何表中使用。它充分利用了内省机制,省去了所有特定于 bean 的编码工作,在传统的 TableModel 中,这类编码工作绝对是必需的 —— 同时也是完全冗余的。BeanTableModel 还可以在 TMF 框架之外使用,虽然在外面使用会丧失一些威力和灵活性。

#p#

看过这段代码之后,您会提出两个问题。首先,BeanTableModel 从哪里获得列名-字段与键-值配对的信息?第二,到底什么是 ObservableCollection ?这些问题会将我们引入框架的接下来的两个部分。这些问题的答案以及更多的内容,将在本文后面接下来的章节中出现。

Swing Castor XML 解析器

保存必需的列名-字段信息的最合理的位置位于 Java 类之外,这样,不需要再重新编译 Java 代码,就可以修改这个信息。因为关于列名和字段的信息是 TMF 框架中惟一明确与表有关的信息,这意味着整个表格都可以在外部进行配置。

显然,该解决方案会自然而然把 XML 作为配置文件的语言选择。配置文件必须为多种表模型保存信息;您还需要能够用这个文件指定每个列中的数据。配置文件还应当尽可能地易于阅读,因为开发人员之外的人员有可能要修改它。

这些问题的最佳解决方案是 Castor XML 解析器。查看 Castor 实际使用的最佳方法就是查看如何在框架中使用它。

让我们来考虑一下配置文件的目的:保存表模型和表中列的信息。 XML 文件应当尽可能简单地显示这些信息。TMF 框架中的 XML 文件用清单 3 所示的格式来保存表模型信息。

清单3. TMF 配置文件示例

  1. <model> 
  2. <className>demo.hr.TableModelFreeExampleclassName> 
  3. <name>Hirename> 
  4. <column> 
  5. <name>First Namename> 
  6. <field>firstNamefield> 
  7. column> 
  8. <name>Last Namename> 
  9. <field>lastNamefield> 
  10. column> 
  11. model> 

与这个目的相反的目标是,开发人员必须处理的 Java 对象应当像 XML 文件一样容易理解。通过 Castor XML 解析器用来存储http://storage.it168.com/" target=_blank>存储列信息的三个 Java 对象,就可以看到这一点,这三个对象是: TableData (存储文件中的所有表模型)、 TableModelData (存储特定于表模型的信息)和 TableModelColumnData (存储列信息)。这三个类提供了 Java 开发人员所需的所有包装器,以便得到有关 TableModel 的所有必要信息。

将所有这些包装在一起所缺少的一个环节就是 映射文件,它是一个 XML 文件,Castor 用它把简单的 XML 映射到简单的 Java 对象中。在完美的世界中,映射文件也应当很简单,但事实要比这复杂得多。良好的映射文件要使别的一切东西都保持简单;所以一般来说,映射文件越复杂,配置文件和 Java 对象就越容易处理。映射文件所做的工作顾名思义就是把 XML 对象映射到 Java 对象。清单 4 显示了 TMF 框架使用的映射文件。

清单 4. TMF 框架使用的 Castor 映射文件

  1. xmlversionxmlversion="1.0"?> 
  2. <mapping> 
  3. <description>Amappingfileforexternalizedtablemodelsdescription> 
  4. <classnameclassname="com.ibm.j2x.swing.table.TableData"> 
  5. <map-toxmlmap-toxml="data"/> 
  6. <fieldnamefieldname="tableModelData"collection="arraylist"type=  
  7. "com.ibm.j2x.swing.table.TableModelData"> 
  8. <bind-xmlnamebind-xmlname="tableModelData"/> 
  9. field> 
  10. class> 
  11. <classnameclassname="com.ibm.j2x.swing.table.TableModelData"> 
  12. <map-toxmlmap-toxml="model"/> 
  13. <fieldnamefieldname="className"type="string"> 
  14. <bind-xmlnamebind-xmlname="className"/> 
  15. field> 
  16. <fieldnamefieldname="name"type="string"> 
  17. <bind-xmlnamebind-xmlname="name"/> 
  18. field> 
  19. <fieldnamefieldname="columns"collection="arraylist"type=  
  20. "com.ibm.j2x.swing.table.TableModelColumnData"> 
  21. <bind-xmlnamebind-xmlname="columns"/> 
  22. field> 
  23. class> 
  24. <classnameclassname="com.ibm.j2x.swing.table.TableModelColumnData"> 
  25. <map-toxmlmap-toxml="column"/> 
  26. <fieldnamefieldname="name"type="string"> 
  27. <bind-xmlnamebind-xmlname="name"/> 
  28. field> 
  29. <fieldnamefieldname="field"type="string"> 
  30. <bind-xmlnamebind-xmlname="field"/> 
  31. field> 
  32. class> 
  33. mapping> 

仅仅通过观察这段代码,您就可以看出,映射文件清晰地勾划出了每个用来存储表模型信息的类,定义了类的类型,并将 XML 文件中的名称连接到了 Java 对象中的字段。请保持相同的名称,这样会让事情简单、更好管理一些,但是没必要保持名称相同。

到现在为止,列名和字段信息都已外部化,可以读入包含列信息的 Java 对象中,并且可以很容易地把信息发送给 BeanTableModel,并用它来设置列。

Swing ObservableCollection

TMF 框架的最后一个关键部分,就是 ObservableCollection 。您们当中的某些人可能熟悉 ObservableCollection 的概念,它是 Java Collections 框架的一个成员,在被修改的时候,它会抛出事件,从而允许其侦听器根据这些事件执行操作。虽然从来没有将它引入 Java 语言的正式发行版中,但在 Internet 上,这个概念已经有了一些第三方实现。就本文而言,我使用了自己的 ObservableCollection 实现,因为框架只需要一些最基本的功能。我的实现使用了一个称为 collectionChanged() 的方法,每次发生修改时, ObservableCollection 都会在自己的侦听器上调用该方法。也可以将该用法称为 Collection 类的 Decorator(有关 Collections 的 Decorator 更多信息,请参阅 Collections 框架的站点),只需要增加几行代码,您就可以在普通的 Collection 类中创建 Collection 类的 Observable 实例。清单 5 显示了 ObservableCollection 用法的示例。(这只是一个示例,没有包含在 j2x.zip 中。)

清单 5. ObservableCollection 用法示例

  1. convert a normal list to an ObservableList  
  2. ObservableList oList = CollectionUtilities.observableList(list);  
  3. // A listener could then register for events from this list by calling  
  4. oList.addCollectionListener(this);  
  5. // trigger event  
  6. oList.add(new Integer(3));  
  7. // listener receives event  
  8. public void collectionChanged(CollectionEvent e)  
  9. {  
  10. // event received here}  

ObservableCollection 有许多 TMF 框架之外的应用程序。如果您决定采用 TMF 框架,您会发现,在开发代码期间, ObservableCollection 框架有许多实际的用途。

但是,它在 TMF 框架中的用途,重点在于它能更好地定义视图和模型之间的关系,当数据发生变化时,可以自动更新视图。您可以回想一下,这正是传统 TableModel 的最大限制,因为每当数据发生变化时,都必须用表模型的引用来更新视图。而在 TMF 框架中使用 ObservableCollection 时,当数据发生变化时,视图会自动更新,不需要维护一个到模型的引用。在 BeanTableModel 的 collectionChanged() 方法的实现中,您可以看到这一点。

Swing TableUtilities

在该框架中执行的最后一步操作,是将所有内容集成到一些实用方法中,让 TMF 框架使用起来简单明了。这些实用方法可以在 com.ibm.j2x.swing.table.TableUtilities 类中找到,该类提供了您将需要的所有辅助函数:

getColumnInfo() :该实用方法用 Castor XML 文件解析指定的文件,并返回指定表模型的所有列信息,返回的形式是 BeanTableModel 所需的 LinkedMap 。当开发人员选择从 BeanTableModel 中派生子类时,这个方法很重要。

getTableModel() :该实用方法是建立在上面的 getColumnInfo() 方法之上,它获得列的信息,然后把信息传递给 BeanTableModel,返回已经设置好所有信息的 BeanTableModel。

setViewToModel() :该实用方法是最重要的函数,也是 TMF 框架的主要吸引人的地方。它也是建立在 getTableModel() 方法之上,也有一个到 JTable 的引用(JTable 中有这个表的模型),以及一个到数据(要在表中显示)的引用。它对 JTable 上的 TableModel 进行设置,并把数据传递给 TableModel,结果是:只需一行代码,就为 JTable 完成了 TableModel 的设置。TMF 框架在该方法上得到了最佳印证,TableModel 将永远地被下面这个简单的方法所代替:

  1. TableUtilities.setViewToModel("table_config.xml", "Table", myJTable, myList);  

【编辑推荐】

  1. 浅谈学习Swing组件
  2. Swing使用invokeLater()方法
  3. 浅析Swing组件的规则
  4. 详解Swing中JTree组件的功能
  5. 简单介绍Swing外观
责任编辑:佚名 来源: 中国IT实验室
相关推荐

2009-06-29 15:28:00

TableModelSwing开发

2009-07-14 09:52:10

TableModelESwing

2009-07-14 09:28:26

JTable和TablSwing

2017-04-19 08:47:42

AsyncJavascript异步代码

2019-08-29 08:00:00

微服务架构服务网格

2011-11-04 10:22:43

Java Mail

2012-01-16 12:53:51

JavaSwing

2012-02-16 09:11:34

Swing

2011-04-02 09:34:38

2009-07-15 15:35:59

Swing程序Swing性能

2009-07-16 08:53:03

Swing任务Swing线程

2009-07-16 16:01:55

EventQueue

2021-03-03 23:20:51

机器学习保险人工智能

2009-07-10 10:37:50

Swing Set示例

2009-07-15 13:06:38

Swing组件

2009-07-14 18:28:58

Swing入门

2009-07-15 11:19:17

invokeLaterSwing

2009-07-10 13:36:32

Swing容器

2009-07-15 14:29:24

构造JListSwing

2011-04-12 10:04:01

OPLCFTTH
点赞
收藏

51CTO技术栈公众号