• 高级Swing——列表


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

      

  • 相关阅读:
    linux启动init流程(转)
    .bash_profile .bashrc profile 文件的作用的执行顺序(转)
    Linux常用命令
    面试中常见的问题
    systemd启动过程(转)
    .bashrc文件是干什么的(转)
    关于 profile文件(转)
    从MVC框架看MVC架构的设计(转)
    Java高级软件工程师面试考纲(转)
    关于Python中的lambda
  • 原文地址:https://www.cnblogs.com/gaopeng527/p/4537003.html
Copyright © 2020-2023  润新知