1. 列表
1.1 JList構件
JList可以將多個選項放置在單個框中。為了構建列表框,首先需要創建一個字符串數組,然后將這個數組傳遞給JList構造器。
String[] words= { "quick", "brown", "hungry", "wild", . . . };
JList<String> wordList = new JList<>(words);
列表框不能自動滾動,要想為列表框加上滾動條,必須將它插入到一個滾動面板中:
JScrollPane scrollPane = new JScrollPane(wordList);
然后應該應該把滾動面板而不是列表框,插入到外圍面板上。
默認情況下,列表框構件可以顯示8個選項;可以使用setVisibleRowCount方法改變這個值:
wordList.setVisibleRowCount(4); // display 4 items
還可以使用以下三個值中的任意一個來設置列表框擺放的方向:
• JList.VERTICAL ( 默認值) :垂直擺放所有選項。
• JList.VERTICAL_WRAP: 如果選項數超過了可視行數,就開始新的一列。
• JList.HORIZONTAL_WRAP: 如果選項數超過了可視行數,就開始新的一行,並且按照水平方向進行填充。
在默認的情況下,用戶可以選擇多個選項。為了選擇多個選項,只需要按住CTRL鍵,然后在要選擇的選項上單擊。要選擇處於連續范圍內的選項,首先選擇第一個選項,然后按住SHIFT鍵,並在最后一個選項上點擊即可。
使用setSelectionMode方法,還可以對用戶的選擇模式加以限制:
wordList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // select one item at a time wordList.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); // select one item or one range of items
列表框使用一種不同的事件通知機制,它不需要監聽動作事件,而是監聽列表選擇事件。可以向列表構件添加一個列表選擇監聽器,然后在監聽器中實現下面這個方法:
public void valueChanged(ListSelectionEvent evt)
在用戶選擇了若干個選項的同時,將產生一系列列表選擇事件。假如用戶在一個新選項上單擊,當鼠標按下的時候,就會有一個事件來報告選項的改變。這是一種過渡型事件,在調用event.getValueIsAdjusting()時,如果該選擇仍未最終結束則返回true。然后,當松開鼠標時,就產生另一事件,此時event.getValueIsAdjusting()返回false。如果你對這種過渡型事件不感興趣,那么可以等待event.getValueIsAdjusting()調用返回false的事件。不過,如果希望只是要點擊鼠標就給用戶一個即時反饋,那么就需要處理所有的事件。
一旦被告知某個事件已經發生,那么就需要弄清楚當前選擇了哪些選項。如果是單選模式,調用getSelectedValue可以獲取所有選中列表的元素的值;否則調用getSelectedValues返回一個包含所有選中選項的對象數組。之后,可以以常規方式處理它。
for (String value : wordList.getSelectedValuesList()) // do something with value
注意:列表構件不響應鼠標的雙擊事件。正如Swing設計者所構想的那樣,使用列表選擇一個選項,然后點擊某個按鈕執行某個動作。但是,某些用戶界面允許用戶在一個列表選項上雙擊鼠標,作為選擇一個選項並調用一個默認動作的快捷方式。如果想實現這中行為,那么必須對這個列表框添加一個鼠標監聽器,然后按照下面這樣捕獲鼠標事件:
public void mouseClicked(MouseEvent evt) { if (evt.getClickCount() == 2) { JList source = (JList) evt.getSource(); Object[] selection = source.getSelectedValues(); doAction(selection); } }
下面的程序展示了一個填入字符串的列表框。請注意valueChanged方法是怎樣根據被選項來創建消息字符的。
package list; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; public class ListFrame extends JFrame { private static final int DEFAULT_WIDTH=400; private static final int DEFAULT_HEIGHT=300; private JPanel listPanel; private JList<String> wordlist; private JLabel label; private JPanel buttonPanel; private ButtonGroup group; private String prefix="The "; private String suffix="fox jump over the lazy dog."; public ListFrame(){ this.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); String[] words = {"quick","brown","hungry","wild","silent","huge","private","abstract","static","final"}; wordlist = new JList<>(words); wordlist.setVisibleRowCount(4); JScrollPane scrollPane = new JScrollPane(wordlist); listPanel = new JPanel(); listPanel.add(scrollPane); wordlist.addListSelectionListener(new ListSelectionListener(){ @Override public void valueChanged(ListSelectionEvent e) { StringBuilder text = new StringBuilder(prefix); for(String value : wordlist.getSelectedValuesList()){ text.append(value); text.append(" "); } text.append(suffix); label.setText(text.toString()); } }); buttonPanel = new JPanel(); group = new ButtonGroup(); makeButton("Vertical", JList.VERTICAL); makeButton("Vertical Wrap",JList.VERTICAL_WRAP); makeButton("Horizontal Wrap",JList.HORIZONTAL_WRAP); this.add(listPanel, BorderLayout.NORTH); label = new JLabel(prefix+suffix); this.add(label, BorderLayout.CENTER); this.add(buttonPanel, BorderLayout.SOUTH); this.setTitle("ListTest"); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } /** * 設置按鈕 * @param string 按鈕名字 * @param vertical 按鈕類型 */ private void makeButton(String string, final int vertical) { JRadioButton button = new JRadioButton(string); buttonPanel.add(button); if(group.getButtonCount()==0) button.setSelected(true); group.add(button); button.addActionListener(new ActionListener(){ @Override public void actionPerformed(ActionEvent e) { wordlist.setLayoutOrientation(vertical); listPanel.revalidate(); //重新布局並繪制 } }); } public static void main(String[] args) { new ListFrame(); } }
1.2 列表模式
通過前一節,我們已經對列表構件的一些常用方法有了一定的了解:
1)指定一組在列表中顯示的固定字符串
2)將列表放置到一個滾動面板中
3)捕獲列表選擇事件
列表構件使用了模型-視圖-控制器這種設計模式,將可視化外觀(以某種方式呈現的一列選項)和底層數據(一個對象集合)進行了分離。JList類負責數據的可視化外觀。實際上,它對這些數據是怎樣存儲的知之甚少,它只知道可以通過某個實現了ListModel接口的對象來獲取這些數據:
public interface ListModel<E> { int getSize(); E getElementAt(int i); void addListDataListener(ListDataListener l); void removeListDataListener(ListDataListener l); }
通過這個接口,JList就可以獲得元素的個數,並且能夠獲取每一個元素。另外,JList對象可以將其自身添加為一個ListDataListener。在這種方式下,一旦元素集合發生了變化,就會通知JList,從而使它能夠重新繪制列表。
為什么這種通用性非常有用呢?為什么JList對象不直接存儲一個對象數組呢?
請注意,這個接口並未指定這些對象是怎樣存儲的。尤其是,它根本就沒有強制要求這些對象一定要被存儲!無論何時調用getElementAt方法,它都會對每個值進行重新計算。如果想顯示一個極大的集合,而且又不想存儲這些值,那么這個方法可能會有所幫助。
下面我們舉一個例子:允許用戶在列表框中所有三個字母的單詞當中進行選擇。
三個字母的組合一共有26x26x26=17576個。我們不希望將所有這些組合都存儲起來,而是想用戶滾動這些單詞的時候,依照請求對它們重新計算。
事實證明,這實現起來很容易。其中比較麻煩的部分,即添加和刪除監聽器,在我們所繼承的AbstractListModel類中已經為我們實現了。我們只需要提供getSize和getElementAt方法即可:
class WordListModel extends AbstractListModel<String> { public WordListModel(int n) { length = n; } public int getSize() { return (int) Math.pow(26, length); } public String getElementAt(int n) { // compute nth string . . . } . . . }
既然我們已經有了一個模型,那么就可以構建一個列表,讓用戶可以通過滾動來選擇該模型所提供的任意元素:
JList<String> wordList = new JList<>(new WordListModel(3)); wordList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); JScrollPane scrollPane = new JScrollPane(wordList);
這里的關鍵是這些字符串從來都沒有被存儲過,而只有那些用戶實際要求查看的字符串才會被生成。
我們還必須進行另外一項設置。那就是,我們必須告訴列表構件,所有的選項都有一個固定的寬度和高度。最簡單的方法就是通過設置單元格的尺寸大小(cell dimension)來設定原型單元格的值(prototype cell value):
wordList.setPrototypeCellValue("www");
原型單元格的值通常用來確定所有單元格的尺寸(我們使用字符串“www"是因為"w"在大多數字體中都是最寬的小寫字母)。另外,可以像下面這樣設置一個固定不變的單元格尺寸。
wordList.setFixedCellWidth(50);
wordList.setFixedCellHeight(15);
如果你既沒有設置原型值也沒有設置固定的單元格尺寸,那么列表構建就必須計算每個選項的寬度和高度。這可能需要花費更長的時間。
完整的程序代碼如下:
WordListModel.java
package longList; import javax.swing.AbstractListModel; //計算生成列表元素 public class WordListModel extends AbstractListModel<String>{ private static final long serialVersionUID = 1L; private int length; public static final char FIRST = 'a'; public static final char LAST = 'z'; public WordListModel(int n){ length = n; } @Override public int getSize() { // TODO Auto-generated method stub return (int) Math.pow(LAST-FIRST+1, length); } @Override public String getElementAt(int n) { // TODO Auto-generated method stub StringBuilder r = new StringBuilder(); for(int i=0;i<length;i++){ char c = (char)(FIRST + n%(LAST-FIRST+1)); r.insert(0,c); n=n/(LAST-FIRST+1); } return r.toString(); } }
LongListFrame.java
package longList; import java.awt.BorderLayout; import java.awt.Container; import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; //允許用戶在列表框中所有三個字母的單詞當中進行選擇 public class LongListFrame extends JFrame{ private static final long serialVersionUID = 1L; private JList<String> wordList; private JLabel label; private String prefix = "The quick brown "; private String suffix = " jumps over the lazy dog."; public LongListFrame(){ wordList = new JList<String>(new WordListModel(3)); wordList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); wordList.setPrototypeCellValue("www"); JScrollPane scrollPane = new JScrollPane(wordList); JPanel p = new JPanel(); p.add(scrollPane); wordList.addListSelectionListener(new ListSelectionListener(){ @Override public void valueChanged(ListSelectionEvent e) { // TODO Auto-generated method stub setSubject(wordList.getSelectedValue()); } }); Container contentPane = getContentPane(); contentPane.add(p, BorderLayout.NORTH); label = new JLabel(prefix+suffix); contentPane.add(label, BorderLayout.CENTER); setSubject("fox"); this.setTitle("LongListTest"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); this.pack(); //按組件的首選大小布局 } //在label中添加選中的信息 public void setSubject(String word) { StringBuilder text = new StringBuilder(prefix); text.append(word); text.append(suffix); label.setText(text.toString()); } public static void main(String[] args) { new LongListFrame(); } }
1.3 插入和移除值
不能直接編輯列表值的集合。相反地,必須先訪問模型,然后再添加或移除元素。
我們需要先構建一個 DefaultListModel對象,填入初始值,然后將他與一個列表關聯起來。 DefaultListModel類實現了ListModel接口,並管理着一個對象集合。
DefaultListModel<String> model = new DefaultListModel<>(); model.addElement("quick"); model.addElement("brown"); . . . JList<String> list = new JList<>(model);
現在,就可以從model對象中添加或移除元素值了。然后,model對象會告知列表發生了哪些變化,接着,列表會對自身進行重新繪制。
model.removeElement("quick");
model.addElement("slow");
由於歷史遺留問題,DefaultListModel類使用的方法名和集合類的方法名並不相同。默認的列表模型在內部是使用一個向量來存儲元素值的。
1.4 值的繪制
具有繪畫單元格的類表框如下程序如下:
FontCellRenderer.java
package listRendering; import java.awt.*; import javax.swing.*; /** * A cell renderer for Font objects that renders the font name in its own font. */ public class FontCellRenderer extends JComponent implements ListCellRenderer<Font> { private Font font; private Color background; private Color foreground; public Component getListCellRendererComponent(JList<? extends Font> list, Font value, int index, boolean isSelected, boolean cellHasFocus) { font = value; background = isSelected ? list.getSelectionBackground() : list.getBackground(); foreground = isSelected ? list.getSelectionForeground() : list.getForeground(); return this; } public void paintComponent(Graphics g) { String text = font.getFamily(); FontMetrics fm = g.getFontMetrics(font); g.setColor(background); g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(foreground); g.setFont(font); g.drawString(text, 0, fm.getAscent()); } public Dimension getPreferredSize() { String text = font.getFamily(); Graphics g = getGraphics(); FontMetrics fm = g.getFontMetrics(font); return new Dimension(fm.stringWidth(text), fm.getHeight()); } }
ListRenderingFrame.java
package listRendering; import java.awt.*; import java.util.*; import javax.swing.*; import javax.swing.event.*; /** * This frame contains a list with a set of fonts and a text area that is set to the selected font. */ public class ListRenderingFrame extends JFrame { private static final int TEXT_ROWS = 8; private static final int TEXT_COLUMNS = 20; private JTextArea text; private JList<Font> fontList; public ListRenderingFrame() { java.util.List<Font> fonts = new ArrayList<>(); final int SIZE = 24; fonts.add(new Font("Serif", Font.PLAIN, SIZE)); fonts.add(new Font("SansSerif", Font.PLAIN, SIZE)); fonts.add(new Font("Monospaced", Font.PLAIN, SIZE)); fonts.add(new Font("Dialog", Font.PLAIN, SIZE)); fonts.add(new Font("DialogInput", Font.PLAIN, SIZE)); fontList = new JList<Font>(fonts.toArray(new Font[]{})); fontList.setVisibleRowCount(4); fontList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); fontList.setCellRenderer(new FontCellRenderer()); JScrollPane scrollPane = new JScrollPane(fontList); JPanel p = new JPanel(); p.add(scrollPane); fontList.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent evt) { Font font = fontList.getSelectedValue(); text.setFont(font); } }); Container contentPane = getContentPane(); contentPane.add(p, BorderLayout.SOUTH); text = new JTextArea(TEXT_ROWS, TEXT_COLUMNS); text.setText("The quick brown fox jumps over the lazy dog"); text.setFont(fonts.get(0)); text.setLineWrap(true); text.setWrapStyleWord(true); contentPane.add(text, BorderLayout.CENTER); pack(); } }
ListRenderingTest.java
package listRendering; import java.awt.*; import javax.swing.*; /** * This program demonstrates the use of cell renderers in a list box. * @version 1.24 2012-01-26 * @author Cay Horstmann */ public class ListRenderingTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { JFrame frame = new ListRenderingFrame(); frame.setTitle("ListRenderingTest"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } }
javax.swing.JList < E > 1.2
• Color getBackground()
返回未選定單元格的背景顏色。
• Color getSelectionBackground()
返回選定單元格的背景顏色。
• Color getForeground()
返回未選定單元格的前景顏色。
• Color getSelectionForeground()
返回選定單元格的前景顏色。
• void setCellRenderer(ListCellRenderer<? super E> cellRenderer)
設置用於繪制列表中單元格的繪制器。
javax.swing.ListCellRenderer<E> 1.2
• Component getListCellRendererComponent(JList<? extends E> list, E item, int index, boolean isSelected, boolean hasFocus)
返回一個其paint方法用於繪制單元格內容的構件,如果列表的單元格尺寸沒有固定,那么該構件還必須實現getPreferredSize。
參數: list 單元格正在被繪制的列表
item 要繪制的選項
index 存儲在模型中的選項索引
inSelected true表示指定的單元格被選定
hasFocus true表示焦點在指定的單元格上
