Swing组件集合的JComboBox组件是一个多部分组件,允许用户借助于下拉列表由一个预定义的选项集合中进行选择。在其基本配置中,JComboBox类似于JLabel来显示当前的用户选择。嵌入在JLabel中的一个包含在JList控件中选择的弹出菜单。当所需要选项不可用时,JComboBox可以使用JTextField来输入新的选项。当需要时,JList部分会自被嵌入在JScrollPane中;我们并不需要手动创建JList或是将其放在JScrollPane中。另外,用于编辑的文本框默认是禁止的,只允许用户由预定义的选项集合中进行选择。图13-14演示了两个JComboBox组件:一个是不可以编辑,显示其选项列表,而另一个可以编辑而不显示其选项。
四个核心元素定义了JComboBox组件及其实现:
JComboBox的许多功能与JList组件所共用。这并不是巧合;这两个组件非常相似。下面我们详细了解JComboBox。
类似于JList组件,JComboBox组件有四个构造函数,允许我们基于初始的数据结构进行创建。与JList组件不同,数组与Vector构造函数所用的默认模型允许添加或是移除数据元素。
public JComboBox() JComboBox comboBox = new JComboBox(); public JComboBox(Object listData[]) String labels[] = { "Chardonnay", "Sauvignon", "Riesling", "Cabernet", "Zinfandel", "Merlot", "Pinot Noir", "Sauvignon Blanc", "Syrah", "Gewürztraminer"}; JComboBox comboBox = new JComboBox(labels); public JComboBox(Vector listData) Vector vector = aBufferedImage.getSources(); JComboBox comboBox = new JComboBox(vector); public JComboBox(ComboBoxModel model) ResultSet results = aJDBCStatement.executeQuery("SELECT columnName FROM tableName"); DefaultComboBoxModel model = new DefaultComboBoxModel(); while (result.next()) model.addElement(results.getString(1)); JComboBox comboBox = new JComboBox(model);
在我们创建了JComboBox组件之后,我们可以修改其属性。表13-10显示了JComboBox的22个属性。
JComboBox的重要属性关注弹出列表的显示。我们可以通过设置maximumRowCount属性来控制弹出列表可见项的最大数目。lightWeightPopupEnabled属性设置有助于确定当显示弹出选项菜单时所用的窗口类型。如果组件完全适应程序的顶级窗口,组件将是轻量级的。如果不适应,则其将是重量级的。如果我们在程序中混合使用AWT与Swing组件,我们可以通过将lightWeightPopupEnabled属性设置为true强制弹出选项菜单为重量级的。这将强制弹出菜单显示在其他组件之上。其他与弹出列表相关的属性是popupVisible属性,这将允许我们编程显示弹出列表。
注意,除了设置popupVisible属性以外,我们可以使用public void hidePopup()与public void showPopup()方法来切换弹出列表的可视状态。
JComboBox内的元素的渲染是使用ListCellRenderer来完成的。这是与JList组件所用的相同的渲染器。一旦我们为这两个组件中的一个创建一个渲染器,我们就可以为另一个组件使用相同的渲染器。为了重用本章前面的ComplexCellRenderer,我们可以将下面的代码添加到ComplexRenderingSample示例中使得两个组件共享相同的渲染器。
JComboBox comboBox = new JComboBox(elements); comboBox.setRenderer(renderer); frame.add(comboBox, BorderLayout.NORTH);
最终的结果如图13-15所示。
并不是所有的渲染器都会在JComboBox与JList组件之间得到所期望的结果。例如,在前面的图13-6中所演示的FocusedTitleListCellRenderer不会在JComboBox中显示的具有输入焦点的标题边框,因为选项绝不会具有输入焦点。另外,不同的组件也许具有不同的颜色(在这种情况下是不同的未选中背景颜色)。也许询问通常情况下组件以哪种颜色进行渲染是必要的,并且进行相应的响应。
JComboBox组件支持至少三个与选择相关的不同事件。我们可以监听键盘输入来支持借助于JComboBox.KeySelectionManager类的键盘选择。我们也可以使用ActionListener或ItemListener进行监听来确定何时JComboBox的选中项发生变化。
如果我们希望编程选中一个元素,则可以使用public void setSelectedItem(Object element)或是public void setSelectedIndex(int index)。
提示,要编程实现取消JComboBox的当前选项,使用参数-1来调用setSelectedIndex()方法。
使用KeySelectionManager监听键盘事件
JComboBox包含一个非常重要的公开内联接口。KeySelectionManager及其默认实现管理由键盘实现的对JComboBox中的项的选中。默认管理器定位与按下的键相对应的下一个元素。他具有记忆功能,所以如果我们具有相似前缀的多个条目,用户可以连续输入直接足够匹配唯一的元素。如果我们不喜欢这种行为,我们可以将其关掉或是创建一个新的键盘选择管理器。
注意,KeySelectionManager只可以用在不可编辑的组合框中。
如果我们希望关掉键盘选择功能,我们不能简单的将KeySelectionManager属性设置为null。相反,我们必须以相应的方法创建接口的实现。接口的唯一方法为public int selectionForKey(char aKey, ComboBoxModel aModel)。如果按下的键与任何元素都不匹配,这个方法需要返回-1。否则,他应该返回匹配元素的位置。所以,要忽略键盘输入,这个方法应总是返回-1,如下所示:
JComboBox.KeySelectionManager manager = new JComboBox.KeySelectionManager() { public int selectionForKey(char aKey, ComboBoxModel aModel) { return -1; } }; aJcombo.setKeySelectionManager(manager);
使用ActionListener监听JComboBox事件
监听选中事件的最基本的方法是使用ActionListsener,通常是使用setAction(Action)来设置的。他会通知我们JComboBox中的元素何时被选中。不幸的,这个监听器并不会知道哪一个元素被选中。
注意,通过setAction(Action)设置ActionListsener同时配置工具提示文本以及基于Action的JComboBoxenabled状态。
因为ActionListener不能标识被选中的元素,他必须询问作为事件源的JComboBox。要确定JComboBx中的选中元素,使用getSelectedItem()或是getSelectedIndex()方法。如果返回的索引为-1,那么当前选中的元素并不是模型的一部分。当JComboBox是可编辑的而用户输入了一个并不是原始模型一部分的值时也许就会发生不可能的情况。
注意,文本字符串comboBoxChanged是当JComboBox中的一个元素发生变化时发送给ActionListener的ActionEvent的动作命令。
使用ItemListsener监听JComboBox事件
如果我们使用ItemListener来确定JComboBox中的选中元素何时发生变化,我们也可以了解哪一个元素被取消选中。
为了演示ActionListener与ItemListener,列表13-12中所示的示例程序将两个监听器关联到同一个JComboBox上。ActionListsener输入其动作命令以及当前选中的元素。ItemListener输出受影响的元素及其状态变化,以及当前被选中的元素。
package swingstudy.ch13; import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.ItemSelectable; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.PrintWriter; import java.io.StringWriter; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTextArea; public class SelectingComboSample { static private String selectedString(ItemSelectable is) { Object selected[] = is.getSelectedObjects(); return ((selected.length==0)?"null":(String)selected[0]); } /** * @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 JComboBox"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JComboBox comboBox = new JComboBox(labels); frame.add(comboBox, BorderLayout.SOUTH); final JTextArea textArea = new JTextArea(); textArea.setEditable(false); JScrollPane sp = new JScrollPane(textArea); frame.add(sp, BorderLayout.CENTER); ItemListener itemListener = new ItemListener() { public void itemStateChanged(ItemEvent event) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); int state = event.getStateChange(); String stateString = ((state==ItemEvent.SELECTED)?"Selected":"Deselected"); pw.print("Item: "+event.getItem()); pw.print(", state: "+stateString); ItemSelectable is = event.getItemSelectable(); pw.print(", Selected: "+selectedString(is)); pw.println(); textArea.append(sw.toString()); } }; comboBox.addItemListener(itemListener); ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent event) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); pw.print("Command: "+event.getActionCommand()); ItemSelectable is = (ItemSelectable)event.getSource(); pw.print(", Selected: "+selectedString(is)); pw.println(); textArea.append(sw.toString()); } }; comboBox.addActionListener(actionListener); frame.setSize(400, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
图13-16显示了程序运行一段时间后的结果。
使用ListDataListener监听JComboBox事件
我们可以ListDataListener关联到JComboBox的数据模型。当模型选中的元素发生变化时这个监听器会得到通知。不幸的是,监听器也会得到其他数据模型变化的通知。换句话说,使用ListDataListener来确定JComboBox的元素何时被选中并不是推荐的方法。
注意,JComboBox中鼠标移动与当标移动事件不会改变被选中的元素;鼠标释放会修改被选中的元素。当选中的鼠标按钮在JComboBox弹出列表的元素上释放时,所注册的监听器会得到通知。
我们也许会希望像文本输入框一样使用组合框,其中列出最户最可能的文本输入,但是同时允许输入一些其他的内容。通过允许JComboBox的editable属性,我们就可以获得这一功能。为了演示,图13-17显示了一个可编辑的JComboBox。这个窗口同时包含一个文本框来报告当前选中的元素及其索引。尽管我们手动在JComboBox输入一个选项,getSelectedIndex()将会报告正确的位置。记住,如果我们输入并没有出现的值,getSelectedIndex()会返回-1。
图13-17中的程序源码显示在列表13-13中。
package swingstudy.ch13; import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTextArea; public class EditComboBox { /** * @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("Editable JComboBox"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final JComboBox comboBox = new JComboBox(labels); comboBox.setMaximumRowCount(5); comboBox.setEditable(true); frame.add(comboBox, BorderLayout.NORTH); final JTextArea textArea = new JTextArea(); JScrollPane scrollPane = new JScrollPane(textArea); frame.add(scrollPane, BorderLayout.CENTER); ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent event) { textArea.append("Selected: "+comboBox.getSelectedItem()); textArea.append(", Position: "+comboBox.getSelectedIndex()); textArea.append(System.getProperty("line.seperator")); } }; comboBox.addActionListener(actionListener); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
默认情况下,用于编辑的输入框是JTextField。如果我们的数据模式由文本字符串组成,则默认的JTextField将是一个好的编辑器。然而,一旦我们的模型包含不同的对象类型(例如,颜色),我们需要提供一个不同的编辑器。默认情况下,一旦我们在文本框中输入(为我们的元素编辑toString的结果),对象会被看作一个String。由技术上来说,不同的编辑器并不总是必需的。如果我们能将作为字符串的文本框内容解析为正确的数据类型,那我们就可以这样做。但是,如查我们希望以某种方式限制输入(例如,只允许输入数字)或是提供一个更好的输入机制,我们必须提供我们自己的编辑器。定义为了必需行为的接口名为ComboBoxEditor,而其定义如下所示:
public interface ComboBoxEditor { // Properties public Component getEditorComponent(); public Object getItem(); public void setItem(Object anObject); // Listeners public void addActionListener(ActionListener l); public void removeActionListener(ActionListener l); // Other methods public void selectAll(); }
注意,默认的编辑器是javax.swing.plaf.basic包中的BasicComboBoxEditor实现。
add/remove监听器方法对于当ComboBoxEditor值发生变化时通知监听器是必需的。我们并没有必要添加一个监听器,而通常我们也并不希望这样做。无论如何,这些方法是接口的一部分,所以如果我们希望提供自己的编辑器我们需要实现这些方法。
getEditorComponent()方法返回编辑器所用的Component对象。我们可以为编辑器使用AWT或是Swing组件(例如,用于颜色选择的JColorChooser)。当编辑器首次显示时selectAll()方法会被调用。他通知编辑器选中其内的所有内容。选中所有内容允许用户仅在默认JTextField情况的当前输入上输入。某些编辑器并不需要使用这种方法。
当我们提供自定义的编辑器时,item属性需要最多的工作。为了显示要编辑的数据,我们需要提供一个方法来将Object子类的特定片段映射到组件。然后我们需要由编辑器获取数据,从而数据可以存储到原始对象的实例中。
为了演示,列表13-14中的源码是用于Color类的ComboBoxEditor。自定义的编辑器是必需的,因为没有自动的方法来解析用于显示Color的默认字符串的编辑结果。这个编辑器使用JColorChooser从而允许用户选择一个新的颜色值。getItem()方法需要只返回当前值,Color。setItem()方法需要转换传递给Color对象的对象;setItem()方法的参数是一个Object。可以使得setItem()方法只接受Color参数。然而,对于这个例子,使用Color.decode()方法解码的字符串也可以被支持。
package swingstudy.ch13; import java.awt.Color; import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.ComboBoxEditor; import javax.swing.JButton; import javax.swing.JColorChooser; import javax.swing.event.EventListenerList; public class ColorComboBoxEditor implements ComboBoxEditor { final protected JButton editor; protected EventListenerList listenerList = new EventListenerList(); public ColorComboBoxEditor(Color initialColor) { editor = new JButton(""); editor.setBackground(initialColor); ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent event) { Color currentBackground = editor.getBackground(); Color color = JColorChooser.showDialog(editor, "Color Chooser", currentBackground); if((color != null) && (currentBackground != color)) { editor.setBackground(color); fireActionEvent(color); } } }; editor.addActionListener(actionListener); } @Override public void addActionListener(ActionListener l) { // TODO Auto-generated method stub listenerList.add(ActionListener.class, l); } @Override public Component getEditorComponent() { // TODO Auto-generated method stub return editor; } @Override public Object getItem() { // TODO Auto-generated method stub return editor.getBackground(); } @Override public void removeActionListener(ActionListener l) { // TODO Auto-generated method stub listenerList.remove(ActionListener.class, l); } @Override public void selectAll() { // TODO Auto-generated method stub } @Override public void setItem(Object newValue) { // TODO Auto-generated method stub if(newValue instanceof Color) { Color color = (Color)newValue; editor.setBackground(color); } else { try { Color color = Color.decode(newValue.toString()); editor.setBackground(color); } catch(NumberFormatException e) { } } } protected void fireActionEvent(Color color) { Object listeners[] = listenerList.getListenerList(); for(int i=listeners.length-2; i>=0; i-=2) { if(listeners[i] == ActionListener.class) { ActionEvent actionEvent = new ActionEvent(editor, ActionEvent.ACTION_PERFORMED, color.toString()); ((ActionListener)listeners[i+1]).actionPerformed(actionEvent); } } } }
要使用新编辑器,我们需要将其关联到JComboBox。在我们修改前面所示的EditorComboBox示例使其数据模型由Color对象的数组组成之后,我们可以通过添加下面的代码来安装编辑器:
Color color = (Color)comboBox.getSelectedItem(); ComboBoxEditor editor = new ColorComboBoxEditor(color); comboBox.setEditor(editor);
列表13-15显示了完整的测试程序。他不同于EditComboBox,因为在JComboBox下面是一个与JComboBox当前选中的颜色同步的JLabel。同时有一个自定义的单元渲染器将背景色设置为单元的值。
package swingstudy.ch13; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.ComboBoxEditor; import javax.swing.DefaultListCellRenderer; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.ListCellRenderer; public class ColorComboBox { static class ColorCellRenderer implements ListCellRenderer { protected DefaultListCellRenderer defaultRenderer = new DefaultListCellRenderer(); // width doesn't matter as the combo box will size private final static Dimension preferredSize = new Dimension(0, 20); public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { JLabel renderer = (JLabel)defaultRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); if(value instanceof Color) { renderer.setBackground((Color)value); } renderer.setPreferredSize(preferredSize); return renderer; } } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { Color colors[] = {Color.BLACK, Color.BLUE, Color.CYAN, Color.DARK_GRAY, Color.GRAY, Color.green, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.RED, Color.WHITE, Color.YELLOW }; JFrame frame = new JFrame("Color JComboBox"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final JComboBox comboBox = new JComboBox(colors); comboBox.setMaximumRowCount(5); comboBox.setEditable(true); comboBox.setRenderer(new ColorCellRenderer()); Color color = (Color)comboBox.getSelectedItem(); ComboBoxEditor editor = new ColorComboBoxEditor(color); comboBox.setEditor(editor); frame.add(comboBox, BorderLayout.NORTH); final JLabel label = new JLabel(); label.setOpaque(true); label.setBackground((Color)comboBox.getSelectedItem()); frame.add(label, BorderLayout.CENTER); ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent event) { Color selectedColor = (Color)comboBox.getSelectedItem(); label.setBackground(selectedColor); } }; comboBox.addActionListener(actionListener); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
图13-18显示了程序运行的结果。
每一个可安装的Swing观感都提供了不同的JComboBox外观以及组件的默认UIResource值设置集合。图13-19显示在了预安装的观感类型集合Motif,Windows以及Ocean下JComboBox组件的外观。
表13-11显示了JComboBox可用的UIResource相关属性的集合。JComboBox组件有21个不同的属性。
修改弹出图标是自定义观感的一个示例。要实现这一目的,我们需要安装一个新的用户界面。基本上,我们由BasicComboBoxUI或是MetalComboBoxUI用户界面委托继承默认功能,并且只覆盖protected JButton createArrowButton()方法。
图13-20显示了修改JComboBox用户界面的结果。
列表13-16列出了完整的源码。
package swingstudy.ch13; import java.awt.BorderLayout; import java.awt.EventQueue; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.plaf.ComboBoxUI; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicArrowButton; import javax.swing.plaf.basic.BasicComboBoxUI; public class PopupComboSample { /** * @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("Popup JComboBox"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JComboBox comboBox = new JComboBox(labels); comboBox.setMaximumRowCount(5); comboBox.setUI((ComboBoxUI)MyComboBoxUI.createUI(comboBox)); frame.add(comboBox, BorderLayout.NORTH); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } static class MyComboBoxUI extends BasicComboBoxUI { public static ComponentUI createUI(JComponent c) { return new MyComboBoxUI(); } protected JButton createArrowButton() { JButton button = new BasicArrowButton(BasicArrowButton.EAST); return button; } } }
我们也许已经注意到了构成JComboBox与JList的部分之间的一些相似之处。我们可以为两个组件使用相同的数据模型与相同的渲染器。在本章前面的部分中,我们已经了解了如果在两个组件之间共享渲染器。本节中所展示的示例演示了我们如何在多个组件之间共享相同的数据模型。
这个例子有两个可编辑的组合框与一个JList,所有的组件共享相同的数据模型。这个示例同时提供了一个按钮,我们可以点击来向数据模型动态添加内容。因为数据模型将会与多个组件相关联,我们将会注意到每一个都具有额外的选项在选中按钮之后进行选择。图13-21显示了程序在添加了一些元素之后的样子。
列表13-17显示了共享数据模型的例子。
package swingstudy.ch13; import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.DefaultComboBoxModel; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; public class SharedDataSample { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { final String labels[] = {"Chardonnay", "Sauvignon", "Riesling", "Cabernet", "Zinfandel", "Merlot", "Pinot Noir", "Sauvignon Blanc", "Syrah", "Gewurztraminer" }; final DefaultComboBoxModel model = new DefaultComboBoxModel(labels); JFrame frame = new JFrame("Shared Data"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JPanel panel = new JPanel(); JComboBox comboBox1 = new JComboBox(model); comboBox1.setMaximumRowCount(5); comboBox1.setEditable(true); JComboBox comboBox2 = new JComboBox(model); comboBox2.setMaximumRowCount(5); comboBox2.setEditable(true); panel.add(comboBox1); panel.add(comboBox2); frame.add(panel, BorderLayout.NORTH); JList jlist = new JList(model); JScrollPane scrollPane = new JScrollPane(jlist); frame.add(scrollPane, BorderLayout.CENTER); JButton button = new JButton("Add"); frame.add(button, BorderLayout.SOUTH); ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent event) { int index = (int)(Math.random()*labels.length); model.addElement(labels[index]); } }; button.addActionListener(actionListener); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
本章演示了如何使用Swing的JList与JComboBox组件。我们已经看到了这两个组件如何他们各自的数据模型,渲染器,选择功能,以及JComboBox组件的自定义编辑器。尽管这些功能是可以自定义的,每一个具有默认配置的组件都可以立即使用。
在第14章中,我们将会开始探讨Swing文本组件,包括JTextField与JTextArea。