高級Swing——列表

1. 列表

1.1 JList構件

  JList能夠將多個選項放置在單個框中。爲了構建列表框,首先須要建立一個字符串數組,而後將這個數組傳遞給JList構造器。java

String[] words= { "quick", "brown", "hungry", "wild", . . . };
JList<String> wordList = new JList<>(words);

  列表框不能自動滾動,要想爲列表框加上滾動條,必須將它插入到一個滾動面板中:設計模式

JScrollPane scrollPane = new JScrollPane(wordList);

  而後應該應該把滾動面板而不是列表框,插入到外圍面板上。數組

  默認狀況下,列表框構件能夠顯示8個選項;可使用setVisibleRowCount方法改變這個值:app

wordList.setVisibleRowCount(4); // display 4 items

  還可使用如下三個值中的任意一個來設置列表框擺放的方向:ide

• JList.VERTICAL ( 默認值) :垂直襬放全部選項。
• JList.VERTICAL_WRAP: 若是選項數超過了可視行數,就開始新的一列。佈局

•  JList.HORIZONTAL_WRAP:  若是選項數超過了可視行數,就開始新的一行,而且按照水平方向進行填充。字體

  在默認的狀況下,用戶能夠選擇多個選項。爲了選擇多個選項,只須要按住CTRL鍵,而後在要選擇的選項上單擊。要選擇處於連續範圍內的選項,首先選擇第一個選項,而後按住SHIFT鍵,並在最後一個選項上點擊便可。ui

  使用setSelectionMode方法,還能夠對用戶的選擇模式加以限制:this

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

  列表框使用一種不一樣的事件通知機制,它不須要監聽動做事件,而是監聽列表選擇事件。能夠向列表構件添加一個列表選擇監聽器,而後在監聽器中實現下面這個方法:spa

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表示焦點在指定的單元格上

相關文章
相關標籤/搜索