JList组件是用于由一个选项集合中选择一个或多个项目的基本Swing组件。我们向用户展示选项列表,依据于组件的选择模式,用户可以选择一个或多个。
三个关键元素及其实现定义了JList结构:
JList组件有四个构造函数,可以允许我们基于我们的初始数据结构创建JList实例:
public JList() JList jlist = new JList(); public JList(Object listData[]) String labels[] = { "Chardonnay", "Sauvignon", "Riesling", "Cabernet", "Zinfandel", "Merlot", "Pinot Noir", "Sauvignon Blanc", "Syrah", "Gewürztraminer"}; JList jlist = new JList(labels); public JList(Vector listData) Vector vector = aBufferedImage.getSources(); JList jlist = new JList(vector); public JList(ListModel model) ResultSet results = aJDBCStatement.executeQuery("SELECT colName FROM tableName"); DefaultListModel model = new DefaultListModel(); while (result.next()) model.addElement(result.getString(1)); JList jlist = new JList(model);
如果我们使用无参数的构造函数,我们可以稍后填充数据。然而,如果我们使用数组或是Vector构造函数,如果不修改整个模式,那么我们就不能修改内容。
注意,如果我们希望显示一些内容而不是每一个数组元素的toString()结果,可以查看本章稍后的“渲染JList元素”来了解如何实现。
在创建了JList组件之后,我们可以修改其每一个属性。表13-5显示了JList的32个属性。
JList属性中的多个都与选择过程有关。例如,anchorSelectionIndex, leadSelectionIndex, maxSelectionIndex, minSelectionIndex, selectedIndex与selectedIndices处理被选中行的索引,而selectedValue与selectedValues与被选中元素的内容有关。anchorSelectionIndex是ListDataEvent最近的index0,而leadSelectionIndex则是最近的index1。
要控制所显示的可视行的数目,设置JList的visibleRowCount属性。这个属性的默认设置为8。
当我们使用JList组件时,如果我们希望允许用户在所有的选项中进行选择,我们必须将组件放置在一个JScrollPane中。如果我们没有将其放置在一个JScrollPane中,而默认显示的行数小于数据模型的尺寸,或者是没有足够的空间来显示行,则其他的选项不会显示。当放置在JScrollPane中时,JList提供了一个垂直滚动条来在所有的选项中移动。
如果我们没有将JList放置在JScrollPane中,并且选项的数目超出了可用空间时,只有上部的选项组可见,如图13-4所示。
提示,当我们看到了一个类实现了Scrollable接口,我们就应该想起在将其添加到程序之前需要将其放在JScrollPane中。
JScrollPane依赖于preferredScrollableViewportSize属性设置所提供的维度来确定面板内容的最优尺寸。当JList的数据模型为空时,则会使用每个可见行16像素高256像素宽的默认尺寸。否则宽度通过遍历所有的行来查找最宽的行来确定,而高度是通过第一行的高度来确定。
为了快速确定JScrollPane视图区域的尺寸,我们可以通过设置prototypeCellValue属性来定义一个原型行。我们必须保证原型toString()的值足够宽与足够高从而适应JList中的所用内容。然后JScrollPane基于原型视图区域的尺寸,从而JList就没有必要来询问每一行的尺寸;相反,只需要询问原型的尺寸即可。
我们也可以通过为fixedCellHeight与fixedCellWidth属性指定尺寸来改善性能。设置这些属性是避免JList询问每一行渲染尺寸的另一种方法。设置两个属性是使得JList确定在视图区域中尺寸的最快的方法。当然,这也是最不灵活的方法,因为他保证当内容发生变化时JList的选项不会变宽(或变短)。然而,如果我们在数据模型中有大量的条目,这些灵活性的缺失对于改善性能是值得的。图13-5帮助我们理解JList的一些尺寸功能。
用来生成图13-5的源码显示在列表13-3中。图中间的列表包含超出1000个固定尺寸的行。顶部的列表显示了我们可以通过setVisibleRowCount()方法设置可视行的数目。然而,因为列表并没有位于JScrollPane中,行数目限制的请求会被忽略。
package swingstudy.ch13; import java.awt.BorderLayout; import java.awt.EventQueue; import javax.swing.DefaultListModel; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JScrollPane; public class SizingSamples { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { String labels[] = { "Chardonnay", "Sauvignon", "Riesling", "Cabernet", "Zinfandel", "Merlot", "Pinot Noir", "Sauvignon Blanc", "Syrah", "Gewurztraminer"}; JFrame frame = new JFrame("Sizing Samples"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JList jlist1 = new JList(labels); jlist1.setVisibleRowCount(4); DefaultListModel model = new DefaultListModel(); for(int i=0; i<100; i++) { for(int j=0; j<10; j++) { model.addElement(labels[j]); } } JScrollPane scrollPane1 = new JScrollPane(jlist1); frame.add(scrollPane1, BorderLayout.NORTH); JList jlist2 = new JList(model); jlist2.setVisibleRowCount(4); jlist2.setFixedCellHeight(12); jlist2.setFixedCellWidth(200); JScrollPane scrollPane2 = new JScrollPane(jlist2); frame.add(scrollPane2, BorderLayout.CENTER); JList jlist3 = new JList(labels); jlist3.setVisibleRowCount(4); frame.add(jlist3, BorderLayout.SOUTH); frame.setSize(300, 350); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
除了将JList放在JScrollPane中以外,我们可以确定哪些选项是可见或是请求特定的元素可见。firstVisibleIndex与lastVisibleIndex属性使得我们可以确定在JScrollPane中哪些选项是当前可见的。如果没有选项可见,两个方法都会返回-1;这通常在数据模型为空的情况下发生。要请求一个特定的元素可见,使用public void ensureIndexIsVisible(int index)方法。例如,要编程将列表移动到顶部可以使用下面的代码:
jlist.ensureIndexIsVisible(0);
JList中的每一个元素被称之为单元。每一个JList都有一个已安装的单元渲染器,当列表需要绘制时渲染器绘制每一个单元。默认的渲染器,DefaultListCellRenderer是JLable的一个子类,这就意味着我们可以使用文本或是图标作为单元的图形显示。这可以满足大多数用户的需要,但是有时单元的外观需要进行某些定制。耏 一,每一个JList至多只有一个安装的渲染器,自定义需要我们替换已安装的渲染器。
ListCellRender接口与DefaultListCellRenderer类
JList有一个已安装的渲染器。实现了ListCellRender接口的类提供了这个渲染器。
public interface ListCellRenderer { public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus); }
当需要绘制单元时,接口的核心方法被调用。返回的渲染器为列表中的单元提供特定的渲染。Jlist使用渲染来绘制元素,然后获取下一个渲染器。
一个到JList的引用会被提供给getListCellRendererComponent()方法,从而渲染器可以共享显示特性。选中的value包含列表数据模型在位置index上的对象。索引由数据模型的开始处由0开始。最后两个参数允许我们基于单元的状态自定义单元的外观,也就是他是否被选中或是具有输入焦点。
列表13-4显示了一个演示这种技术的渲染器。这个渲染器的核心不同之处在于具有输入焦点的单元有一个带有标题的边框。在渲染器被创建之后,我们通过设置JList的cellRenderer属性来安装。
提示,由于性能的原因,最好不要在getListCellRendererComponent()方法创建实际的渲染器。可以派生Component并返回this或是创建一个类变量来存储Component的实例,然后进行自定义并返回。
package swingstudy.ch13; import java.awt.Component; import javax.swing.DefaultListCellRenderer; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.ListCellRenderer; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import javax.swing.border.LineBorder; import javax.swing.border.TitledBorder; public class FocusedTitleListCellRenderer implements ListCellRenderer { protected static Border noFocusBorder = new EmptyBorder(15, 1, 1, 1); protected static TitledBorder focusBorder = new TitledBorder(LineBorder.createGrayLineBorder(), "Focused"); protected DefaultListCellRenderer defaultRenderer = new DefaultListCellRenderer(); public String getTitle() { return focusBorder.getTitle(); } public void setTitle(String newValue) { focusBorder.setTitle(newValue); } @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { // TODO Auto-generated method stub JLabel renderer = (JLabel)defaultRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); renderer.setBorder(cellHasFocus ? focusBorder : noFocusBorder); return renderer; } }
注意,当创建我们自己的渲染器时一个觉错误就是忘记使得渲染器组件非透明。这会使得渲染器的背景颜色被忽略,并且列表容器的背景外漏。使用DefaultListCellRenderer类,渲染器组件已经是不透明的了。
列表13-5显示了一个使用这个新渲染器的示例程序。他并没有做任何特殊的事情,而只是安装了刚才创建的自定义单元渲染器。
package swingstudy.ch13; import java.awt.BorderLayout; import java.awt.EventQueue; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JScrollPane; import javax.swing.ListCellRenderer; public class CustomBorderSample { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { String labels[] = {"Chardonnay", "Sauvignon", "Riesling", "Cabernet", "Zinfandel", "Merlot", "Pinot Noir", "Sauvignon Blanc", "Syrah", "Gewurztraminer" }; JFrame frame = new JFrame("Custom Border"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JList jlist = new JList(labels); ListCellRenderer renderer = new FocusedTitleListCellRenderer(); jlist.setCellRenderer(renderer); JScrollPane sp = new JScrollPane(jlist); frame.add(sp, BorderLayout.CENTER); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
图13-6显示了示例程序的输出。
创建复杂的ListCellRenderer
有时,当数据模型由每个元素中的多个复杂数据组成,不能由文本字符串表示的内容,自定义的单元渲染器(类似于图13-6所示)是必需的。例如,列表13-6显示了一个示例的源码,其中每一个数据模型的元素由字体,前景色,图标与文本字符串组成。保证渲染器中这些元素的正确使用简单的涉及到在配置渲染器组件方面的更多工作。在这个特定的例子中,数据存储在数据模型中数组的每个元素之中。我们可以简单的定义一个新类或是使用散列表。
package swingstudy.ch13; import java.awt.Color; import java.awt.Component; import java.awt.Font; import javax.swing.DefaultListCellRenderer; import javax.swing.Icon; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.ListCellRenderer; public class ComplexCellRenderer implements ListCellRenderer { protected DefaultListCellRenderer defaultRenderer = new DefaultListCellRenderer(); @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { // TODO Auto-generated method stub Font theFont = null; Color theForeground = null; Icon theIcon = null; String theText = null; JLabel renderer = (JLabel)defaultRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); if(value instanceof Object[]) { Object values[] = (Object[])value; theFont = (Font)values[0]; theForeground = (Color)values[1]; theIcon = (Icon)values[2]; theText = (String)values[3]; } else { theFont = list.getFont(); theForeground = list.getForeground(); theText = ""; } if(!isSelected) { renderer.setForeground(theForeground); } if(theIcon != null) { renderer.setIcon(theIcon); } renderer.setText(theText); renderer.setFont(theFont); return renderer; } }
渲染器很少自定义DefaultListCellRenderer返回的渲染器组件。自定义基于作为数组传递给getListCellRendererComponent()方法的value参数的数据模型值。列表13-7显示了测试类。这个演示程序重用了第4章所创建的DiamondIcon。大部分代码用于数据模型的初始化。
package swingstudy.ch13; import java.awt.BorderLayout; import java.awt.Color; import java.awt.EventQueue; import java.awt.Font; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JScrollPane; import javax.swing.ListCellRenderer; import swingstudy.ch04.DiamondIcon; public class ComplexRenderingSample { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { Object elements[][] = { {new Font("Helvetica", Font.PLAIN, 20), Color.RED, new DiamondIcon(Color.BLUE), "Help"}, {new Font("TimesRoman", Font.BOLD, 14), Color.BLUE, new DiamondIcon(Color.GREEN), "Me"}, {new Font("Courier", Font.ITALIC, 18), Color.GREEN, new DiamondIcon(Color.BLACK), "I'm"}, {new Font("Helvetica", Font.BOLD|Font.ITALIC, 12), Color.GRAY, new DiamondIcon(Color.MAGENTA), "Trapped"}, {new Font("TimesRoman", Font.PLAIN, 32), Color.PINK, new DiamondIcon(Color.YELLOW), "Inside"}, {new Font("Courier", Font.BOLD, 16), Color.YELLOW, new DiamondIcon(Color.RED), "This"}, {new Font("Helvetica", Font.ITALIC, 8), Color.DARK_GRAY, new DiamondIcon(Color.PINK), "Computer"} }; JFrame frame = new JFrame("Complex Renderer"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JList jlist = new JList(elements); ListCellRenderer renderer = new ComplexCellRenderer(); jlist.setCellRenderer(renderer); JScrollPane scrollPane = new JScrollPane(jlist); frame.add(scrollPane, BorderLayout.CENTER); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
程序的输出结果如图13-7所示。
提示,当我们创建自己的渲染组件时,我们将会发现最好由默认的列表单元渲染器开始。这可以使得我们专注于我们感兴趣的特定细节。否则,我们就需要考虑所有事情,例如默认选择的前景与背景颜色,以及我们是否记得使得组件非透明。当然,如果我们希望亲自配置所有事情,自由去做就是了。
默认情况下,所有的JList组件处于多项选择模式。这意味着我们可以选择组件中的多个元素。我们如何选择多个元素依赖于我们正在使用的用户界面。例如,对于Ocean观感界面,Ctrl-select可以作为选择切换,而Shift-select可以作为一种范围选择的方法。
ListSelectionModel接口与DefaultListSelectionModel类
ListSelectionModel接口的实现控制JList组件的选择机制。在这里显示的接口定义定义了用于不同选择模式的常量并且描述了如何管理ListSelectionListener对象的列表。他同时提供了一种方法来描述多个内部选择。
public interface ListSelectionModel { // Constants public final static int MULTIPLE_INTERVAL_SELECTION; public final static int SINGLE_INTERVAL_SELECTION; public final static int SINGLE_SELECTION; // Properties public int getAnchorSelectionIndex(); public void setAnchorSelectionIndex(int index); public int getLeadSelectionIndex(); public void setLeadSelectionIndex(int index); public int getMaxSelectionIndex(); public int getMinSelectionIndex(); public boolean isSelectionEmpty(); public int getSelectionMode(); public void setSelectionMode(int selectionMode); public boolean getValueIsAdjusting(); public void setValueIsAdjusting(boolean valueIsAdjusting); // Listeners public void addListSelectionListener(ListSelectionListener x); public void removeListSelectionListener(ListSelectionListener x); // Other methods public void addSelectionInterval(int index0, int index1); public void clearSelection(); public void insertIndexInterval(int index, int length, boolean before); public boolean isSelectedIndex(int index); public void removeIndexInterval(int index0, int index1); public void removeSelectionInterval(int index0, int index1); public void setSelectionInterval(int index0, int index1); }
其中有三个不同的选择模式。表13-6中包含了每个模式的名字及其描述。
图13-8显示了每个选择模式的结果。
要修改JList的选择模式,将selectionModel属性设置为表13-6中的一个ListSelectionModel常量。例如,下面的代码可以将一个列表的修改为单选模式:
JList list = new JList(...); list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
DefaultListSelectionModel类是ListSelectionModel接口的默认实现。我们可以尝试表13-7中所示的九个属性来了解当前的选中范围。
当selectionEmpty属性为false时选择模式可以显示我们当前在多项选择模式中使用的是哪一种。如果是使用public boolean isSelectedIndex(int index)方法选中的,则只需要简单的查询最小与最大选中索引中的每一个索引。因为多项选择模式支持不连续的区域,这是确定哪一个被选中的唯一方法。然而,JList的selectedIndeices属性提供了这种信息,而不需要我们手动检测。
使用ListSelectionListener监听JList事件
如果我们希望了解何时JList的元素被选中,我们需要向JList或是ListSelectionModel关联一个ListSelectionListener。Jlist的addListListSelectionListener()与removeListSelectionListener()方法只会委托给底层的ListSelectionModel。当被选中的元素集合发生变化时,所关联的监听器对象会得到通知。接口定义如下:
public interface ListSelectionListener extends EventListener { public void valueChanged(ListSelectionEvent e); }
监听器所接收的ListSelectionEvent实例描述了这个选中事件的所影响的元素的范围以及选中是否仍在变化,如表13-8所示。当用户仍在修改被选中的元素,通过valueIsAdjusting设置为true,我们也许会希望延迟执行耗费资源的操作,例如绘制一个高分辨率的图形显示。
为了演示JList的选中,列表13-8中所示的程序向窗口添加了一个JTextArea来显示选中监听器的输出。监听器输出当前被选中的项的位置与值。
package swingstudy.ch13; import java.awt.BorderLayout; import java.awt.EventQueue; import java.io.PrintWriter; import java.io.StringWriter; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; public class SelectingJListSample { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { String labels[] = {"Chardonnay", "Sauvignon", "Riesling", "Cabernet", "Zinfandel", "Merlot", "Pinot Noir", "Sauvignon Blanc", "Syrah", "Gewurztraminer" }; JFrame frame = new JFrame("Selecting Jlist"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JList jlist = new JList(labels); JScrollPane scrollPane1 = new JScrollPane(jlist); frame.add(scrollPane1, BorderLayout.WEST); final JTextArea textArea = new JTextArea(); textArea.setEditable(false); JScrollPane scrollPane2 = new JScrollPane(textArea); frame.add(scrollPane2, BorderLayout.CENTER); ListSelectionListener listSelectionListener= new ListSelectionListener() { public void valueChanged(ListSelectionEvent event) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); pw.println("First index: "+event.getFirstIndex()); pw.println(", Last idnex: "+event.getLastIndex()); boolean adjust = event.getValueIsAdjusting(); pw.println(", Adjusting? "+adjust); if(!adjust) { JList list = (JList)event.getSource(); int selections[] = list.getSelectedIndices(); Object selectionValues[] = list.getSelectedValues(); for(int i=0, n=selections.length; i<n; i++) { if(i==0) { pw.println(" Selections: "); } pw.print(selections[i]+"/"+selectionValues[i]+" "); } pw.println(); } textArea.append(sw.toString()); } }; jlist.addListSelectionListener(listSelectionListener); frame.setSize(350, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
注意,如果我们知道JList处理单项选择模式中,我们可以使用selectedIndex或是selectedValue属性来获取当前被选中的项目。
图13-9显示了程序运行的结果。
列表13-8中的示例在没有快速更新时只输出当前选中的项(当isAdjusting报告false)时。否则,程序仅报告选中范围变化的起始与结束,以及调整状态。程序会检测JList的selectedIndices与selectedValues属性来获得选中项的有序列表。slectedIndices与slectedValues数组以相同的方式排序,所以数据模式中的特定元素显示在两个列表中的相同位置。
并没有特殊的选中事件用于处理列表中元素的双击。如果我们对双击感兴趣,那我们就要求助于AWT的MouseEvent/MouseListener对了。将下面的代码添加到列表13-8的程序中会向TextArea添加相应的文本用于响应双击事件。这里的关键方法是JList的public int locationToIndex(Point location),他会将屏幕坐标映射到列表元素。
import java.awt.event.*; ... MouseListener mouseListener = new MouseAdapter() { public void mouseClicked(MouseEvent mouseEvent) { JList theList = (JList)mouseEvent.getSource(); if (mouseEvent.getClickCount() == 2) { int index = theList.locationToIndex(mouseEvent.getPoint()); if (index >= 0) { Object o = theList.getModel().getElementAt(index); textArea.append("Double-clicked on: " + o.toString()); textArea.append(System.getProperty("line.separator")); } } } }; jlist.addMouseListener(mouseListener);
注意,JList类同时提供了一个public Point indexToLocation(int index)方法,这个方法会生成相反的行为。
手动选择JList事件
除了检测用户何时选择了列表中的项以外,我们也可以编程实现了列表项的选中与非选中。如果ListSelectionListener对象被叛逆到JList,当选中的项目集合被编程修改时他们也会得到相应的通知。可以使用下面的方法:
通常,当我们使用JList时,我们在单列中的显示其选项。尽管这是通常的使用方法,Swing JList控件为在多列中显示其选项提供了支持。借且于setLayoutOrientation()方法,我们可以设置JList方法来在水平列或是垂直列中的布局其单元。JList.VERTICAL是默认设置,其中所有的选项在一列中的显示。
要水平布局单元,在进入到下一行之前,使用值JList.HORIZONTAL_WRAP。例如,一个具有九个元素的列表可以以下面的方式显示:
要垂直布局单元,在进入下一列之前,使用值JList.VERTICAL_WRAP。例如,一个具有九个元素的列表可以以下面的方式显示:
设置JList的visibleRowCount属性来控制行数。否则,列表的宽度决定HORIZONTAL_WRAP的行数,而列表的高度决定VERTICAL_WRAP的列数。
图13-10显示了一个具有水平换行的JList,其中显示了一个3x3的网格。注意,他仍然支持多项选中模式。
每一个可安装的Swing观感都提供了不同的JList外观以及用于组件的默认的UIResource值设置集合。图13-11显示了JList在预安装的观感类型集合Motif,Windows以及Ocean下的外观。
类似于大多数的UIResource属性,大多数属性的名字都是自解释的。而List.timeFactor在这里需要解释一下。默认情况下,JList具有键盘选中的行为。当我们输入时,JList会查找到目前为止与我们的输入匹配的项。这是借助于public int getNextMatch(String prefix, int startIndex, Position.Bias bias)方法来实现的。“到目前为止”的量是由List.timeFactory设置来控制的。只要两次击键之间有延迟没有超出List.timeFactory指定的毫秒数(默认为1000),所输入的新键就会添加到前一个键序列中。一旦工厂超时,搜索字符串就会被重置。
这一节所展示的示例创建了一个新的名为DualListBox的Swing组件。双列表框的基本目的就是创建两个选项列表:一个用于选取,而另一个构成结果集。当初始选项列表是可调整的时双列表框十分有用。尝试由一个跨越多个屏幕包含多个选项的JList中进行多项选择是一个麻烦的事情,特别是由于我们没有按下Shift或是Ctrl组合键时而恰好取消了我们已经选中的选项时。使用双列表模式,用户在每一个列表中选择选项并将其移动到第二个列表中。用户可以很容易的在两个列表之间进行滚动而不无需担心偶然取消了某了个选项。图13-12显示使用中的DualListBox的样子。
要使用这个自定义组件,通过调用构造函数DualListBox sdual = new DualListBox()来进行创建,然而使用setSourceElements()或addSourceLements()方法使用数据对其进行填充;每个方法都需要一个ListModel或是一个数组参数。add版本会补充已存在的选项,而set版本会首先清除已存在选项。当需要向组件查询用户选中了哪些选项时,我们可以使用destinationIterator()方法向已选中元素的Iterator进行查询。我们也许希望修改的属性如下所示:
下面显示了这个新的DualListBox组件的完整源码。列表13-9包含了每一个类SortedListModel,他提供了一个已排序的ListModel。在其内部,他利用了TreeSet。
package swingstudy.ch13; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.SortedSet; import java.util.TreeSet; import javax.swing.AbstractListModel; public class SortedListModel extends AbstractListModel { SortedSet<Object> model; public SortedListModel() { model = new TreeSet<Object>(); } @Override public Object getElementAt(int index) { // TODO Auto-generated method stub return model.toArray()[index]; } @Override public int getSize() { // TODO Auto-generated method stub return model.size(); } public void add(Object element) { if(model.add(element)) { fireContentsChanged(this, 0, getSize()); } } public void addAll(Object elements[]) { Collection<Object> c = Arrays.asList(elements); model.addAll(c); fireContentsChanged(this, 0, getSize()); } public void clear() { model.clear(); fireContentsChanged(this, 0, getSize()); } public boolean contains(Object element) { return model.contains(element); } public Object firstElement() { return model.first(); } public Iterator iterator() { return model.iterator(); } public Object lastElement() { return model.last(); } public boolean removeElement(Object element) { boolean removed = model.remove(element); if(removed) { fireContentsChanged(this, 0, getSize()); } return removed; } }
列表13-10显示了DualListBox的源码。其中包含的main()方法演示了该组件。
package swingstudy.ch13; import java.awt.BorderLayout; import java.awt.Color; import java.awt.EventQueue; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Iterator; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ListCellRenderer; import javax.swing.ListModel; public class DualListBox extends JPanel { private static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0); private static final String ADD_BUTTON_LABEL = "Add >>"; private static final String REMOVE_BUTTON_LABEL = "<< Remove"; private static final String DEFAULT_SOURCE_CHOICE_LABEL = "Advailable Choices"; private static final String DEFAULT_DEST_CHOICE_LABEL = "Your Choices"; private JLabel sourceLabel; private JList sourceList; private SortedListModel sourceListModel; private JList destList; private SortedListModel destListModel; private JLabel destLabel; private JButton addButton; private JButton removeButton; public DualListBox() { initScreen(); } public String getSourceChoicesTitle() { return sourceLabel.getText(); } public void setSourceChoicesTitle(String newValue) { sourceLabel.setText(newValue); } public String getDestinationChoicesTitle() { return destLabel.getText(); } public void setDestinationChoicesTitle(String newValue) { destLabel.setText(newValue); } public void clearSourceListModel() { sourceListModel.clear(); } public void clearDestinationListModel() { destListModel.clear(); } public void addSourceElements(ListModel newValue) { fillListModel(sourceListModel, newValue); } public void setSourceElements(ListModel newValue) { clearSourceListModel(); addSourceElements(newValue); } public void addDestinationElements(ListModel newValue) { fillListModel(destListModel, newValue); } private void fillListModel(SortedListModel model, ListModel newValues) { int size = newValues.getSize(); for(int i=0; i<size; i++) { model.add(newValues.getElementAt(i)); } } public void addSourceElements(Object newValue[]) { fillListModel(sourceListModel, newValue); } public void setSourceElements(Object newValue[]) { clearSourceListModel(); addSourceElements(newValue); } public void addDestinationElements(Object newValue[]) { fillListModel(destListModel, newValue); } private void fillListModel(SortedListModel model, Object newValues[]) { model.addAll(newValues); } public Iterator sourceIterator() { return sourceListModel.iterator(); } public Iterator destinationIterator() { return destListModel.iterator(); } public void setSourceCellRender(ListCellRenderer newValue) { sourceList.setCellRenderer(newValue); } public ListCellRenderer getSourceCellRenderer() { return sourceList.getCellRenderer(); } public void setDestinationCellRenderer(ListCellRenderer newValue) { destList.setCellRenderer(newValue); } public ListCellRenderer getDestinationCellRenderer() { return destList.getCellRenderer(); } public void stVisibleRowCount(int newValue) { sourceList.setVisibleRowCount(newValue); destList.setVisibleRowCount(newValue); } public int getVisibleRowCount() { return sourceList.getVisibleRowCount(); } public void setSelectionBackground(Color newValue) { sourceList.setSelectionBackground(newValue); destList.setSelectionBackground(newValue); } public Color getSelectionBackground() { return sourceList.getSelectionBackground(); } public void setSelectionForeground(Color newValue) { sourceList.setSelectionForeground(newValue); destList.setSelectionForeground(newValue); } public Color getSelectionForeground() { return sourceList.getSelectionForeground(); } private void clearSourceSelected() { Object selected[] = sourceList.getSelectedValues(); for(int i= selected.length-1; i>=0; i--) { sourceListModel.removeElement(selected[i]); } sourceList.getSelectionModel().clearSelection(); } private void clearDestinationSelected() { Object selected[] = destList.getSelectedValues(); for(int i=selected.length-1; i>=0; --i) { destListModel.removeElement(selected[i]); } destList.getSelectionModel().clearSelection(); } private void initScreen() { setBorder(BorderFactory.createEtchedBorder()); setLayout(new GridBagLayout()); sourceLabel = new JLabel(DEFAULT_SOURCE_CHOICE_LABEL); sourceListModel = new SortedListModel(); sourceList = new JList(sourceListModel); add(sourceLabel, new GridBagConstraints(0,0,1,1,0,0,GridBagConstraints.CENTER,GridBagConstraints.NONE, EMPTY_INSETS, 0, 0)); add(new JScrollPane(sourceList), new GridBagConstraints(0,1,1,5,.5,1,GridBagConstraints.CENTER,GridBagConstraints.BOTH,EMPTY_INSETS,0,0)); addButton = new JButton(ADD_BUTTON_LABEL); add(addButton, new GridBagConstraints(1,2,1,2,0,.25,GridBagConstraints.CENTER,GridBagConstraints.NONE,EMPTY_INSETS,0,0)); addButton.addActionListener(new AddListener()); removeButton = new JButton(REMOVE_BUTTON_LABEL); add(removeButton, new GridBagConstraints(1,4,1,2,0,.25,GridBagConstraints.CENTER,GridBagConstraints.NONE, new Insets(0,5,0,5),0,0)); removeButton.addActionListener(new RemoveListener()); destLabel = new JLabel(DEFAULT_DEST_CHOICE_LABEL); destListModel = new SortedListModel(); destList = new JList(destListModel); add(destLabel, new GridBagConstraints(2,0,2,1,0,0,GridBagConstraints.CENTER,GridBagConstraints.NONE,EMPTY_INSETS,0,0)); add(new JScrollPane(destList), new GridBagConstraints(2,1,1,5,.5,1.0, GridBagConstraints.CENTER,GridBagConstraints.BOTH, EMPTY_INSETS,0,0)); } private class AddListener implements ActionListener { public void actionPerformed(ActionEvent event) { Object selected[] = sourceList.getSelectedValues(); addDestinationElements(selected); clearSourceSelected(); } } private class RemoveListener implements ActionListener { public void actionPerformed(ActionEvent event) { Object selected[] = destList.getSelectedValues(); addSourceElements(selected); clearDestinationSelected(); } } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Dual List Box Tester"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); DualListBox dual = new DualListBox(); dual.addSourceElements(new String[] {"One", "Two", "Three"}); dual.addSourceElements(new String[] {"Four", "Five", "Six"}); dual.addSourceElements(new String[] {"Seven", "Eight", "Nigh"}); dual.addSourceElements(new String[] {"Ten", "Eleven", "Twele"}); dual.addSourceElements(new String[] {"Thirteen", "Fourteen", "Fifteen"}); dual.addSourceElements(new String[] {"Sixteen", "Seventeen", "Eighteen"}); dual.addSourceElements(new String[] {"Nineteen", "Twenty", "Thirty"}); frame.add(dual, BorderLayout.CENTER); frame.setSize(400, 300); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
正如第4章所描述的,所有的Swing组件支持显示工具提示文本。通过调用组件的setToolTipText()方法 ,我们可以在组件上显示任意的文本字符串。在JList组件的情况下,单个的工具提示文本字符串也许并足够。我们也许希望在一个组件中的每一项上显示一个不同的提示。
显示元素级的提示需要一些工作。要在每一项上显示不同的工具提示文本,我们必须创建一个JList的子类。在这个子类中,我们必须手动向组件注册ToolTipManager。这通常是当我们调用setToolTipText()时为我们完成的。但是因为我们不会调用这个方法,我们必须手动通知管理器,如下所示:
ToolTipManager.sharedInstance().registerComponent(this);
在我们通知ToolTipManager之后,管理器会在鼠标滑过组件时通知组件。这允许我们覆盖public String getToolTipText(MouseEvent mouseEvent)方法来为鼠标点下的项提供相应的提示。使用某些Hashtable,HashMap或是Properties列表可以使得我们将鼠标滑过的项映射到相应的工具提示文本。
public String getToolTipText(MouseEvent event) { Point p = event.getPoint(); int location = locationToIndex(p); String key = (String)model.getElementAt(location); String tip = tipProps.getProperty(key); return tip; }
图13-13显示了PropertiesList示例如何基于鼠标停留的元素演示各种工具提示。示例的完整源码显示在列表13-11中。
package swingstudy.ch13; import java.awt.EventQueue; import java.awt.Point; import java.awt.event.MouseEvent; import java.util.Enumeration; import java.util.Properties; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JScrollPane; import javax.swing.ToolTipManager; public class PropertiesList extends JList { SortedListModel model; Properties tipProps; public PropertiesList(Properties props) { model = new SortedListModel(); setModel(model); ToolTipManager.sharedInstance().registerComponent(this); tipProps = props; addProperties(props); } private void addProperties(Properties props) { // Load Enumeration names = props.propertyNames(); while(names.hasMoreElements()) { model.add(names.nextElement()); } } public String getToolTipText(MouseEvent event) { Point p = event.getPoint(); int location = locationToIndex(p); String key = (String)model.getElementAt(location); String tip = tipProps.getProperty(key); return tip; } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Custom Tip Demo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Properties props = System.getProperties(); PropertiesList list = new PropertiesList(props); JScrollPane scrollPane = new JScrollPane(list); frame.add(scrollPane); frame.setSize(300, 300); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }