Java图形化界面编程之——Swing

目录

1、Swing概述

2、Swing基本组件的用法

2.1、Swing组件层次

2.2、AWT组件的Swing实现

2.3、为组件设置边框

2.4、使用JToolBar创建工具条

2.5、JColorChooser和JFileChooser

2.5.1、JColorChooser

2.5.2、JFileChooser

2.6、使用JOptionPane

2.6.1、基本概述

2.6.2、四种对话框演示

3、Swing中的特殊容器

3.1、使用JSplitPane

3.2、使用JTabbedPane

4、JProgressBar、ProgressMonitor、BoundedRangeModel实现进度条

4.1、创建进度条

4.2、创建进度对话框

5、JList、JComboBox实现列表框

5.1、简单列表框

5.2、不强制存储列表项的ListModel和ComboBoxModel

5.3、强制存储列表项的DefaultListModel和DefaultComboBoxModel

5.4、使用ListCellRenderer改变列表外观

6、JTree、TreeModel实现树

6.1、创建树

6.2、编辑树结点

6.3、监听结点事件

6.4、使用DefaultTreeCellRenderer改变结点外观

6.5、扩展DefaultTreeCellRenderer改变结点外观

7、JTable、TableModel实现表格

7.1、创建简单表格

7.2、TableModel


1、Swing概述

Swing是由100%纯Java实现的,不再依赖于本地平台的GUI,因此可以在所有平台上都保持相同的界面外观。独立于本地平台的Swing组件被称为是轻量级组件,而依赖于本地平台的组件被称为重量级组件

由于Swing的所有组件完全采用Java实现,不再调用本地平台的GUI,所以导致Swing图形界面的显示速度要比AWT图形界面的显示速度慢一些,但相对于快速发展的硬件设施而言,这种微小的速度差别无妨大碍。

使用Swing 的优势:

  • Swing组件不再依赖于本地平台的GUI,无需采用各种平台的GUI交集,因此Swing提供了大量图形界面组件,远远超出了AWT所提供的图形界面组件集;
  • Swing组件不再依赖于本地平台GUI,因此不会产生与平台相关的bug;
  • Swing组件在各种平台上运行时可以保证具有相同的图形界面外观。

Swing提供的这些优势,让Java图形界面程序真正实现了“Write Once, Run Anywhere”的目标。

Swing 的特征:

  1. Swing组件采用MVC(Model-View-Controller,即模型—视图—控制器)设计模式:模型(Model):用于维护组件的各种状态;视图(View):是组件的可视化表现;控制器(Controller):用于控制对于各种事件、组件做出响应。当模型发生改变时,它会通知所有依赖它的视图,视图会根据模型数据来更新自己。Swing使用UI代理来包装视图和控制器,还有一个模型对象来维护该组件的状态。例如,按钮JButton有一个维护其状态信息的模型ButtonModel对象,Swing组件的模型是自动设置的,因此一般都使用JButton,而无需关心ButtonModel对象。
  2. Swing在不同的平台上表现一致,并且有能力提供本地平台不支持的显示外观。由于Swing采用MVC模式来维护各组件,所以当组件的外观被改变时,对组件的状态信息(由模型维护)没有任何影响。因此,Swing可以使用插拔式外观感觉(Pluggable Look And Feel, PLAF)来控制组件外观,使得Swing图形界面在同一个平台上运行时能拥有不同的外观,用户可以选择自己喜欢的外观。相比之下,在AWT图形界面中,由于控制组件外观的对等类与具体平台相关,因此AWT组件总是具有与本地平台相同的外观。

2、Swing基本组件的用法

2.1、Swing组件层次

Swing组件继承体系图:

Java图形化界面编程之——Swing_第1张图片

大部分Swing组件都是JComponent抽象类的直接或间接子类(并不是全部的Swing组件),JComponent类定义了所有子类组件的通用方法,JComponent类是AWT里java.awt.Container类的子类,这也是AWT和Swing的联系之一。绝大部分Swing组件类继承了Container类,所以Swing组件都可作为容器使用(JFrame类继承了Frame类)。

Swing组件和AWT组件的对应关系:大部分情况下,只需要在AWT组件的名称前面加个J,就可以得到其对应的Swing组件名称,但有几个例外:

  • JComboBox:对应于AWT里的Choice组件,但比Choice组件功能更丰富;
  • JFileChooser:对应于AWT里的FileDialog组件;
  • JScrollBar:对应于AWT里的ScrollBar组件,注意两个组件类名中b字母的大小写差别;
  • JCheckBox:对应于AWT里的Checkbox组件,注意两个组件类名中b字母的大小写差别;
  • JCheckBoxMenuItem:对应于AWT里的CheckboxMenuItem组件,注意两个组件类名中b字母的大小写差别。

Swing组件按照功能来分类:

  • 顶层容器:JFrame、JApplet、JDialog和JWindow。
  • 中间容器:JPanel、JScrollPane、JSplitPane、JToolBar等。
  • 特殊容器:在用户界面上具有特殊作用的中间容器,如JInternalFrame、JRootPane、JLayeredPane和JDesktopPane等。
  • 基本组件:实现人机交互的组件,如JButton、JComboBox、JList、JMenu、JSlider等。
  • 不可编辑信息的显示组件:向用户显示不可编辑信息的组件,如JLabel、JProgressBar和JToolTip等。
  • 可编辑信息的显示组件:向用户显示能被编辑的格式化信息的组件,如JTable、JTextArea和JTextField等。
  • 特殊对话框组件:可以直接产生特殊对话框的组件,如JColorChooser和JFileChooser等。

2.2、AWT组件的Swing实现

Swing为除Canvas之外的所有AWT组件提供了相应的实现,Swing组件比AWT组件的功能更加强大。相对于AWT组件,Swing组件具有如下的4个额外的功能:

  1. 可以为Swing组件设置提示信息。使用setToolTipText()方法,为组件设置对用户有帮助的提示信息。
  2. 很多Swing组件如按钮、标签、菜单项等,除使用文字外,还可以使用图标修饰自己。为了允许在Swing组件中使用图标,Swing为Icon接口提供了一个实现类:ImageIcon,该实现类代表一个图像图标。
  3. 支持插拔式的外观风格。每个JComponent对象都有一个相应的ComponentUI对象,为它完成所有的绘画、事件处理、决定尺寸大小等工作。ComponentUI对象依赖当前使用的PLAF,使用UIManager.setLookAndFeel()方法可以改变图形界面的外观风格。
  4. 支持设置边框。Swing组件可以设置一个或多个边框。Swing中提供了各式各样的边框供用户使用,也能建立组合边框或自己设置边框。一种空白边框可以用于增大组件,同时协助布局管理器对容器中的组件进行合理的布局。

每个Swing组件都有一个对应的UI类。例如JButton组件就有一个对应的ButtonUI类来作为UI代理。每个Swing组件的UI代理的类名总是将该Swing组件类名的J去掉,然后在后面添加UI后缀。UI代理类通常是一个抽象基类,不同的PLAF会有不同的UI代理实现类。Swing类库中包含了几套UI代理,分别放在不同的包下,每套UI代理都几乎包含了所有Swing组件的ComponentUI实现,每套这样的实现都被称为一种PLAF实现。

Java图形化界面编程之——Swing_第2张图片

 代码演示:

Java图形化界面编程之——Swing_第3张图片

package Package2;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class PLAFDemo {
    //创建窗口JFrame
    JFrame jFrame = new JFrame("测试Swing基本组件");

    //创建菜单
    JMenuBar jMenuBar = new JMenuBar();
    JMenu fileMenu = new JMenu("文件");
    JMenu editMenu = new JMenu("编辑");
    JMenuItem autoItem = new JMenuItem("自动换行");
    JMenuItem copyItem = new JMenuItem("复制");
    JMenuItem pasteItem = new JMenuItem("粘贴");
    JMenu formatMenu = new JMenu("格式");
    JMenuItem commentItem = new JMenuItem("注释");
    JMenuItem cancelCommentItem = new JMenuItem("取消注释");

    //创建文本域
    JTextArea jTextArea = new JTextArea(8,20);

    String[] colors = {"红色","绿色","蓝色"};
    //创建列表框
    JList colorList = new JList(colors);

    //创建选择相关组件
    JComboBox colorSelect = new JComboBox();
    ButtonGroup buttonGroup = new ButtonGroup();
    JRadioButton male = new JRadioButton("男",false);
    JRadioButton female = new JRadioButton("女",true);
    JCheckBox isMarried = new JCheckBox("是否已婚?",true);

    //声明文本框
    JTextField jTextField = new JTextField(40);
    JButton ok = new JButton("确定");
    //JButton ok = new JButton("确定",new ImageIcon("文件路径\\文件名.png"));

    //声明右键菜单
    JPopupMenu jPopupMenu = new JPopupMenu();
    ButtonGroup popupButtonGroup = new ButtonGroup();
    JRadioButtonMenuItem metalItem = new JRadioButtonMenuItem("Metal风格");
    JRadioButtonMenuItem nimbusItem = new JRadioButtonMenuItem("Nimbus风格");
    JRadioButtonMenuItem windowsItem = new JRadioButtonMenuItem("Windows风格",true);
    JRadioButtonMenuItem windowsClassicItem = new JRadioButtonMenuItem("Windows经典风格");
    JRadioButtonMenuItem motifItem = new JRadioButtonMenuItem("Motif风格");

    //初始化界面,组装视图
    public void init() {
        //组装顶部菜单
        formatMenu.add(commentItem);
        formatMenu.add(cancelCommentItem);
        editMenu.add(autoItem);
        editMenu.addSeparator(); //添加分割线
        editMenu.add(copyItem);
        editMenu.add(pasteItem);
        editMenu.addSeparator();
        editMenu.add(formatMenu);
        jMenuBar.add(fileMenu);
        jMenuBar.add(editMenu);
        jFrame.setJMenuBar(jMenuBar);

        //组装右键菜单
        popupButtonGroup.add(metalItem);
        popupButtonGroup.add(nimbusItem);
        popupButtonGroup.add(windowsItem);
        popupButtonGroup.add(windowsClassicItem);
        popupButtonGroup.add(motifItem);
        ActionListener listener = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //当前选择的是哪一个风格
                String actionCommand = e.getActionCommand();
                try {
                    changeStyle(actionCommand); //改变外观
                } catch (UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                } catch (ClassNotFoundException ex) {
                    ex.printStackTrace();
                } catch (InstantiationException ex) {
                    ex.printStackTrace();
                } catch (IllegalAccessException ex) {
                    ex.printStackTrace();
                }
            }
        };
        metalItem.addActionListener(listener);
        nimbusItem.addActionListener(listener);
        windowsItem.addActionListener(listener);
        windowsClassicItem.addActionListener(listener);
        motifItem.addActionListener(listener);
        jPopupMenu.add(metalItem);
        jPopupMenu.add(nimbusItem);
        jPopupMenu.add(windowsItem);
        jPopupMenu.add(windowsClassicItem);
        jPopupMenu.add(motifItem);

        //不需要再监听鼠标事件
        jTextArea.setComponentPopupMenu(jPopupMenu);

        //组装选择相关的组件
        JPanel selectPanel = new JPanel();
        colorSelect.addItem("红色");
        colorSelect.addItem("绿色");
        colorSelect.addItem("蓝色");
        selectPanel.add(colorSelect);
        buttonGroup.add(male);
        buttonGroup.add(female);
        selectPanel.add(male);
        selectPanel.add(female);
        selectPanel.add(isMarried);

        //组装文本域和选择相关组件
        Box topLeftBox = Box.createVerticalBox();
        topLeftBox.add(jTextArea);
        topLeftBox.add(selectPanel);

        //组装顶部
        Box topBox = Box.createHorizontalBox();
        topBox.add(topLeftBox);
        topBox.add(colorList);
        //把顶部放到中间区域
        jFrame.add(topBox,BorderLayout.CENTER);

        //组装底部
        JPanel bottomPanel = new JPanel();
        bottomPanel.add(jTextField);
        bottomPanel.add(ok);
        jFrame.add(bottomPanel, BorderLayout.SOUTH);

        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //设置关闭窗口退出程序的功能
        //通过pack()方法设置最佳大小
        jFrame.pack();
        //设置Frame的位置和大小
        jFrame.setBounds(400,200,500,300);
        //设置Frame可见
        jFrame.setVisible(true);
    }

    //定义一个方法,用于改变界面风格
    private void changeStyle(String command) throws UnsupportedLookAndFeelException, 
            ClassNotFoundException, InstantiationException, IllegalAccessException {
        switch (command) {
            case "Metal风格":
                UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
                break;
            case "Nimbus风格":
                UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
                break;
            case "Windows风格":
                UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
                break;
            case "Windows经典风格":
                UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel");
                break;
            case "Motif风格":
                UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");
                break;
        }
        //刷新组件外观
        SwingUtilities.updateComponentTreeUI(jFrame.getContentPane()); //刷新顶层容器所有内容
        SwingUtilities.updateComponentTreeUI(jMenuBar);
        SwingUtilities.updateComponentTreeUI(jPopupMenu);
    }

    public static void main(String[] args) throws UnsupportedLookAndFeelException, 
            ClassNotFoundException, InstantiationException, IllegalAccessException {
        new PLAFDemo().init();
    }
}

2.3、为组件设置边框

很多情况下,我们常常喜欢给不同的组件设置边框,从而让界面的层次感更明显,Swing中提供了Border对象来代表一个边框,下图是Border的继承体系图:

Java图形化界面编程之——Swing_第4张图片

特殊的Border:

  • TitledBorder:它的作用并不是直接为其它组件添加边框,而是为其它边框设置标题,创建该类的对象时,需要传入一个其它的Border对象;
  • CompoundBorder:用来组合其它两个边框,创建该类的对象时,需要传入其它两个Border对象,一个作为内边框,一个作为外边框。

给组件设置边框的步骤:

  1. 使用BorderFactory或者XxxBorder()创建创建Border的实例对象;
  2. 调用Swing组件的setBorder(Border b)方法为组件设置边框。

代码演示:

Java图形化界面编程之——Swing_第5张图片

package Package2;

import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;

public class BorderDemo {
    JFrame jFrame = new JFrame("测试边框");

    //组装视图
    public void init() throws FontFormatException {
        //将jFrame的布局修改为GridLayout
        jFrame.setLayout(new GridLayout(2,4));

        //往网格中填充不同的jPanel组件,并且设置边框和内容
        Border bevelBorder = BorderFactory.createBevelBorder(BevelBorder.RAISED,
                Color.RED,Color.GREEN,Color.BLUE,Color.GRAY);
        jFrame.add(getJPanelWithBorder(bevelBorder,"BevelBorder"));
        Border lineBorder = BorderFactory.createLineBorder(Color.ORANGE,10);
        jFrame.add(getJPanelWithBorder(lineBorder,"LineBorder"));
        Border emptyBorder = BorderFactory.createEmptyBorder(10,5,20,30);
        jFrame.add(getJPanelWithBorder(emptyBorder,"EmptyBorder"));
        Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.RAISED,Color.RED,Color.GREEN);
        jFrame.add(getJPanelWithBorder(etchedBorder,"EtchedBorder"));

        TitledBorder titledBorder = new TitledBorder(new LineBorder(Color.ORANGE,10),"测试标题",
                TitledBorder.LEFT,TitledBorder.BOTTOM,new Font("StSong",Font.BOLD,15),Color.BLUE);
        jFrame.add(getJPanelWithBorder(titledBorder,"TitledBorder"));
        MatteBorder matteBorder = new MatteBorder(10,5,20,10,Color.GREEN);
        jFrame.add(getJPanelWithBorder(matteBorder,"MatteBorder"));
        CompoundBorder compoundBorder = new CompoundBorder(new LineBorder(Color.RED,10),titledBorder);
        jFrame.add(getJPanelWithBorder(compoundBorder,"CompoundBorder"));

        //通过pack()方法设置最佳大小
        jFrame.pack();
        //设置Frame的位置和大小
        jFrame.setBounds(400,200,500,300);
        //设置Frame可见
        jFrame.setVisible(true);
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    }

    public JPanel getJPanelWithBorder(Border border, String content) {
        JPanel jPanel = new JPanel();
        jPanel.add(new JLabel(content));
        jPanel.setBorder(border); //设置边框
        return jPanel;
    }

    public static void main(String[] args) throws FontFormatException {
        new BorderDemo().init();
    }

2.4、使用JToolBar创建工具条

Swing提供了JToolBar类来创建工具条,并且可以往JToolBar中添加多个工具按钮。

JToolBar API:

方法名称 方法功能
JToolBar(String name, int orentation) 创建一个名字为name,方向为orentation的工具条对象,其orentation的取值可以是SwingConstants.HORIZONTAL或SwingConstants.VERTICAL。
JButton add(Action a) 通过Action对象为JToolBar工具条添加对应的工具按钮。
addSeparator(Dimension size) 向工具条中添加指定大小的分隔符。
setFloatable(boolean b) 设定工具条是否可以被拖动。
setMargin(Insets m) 设置工具条与工具按钮的边距。
setOrentation(int o) 设置工具条的方向。

add(Action a)方法:上述API中add(Action a)这个方法比较难理解,为什么呢?Action接口是ActionListener的一个子接口,那么它就代表一个事件监听器,而这里add方法是在给工具条添加一个工具按钮,为什么传递的是一个事件监听器呢?

首先要明确的是不管菜单条中的菜单项还是工具条中的工具按钮,最终肯定是需要点击来完成一些操作,所以JToolBar以及JMenu都提供了更加便捷 的添加子组件的方法add(Action a),在这个方法的内部会做如下几件事:

  1. 创建一个适用于该容器的组件(例如,在工具栏中创建一个工具按钮);
  2. 从Action对象中获得对应的属性来设置该组件(通过Icon来设置按钮图标);
  3. 把Action监听器注册到刚才创建的组件上。

代码演示:

Java图形化界面编程之——Swing_第6张图片

import java.awt.event.ActionEvent;

public class JToolBarDemo {
    JFrame jFrame = new JFrame("测试工具条");
    JTextArea jTextArea = new JTextArea(6,35);

    //声明工具条相关内容
    JToolBar jToolBar = new JToolBar("播放工具条",SwingConstants.HORIZONTAL);
    //创建三个Action对象
    Action pre = new AbstractAction("上一曲") { //还可添加Icon
        @Override
        public void actionPerformed(ActionEvent e) {
            jTextArea.append("上一曲\n");
        }
    };
    Action pause = new AbstractAction("暂停") {
        @Override
        public void actionPerformed(ActionEvent e) {
            jTextArea.append("暂停播放\n");
        }
    };
    Action next = new AbstractAction("下一曲") {
        @Override
        public void actionPerformed(ActionEvent e) {
            jTextArea.append("下一曲\n");
        }
    };

    public void init() {
        //通过Action对象来创建JButton
        JButton preBtn = new JButton(pre);
        JButton pauseBtn = new JButton(pause);
        JButton nextBtn = new JButton(next);

        jToolBar.add(preBtn);
        jToolBar.addSeparator();
        jToolBar.add(pauseBtn);
        jToolBar.addSeparator();
        jToolBar.add(nextBtn);

        //让工具条可以拖动
        jToolBar.setFloatable(true);

        //文本框默认不支持滚动条
        //把一个组件设置到JScrollPane,那么组件就支持滚动条了
        JScrollPane jScrollPane = new JScrollPane(jTextArea);

        jFrame.add(jToolBar, BorderLayout.NORTH);
        jFrame.add(jScrollPane,BorderLayout.CENTER);

        //通过pack()方法设置最佳大小
        jFrame.pack();
        //设置Frame的位置和大小
        jFrame.setBounds(400,200,500,300);
        //设置Frame可见
        jFrame.setVisible(true);
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {
        new JToolBarDemo().init();
    }
}

2.5、JColorChooser和JFileChooser

Swing提供了JColorChooser和JFileChooser这两种对话框,可以很方便地完成颜色的选择和本地文件的选择。

2.5.1、JColorChooser

JColorChooser用于创建颜色选择器对话框,该类的用法非常简单,只需要调用它的静态方法就可以快速生成一个颜色选择对话框:

public static Color showDialog(Component component, String title, Color initialColor)

参数:
component:指定当前对话框的父组件;
title:当前对话框的名称;
initialColor:指定默认选中的颜色。

返回值:
返回用户选中的颜色。

代码演示:

Java图形化界面编程之——Swing_第7张图片

package Package2;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;

public class JColorChooserDemo {
    JFrame jFrame = new JFrame("测试颜色选择器");

    JTextArea jTextArea = new JTextArea("张三",6,30);

    //声明按钮
    JButton btn = new JButton(new AbstractAction("改变文本框背景颜色") {
        @Override
        public void actionPerformed(ActionEvent e) {
            //弹出一个颜色选择器
            Color result = JColorChooser.showDialog(jFrame, "颜色选择器", Color.WHITE);
            //修改文本框背景
            jTextArea.setBackground(result);
        }
    });

    //组装视图
    public void init() {
        jFrame.add(jTextArea);
        jFrame.add(btn, BorderLayout.SOUTH);

        //通过pack()方法设置最佳大小
        jFrame.pack();
        //设置Frame的位置和大小
        jFrame.setBounds(400,200,500,300);
        //设置Frame可见
        jFrame.setVisible(true);
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {
        new JColorChooserDemo().init();
    }
}

2.5.2、JFileChooser

JFileChooser的功能与AWT中的FileDialog基本相似,也是用于生成"打开文件"、“保存文件”对话框。与FileDialog不同的是,JFileChooser无需依赖于本地平台的GUI,它由100%纯java实现,在所有平台上具有完全相同的行为,并可以在所有平台上具有相同的外观风格。

JFileChooser使用步骤:

1、创建JFileChooser对象

  • JFileChooser chooser = new JFileChooser("D:\\a"); //指定默认打开的本地磁盘路径

2、调用JFileChoose的一系列可选方法,进行初始化:

  • setSelectedFile(File file) / setSelectedFiles(Filep[ selectedFiles):设置默认选中的文件
  • setMultiSelectionEnabled(boolean b):设置是否允许多选,默认是单选
  • setFileSelectionMode(int mode):设置可以选择内容,例如文件、文件夹等,默认只能选择文件

3、打开文件对话框:

  • showOpenDialog(Component parent):打开问价加载对话框,并指定父组件
  • showSaveDialog(Component parent):打开文件保存对话框,并指定父组件

4、获取用户选择的结果:

  • File getSelectedFile():获取用户选择的一个文件
  • File[] getSelectedFiles():获取用户选择的多个文件

代码演示:

Java图形化界面编程之——Swing_第8张图片

package Package2;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class JFileChooserDemo {
    JFrame jFrame = new JFrame("测试颜色选择器");

    //创建菜单栏
    JMenuBar jMenuBar = new JMenuBar();
    JMenu fileMenu = new JMenu("文件");
    JMenuItem openMenuItem = new JMenuItem(new AbstractAction("打开") {
        @Override
        public void actionPerformed(ActionEvent e) {
            //显示一个文件选择器
            JFileChooser jFileChooser = new JFileChooser("."); //当前项目路径
            jFileChooser.showOpenDialog(jFrame);
            //获取用户选择的文件
            File file = jFileChooser.getSelectedFile();
            //进行显示
            try {
                bufferedImage = ImageIO.read(file);
                myCanvas.repaint();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    });
    JMenuItem saveMenuItem = new JMenuItem(new AbstractAction("另存为") {
        @Override
        public void actionPerformed(ActionEvent e) {
            //显示一个文件选择器
            JFileChooser jFileChooser = new JFileChooser("."); //当前项目路径
            jFileChooser.showSaveDialog(jFrame);
            //获取用户选择的保存文件路径
            File file = jFileChooser.getSelectedFile();
            //进行保存
            try {
                ImageIO.write(bufferedImage,"jpeg",file);
                myCanvas.repaint();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    });

    BufferedImage bufferedImage;
    //Swing提供的组件,都使用了图像缓冲区技术
    private class MyCanvas extends JPanel {
        @Override
        public void paint(Graphics g) {
            //把图片绘制到组件上即可
            g.drawImage(bufferedImage,0,0,null);
        }
    }
    MyCanvas myCanvas = new MyCanvas();

    //组装视图
    public void init() {
        fileMenu.add(openMenuItem);
        fileMenu.add(saveMenuItem);
        jMenuBar.add(fileMenu);
        jFrame.setJMenuBar(jMenuBar);

        //设置绘图区域大小
        myCanvas.setPreferredSize(new Dimension(300,200));
        jFrame.add(myCanvas);

        //通过pack()方法设置最佳大小
        jFrame.pack();
        //设置Frame的位置和大小
        jFrame.setBounds(400,200,500,300);
        //设置Frame可见
        jFrame.setVisible(true);
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {
        new JFileChooserDemo().init();
    }

2.6、使用JOptionPane

2.6.1、基本概述

通过JOptionPane可以非常方便地创建一些简单的对话框,Swing已经为这些对话框添加了相应的组件,无需程序员手动添加组件。JOptionPane提供了如下4个方法来创建对话框。

方法名称 方法功能
showMessageDialog / showInternalMessageDialog 消息对话框,告知用户某事已发生,用户只能单击“确定按钮”,类似于JavaScript的alert函数。
showConfirmDialog / showInternalConfirmDialog 确认对话框,向用户确认某个问题,用户可以选择yes、no-cancel等选项。类似于JavaScript的comfirm函数。该方法返回用户单击了哪个按钮。
showInputDialog / showInternalInputDialog 输入对话框,提示要求输入某些信息,类似于JavaScript的prompt函数。该方法返回用户输入的字符串。
showOptionDialog / showInternalOptionDialog 自定义选项对话框,允许使用自定义选项,可以取代showConfirmDialog所产生的对话框,只是用起来更复杂。

上述方法都有很多重载形式,选择其中一种最全的形式,参数解释如下:

showXxxDialog(Component parentComponent,
    Object message,
    String title,
    int optionType,
    int messageType,
    Icon icon,
    Object[] options,
    Object initialValue)

参数解释:
parentComponent:当前对话框的父组件
message:对话框上显示的信息,信息可以是字符串、组件、图片等
title:当前对话框的标题
optionType:当前对话框上显示的按钮类型:DEFAULT_OPTION、YES_NO_OPTION、YES_NO_CANCEL_OPTION、OK_CANCEL_OPTION
messageType:当前对话框的类型:ERROR_MESSAGE、INFORMATION_MESSAGE、WARNING_MESSAGE、QUSETION_MESSAGE、PLAIN_MESSAGE
icon:当前对话框左上角的图标
options:自定义下拉列表的选项
initialValue:自定义选项中的默认选中项

当用户与对话框交互结束后,不同类型对话框的返回值如下:

  • showMessageDialog:无返回值;
  • showInputDialog:返回用户输入或选择的字符串;
  • showConfirmDialog:返回一个整数代表用户选择的选项;
  • showOptionDialog:返回一个整数代表用户选择的选项,如果用户选择第一项,则返回0;如果选择第n项,则返回n-1。

对showConfirmDialog所产生的对话框,有如下几个返回值:

  • YES_OPTION:用户单击了“是”按钮后返回;
  • NO_OPTION:用户单击了“否”按钮后返回;
  • CANCEL_OPTION:用户单机了“取消”按钮后返回;
  • OK_OPTION:用户单机了“确定”按钮后返回;
  • CLOSED_OPTION:用户单机了对话框右上角的“x”按钮后返回。

2.6.2、四种对话框演示

Java图形化界面编程之——Swing_第9张图片

package Package2;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;

public class MessageDialogDemo {
    JFrame jFrame = new JFrame("四种对话框演示");
    JTextArea jTextArea = new JTextArea(6, 30);

    //声明按钮
    JPanel jPanel = new JPanel();
    JButton btn1 = new JButton(new AbstractAction("弹出消息对话框") {
        @Override
        public void actionPerformed(ActionEvent e) {
            //弹出一个消息对话框,并且显示文本域中输入的内容
            String text = jTextArea.getText();
            //参数:messageType:指定消息对话框的类型:错误消息、警告消息、问题消息...
            JOptionPane.showMessageDialog(jFrame, text, "消息对话框-ERROR_MESSAGE",
                    JOptionPane.ERROR_MESSAGE);
            JOptionPane.showMessageDialog(jFrame, text, "消息对话框-INFORMATION_MESSAGE",
                    JOptionPane.INFORMATION_MESSAGE);
            JOptionPane.showMessageDialog(jFrame, text, "消息对话框-WARNING_MESSAGE",
                    JOptionPane.WARNING_MESSAGE);
            JOptionPane.showMessageDialog(jFrame, text, "消息对话框-QUESTION_MESSAGE",
                    JOptionPane.QUESTION_MESSAGE);
            JOptionPane.showMessageDialog(jFrame, text, "消息对话框-PLAIN_MESSAGE",
                    JOptionPane.PLAIN_MESSAGE);
//            JOptionPane.showMessageDialog(jFrame,text,"消息对话框-PLAIN_MESSAGE",
//                    JOptionPane.PLAIN_MESSAGE,new ImageIcon()); //自定义图标
        }
    });
    JButton btn2 = new JButton(new AbstractAction("弹出确认对话框") {
        @Override
        public void actionPerformed(ActionEvent e) {
            //弹出确认对话框
            String text = jTextArea.getText();
            jTextArea.append("\n");
//            int result = JOptionPane.showConfirmDialog(jFrame,text,"确认对话框-DEFAULT_OPTION",
//                    JOptionPane.DEFAULT_OPTION);
//            int result = JOptionPane.showConfirmDialog(jFrame,text,"确认对话框-DEFAULT_OPTION",
//                    JOptionPane.YES_NO_OPTION);
            int result = JOptionPane.showConfirmDialog(jFrame, text, "确认对话框-DEFAULT_OPTION",
                    JOptionPane.OK_CANCEL_OPTION);
            if (result == JOptionPane.YES_OPTION) {
                jTextArea.append("用户点击了 是 按钮\n");
            }
            if (result == JOptionPane.NO_OPTION) {
                jTextArea.append("用户点击了 否 按钮\n");
            }
            if (result == JOptionPane.OK_OPTION) {
                jTextArea.append("用户点击了 确认 按钮\n");
            }
            if (result == JOptionPane.CANCEL_OPTION) {
                jTextArea.append("用户点击了 取消 按钮\n");
            }
        }
    });
    JButton btn3 = new JButton(new AbstractAction("弹出输入对话框") {
        @Override
        public void actionPerformed(ActionEvent e) {
            //弹出输入对话框
            String result = JOptionPane.showInputDialog(jFrame, "输入姓名:",
                    "输入对话框", JOptionPane.INFORMATION_MESSAGE);
            jTextArea.append("姓名:" + result + "\n");
        }
    });
    JButton btn4 = new JButton(new AbstractAction("弹出选项对话框") {
        @Override
        public void actionPerformed(ActionEvent e) {
            int result = JOptionPane.showOptionDialog(jFrame,"选择你的性别",
                    "选项对话框",JOptionPane.DEFAULT_OPTION,JOptionPane.INFORMATION_MESSAGE,
                    null,new String[]{"男","女"},"男");
            switch (result) {
                case 0:
                    jTextArea.append("性别:男\n");
                    break;
                case 1:
                    jTextArea.append("性别:女\n");
                    break;
            }
        }
    });

    //组装视图
    public void init() {
        jFrame.add(jTextArea);
        jPanel.add(btn1);
        jPanel.add(btn2);
        jPanel.add(btn3);
        jPanel.add(btn4);
        jFrame.add(jPanel, BorderLayout.SOUTH);

        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //设置关闭窗口退出程序的功能
        //通过pack()方法设置最佳大小
        jFrame.pack();
        //设置Frame的位置和大小
        //jFrame.setBounds(400, 200, 500, 300);
        //设置Frame可见
        jFrame.setVisible(true);
    }

    public static void main(String[] args) {
        new MessageDialogDemo().init();
    }
}

3、Swing中的特殊容器

Swing提供了一些具有特殊功能的容器,这些特殊容器可以用于创建一些更复杂的用户界面。

3.1、使用JSplitPane

JSplitPane用于创建一个分隔面板,它可以将一个组件(通常是一个容器)分隔成两个部分,并提供一个分隔条,用户可以拖动该分割条来调整两个部分的大小。

JSplitPane的使用步骤:

1、创建JSplitPane对象:

通过如下构造方法可以创建JSplitPane对象
JSplitPane(int newOrientation, Component newLeftComponent, Component newRightComponent)
newOrientation:指定JSplitPane容器的分割方向:
    如果值为JSplitPane.VERTICAL_SPLIT,为纵向分割;
    如果值为JSPlitPane.HORIZONTAL_SPLIT,为横向分割。
newLeftComponent:左侧或者上侧的组件。
newRightComponent:右侧或者下侧的组件。

2、设置是否开启连续布局的支持(可选):

setContinuousLayout(boolean newContinuousLayout)
默认是关闭的,如果设置为true,则打开连续布局的支持,但由于连续布局支持需要不断地重绘组件,所以效果会低一些

3、设置是否支持“一触即展”的支持(可选):

setOneTouchExpandable(boolean newValue)
默认是关闭地,如果设置为true,则打开“一触即展”的支持。

4、其它设置:

setDividerLocation(double proportionalLocation)
设置分割条的位置为JSplitPane的某个百分比

setDividerLocation(int location)
通过像素值设置分割条的位置

setDividerSize(int newSize)
通过像素值设置分割条的大小

setLeftComponent(Component comp) / setRightComponent(Component comp) /
setTopComponent(Component comp) / setBottomComponent(Component comp)
设置指定位置的组件

3.2、使用JTabbedPane

JTabbedPane可以很方便地在窗口上放置多个标签页,每个标签页相当于获得了一个与外部容器具有相同大小的组件摆放区域。通过这种方式,就可以在一个容器里放置更多的组件,例如右击桌面上的“我的电脑”图标,在弹出的快捷菜单里单击“属性”菜单页,就可以看到一个“系统属性”对话框,这个对话框里包含了若干个标签页。

如果需要使用JTabbedPane在窗口上创建标签页,则可以按如下步骤进行:

1、创建JTabbedPane对象:

JTabbedPane(int tabPlacement, int tabLayoutPolicy)
tabPlacement:指定标签标题的放置位置,可以选择SwingConstants中的四个常量:TOP、LEFT、BOTTOM、RIGHT
tabLayoutPolicy:指定当窗口不能容纳标签页标题时的布局策略,可以选择JTabbedPane.WRAP_TAB_LAYOUT和JTabbedPane.SCROLL+TAB_LAYOUT

2、通过JTabbedPane对象对标签进行增删改查:

-添加标签:
addTab(String title, Icon icon, Component component, String tip)
title:标签的名称
icon:标签的图标
component:标签对应的组件
tip:光标放到标签上的提示

-插入标签页
insertTab(String title, Icon icon, Component component, String tip, int index)
title:标签的名称
icon:标签的图标
component:标签对应的组件
tip:光标放到标签上的提示
index:在哪个索引处插入标签页

-修改标签页对应的组件
setComponentAt(int index, Component component)
index:修改哪个索引处的标签
component:标签对应的组件

-删除标签页
removeTabAt(int index)
index:删除哪个索引处的标签

3、设置当前显示的标签页:

-设置哪个索引处的标签被选中
setSelectedIndex(int index)

4、设置JTabbedPane的其它属性:

-将指定位置的禁用图标设置为icon,该图标也可以是null表示不使用禁用图标
setDisabledIcon(int index, Icon disabledIcon)

-设置指定位置的标签页是否启用
setEnabledAt(int index, boolean enabled)

-设置指定位置标签页的标题为title,该title可以是null,这表明设置该标签页的标题为空
setTitleAt(int index, String title)

-设置指定位置标签页的提示文本
setToolTipTextAt(int index, String toolTipText)

5、为JTabbedPane设置监听器

-给JTabbedPane设置监听器
addChangeListener(ChangeListener l)

代码演示:

Java图形化界面编程之——Swing_第10张图片

package Package2;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class JTabbedPaneDemo {
    JFrame jFrame = new JFrame("测试JTabbedPane");

    JTabbedPane jTabbedPane = new JTabbedPane(SwingConstants.LEFT,JTabbedPane.SCROLL_TAB_LAYOUT);

    //组装视图
    public void init() {
        //添加标签
        jTabbedPane.addTab("用户管理",null,
                new JList(new String[]{"用户1","用户2","用户3"}));
        jTabbedPane.addTab("商品管理",null,
                new JList(new String[]{"商品1","商品2","商品3"}));
        jTabbedPane.addTab("订单管理",null,
                new JList(new String[]{"订单1","订单2","订单3"}));
        //完成设置
        jTabbedPane.setEnabledAt(0,false);
        jTabbedPane.setSelectedIndex(1);
        jFrame.add(jTabbedPane);

        //监听当前标签面板的选中情况
        jTabbedPane.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                int selectedIndex = jTabbedPane.getSelectedIndex();
                JOptionPane.showMessageDialog(jFrame,"当前选中了第"+(selectedIndex+1)+"个标签");
            }
        });

        //通过pack()方法设置最佳大小
        jFrame.pack();
        //设置Frame的位置和大小
        jFrame.setBounds(400,200,500,300);
        jFrame.setResizable(false);//固定窗口大小
        //设置Frame可见
        jFrame.setVisible(true);
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {
        new JTabbedPaneDemo().init();
    }
}

4、JProgressBar、ProgressMonitor、BoundedRangeModel实现进度条

进度条是图形界面中广泛使用的GUI组件,当复制一个较大的文件时,操作系统会显示一个进度条,用于标识复制操作完成的比例。

4.1、创建进度条

使用JProgressBar创建进度条的步骤:

1、创建JProgressBar对象:

public JProgressBar(int orient, int min, int max)
orient:方向
min:最小值
max:最大值

2、设置属性:

-设置进度条是否有边框
setBorderPainted(boolean b)

-设置当前进度条是不是进度不确定的进度条,如果是,则看到一个滑块在进度条中左右移动
setIndeterminate(boolean newValue)

-设置进度条是否显示当前完成的百分比
setStringPainted(boolean b)

3、获取和设置当前进度条的进度状态

-设置当前进度值
setValue(int n)

-获取进度条的完成百分比
double getPercentComplete()

-返回进度字符串的当前值
String getString()

代码演示:

Java图形化界面编程之——Swing_第11张图片

package Package2;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class Test {
    JFrame jFrame = new JFrame("测试进度条");

    //创建一个水平进度条
    JProgressBar bar = new JProgressBar(JProgressBar.HORIZONTAL, 0, 100);
    JCheckBox indeterminate = new JCheckBox("不确定进度");
    JCheckBox noBorder = new JCheckBox("不绘制边框");

    //组装视图
    public void init() throws InterruptedException {
        //处理复选框的点击行为
        indeterminate.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //获取一下indeterminate复选框有没有被选中
                boolean selected = indeterminate.isSelected();
                //设置当前进度条是否不确定进度
                bar.setIndeterminate(selected);
                //设置是否绘制百分比显示
                bar.setStringPainted(!selected);
            }
        });
        noBorder.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //获取一下noBorder复选框有没有被选中
                boolean selected = noBorder.isSelected();
                bar.setBorderPainted(!selected);
            }
        });
        Box vBox = Box.createVerticalBox();
        vBox.add(indeterminate);
        vBox.add(noBorder);

        //设置进度条的属性
        bar.setStringPainted(true);
        bar.setBorderPainted(true);

        //把当前布局方式修改FlowLayout
        jFrame.setLayout(new FlowLayout());
        jFrame.add(vBox);
        jFrame.add(bar);

        //通过pack()方法设置最佳大小
        jFrame.pack();
        //设置Frame的位置和大小
        jFrame.setBounds(400, 200, 500, 300);
        jFrame.setResizable(false);//固定窗口大小
        //设置Frame可见
        jFrame.setVisible(true);
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //开启子线程
        SimulaterActivity simulaterActivity = new SimulaterActivity(bar.getMaximum(), bar);
        new Thread(simulaterActivity).start();

        //设置一个定时任务
//        Timer timer = new Timer(500, new ActionListener() {
//            @Override
//            public void actionPerformed(ActionEvent e) {
//                //读取线程任务的当前完成量,设置给进度条
//                int current = simulaterActivity.getCurrent();
//                bar.setValue(current);
//            }
//        });
//        timer.start(); //开启定时线程
//        //监听进度条的任务变化
//        bar.addChangeListener(new ChangeListener() {
//            @Override
//            public void stateChanged(ChangeEvent e) {
//                int value = bar.getValue();
//                if(value == simulaterActivity.getAmount()) {
//                    timer.stop();
//                }
//            }
//        });
    }

    private class SimulaterActivity implements Runnable {
        private JProgressBar bar;
        //记录任务总量
        private int amount;
        //记录当前任务完成量
        private volatile int current;
        //volatile:内存可见,本线程修改这个值,其它线程可以立刻看到修改后的效果

        public SimulaterActivity(int amount, JProgressBar bar) {
            this.bar = bar;
            this.amount = amount;
        }

        public int getAmount() {
            return amount;
        }

        public void setAmount(int amount) {
            this.amount = amount;
        }

        public int getCurrent() {
            return current;
        }

        public void setCurrent(int current) {
            this.current = current;
        }

        @Override
        public void run() {
            //子线程的任务:通过模拟循环修改进度条的进度
            while (current <= amount) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                current++;
                bar.setValue(current);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Test().init();
    }
}

之前我们学习过,其实Swing中很多组件的界面与数据都采用了MVC的设计思想:

Java图形化界面编程之——Swing_第12张图片

Swing组件大都将外观显示和内部数据分离,JProgressBar也不例外,JProgressBar组件有一个内置的用于保存其状态数据的Model对象,这个对象由BoundedRangeModel对象表示,程序调用JProgressBar对象的方法完成进度百分比的设置,监听进度条的数据变化,其实都是通过它内置的BounderRangeModel对象完成的。

4.2、创建进度对话框

ProgressMonitor的用法与JProgressBar的用法基本相似,只是ProgressMonitor可以直接创建一个进度对话框,它提供了下面的构造器完成对话框的创建:

public ProgressMonitor(Component parentComponent, Object message, String note, int min, int max)
parentComponent:对话框的父组件
message:对话框的描述信息
note:进度的提示信息
min:进度条的最小值
max:进度条的最大值

使用ProgressMonitor创建的对话框里包含的进度条是非常固定的,程序甚至不能设置该进度条是否包含边框(总是包含边框),不能设置进度不确定,不能改变进度条的方向(总是水平方向)。

代码演示:

Java图形化界面编程之——Swing_第13张图片

package Package2;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class ProgressMonitorDemo {
    Timer timer;
    public void init() {
        //创建一个ProgressMonitor对象
        ProgressMonitor progressMonitor = new ProgressMonitor(null,
                "等待任务完成","已完成:",0,100);
        SimulaterActivity simulaterActivity = new SimulaterActivity(100);
        new Thread(simulaterActivity).start();

        //设置定时任务
        timer = new Timer(200, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //读取当前任务量,修改进度
                int current = simulaterActivity.getCurrent();
                progressMonitor.setProgress(current);

                //判断是否点击了取消按钮,停止定时任务,关闭对话框,退出程序
                if(progressMonitor.isCanceled()) {
                    timer.stop();
                    progressMonitor.close();
                    System.exit(0);
                }
            }
        });
        timer.start();
    }

    public static void main(String[] args) {
        new ProgressMonitorDemo().init();
    }

    //定义一个线程任务,模拟耗时操作
    private class SimulaterActivity implements Runnable {
        //内存可见
        private volatile int current = 0;
        private int amount;

        public SimulaterActivity(int amount) {
            this.amount = amount;
        }

        public int getCurrent() {
            return current;
        }

        public void setCurrent(int current) {
            this.current = current;
        }

        public int getAmount() {
            return amount;
        }

        public void setAmount(int amount) {
            this.amount = amount;
        }

        @Override
        public void run() {
            while(current <= amount) {
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                current ++;
            }
        }
    }
}

5、JList、JComboBox实现列表框

无论从哪个角度来看,JList和JComboBox都是及其相似的,它们都有一个列表框,只是JComboBox的列表框需要以下拉方式显示出来;JList和JComboBox都可以通过调用setRenderer()方法来改变列表项的表现形式;甚至维护这两个组件的Model都是相似的,JList使用ListModel,JComboBox使用ComboBoxModel,而ComboBoxModel是ListModel的子类。

5.1、简单列表框

使用JList或JComboBox实现简单列表框的步骤:

1、创建JList或JComboBox对象

-创建JList对象,把listData数组中的每一项内容转换成一个列表项展示
JList(final E[] listData)

-创建JList对象,把listData数组中的每项内容转换成一个列表项展示
JList(final Vector listData)

JComboBox(E[] items)

JComboBox(Vector items)

2、设置JList或ComboBox的外观行为

-----------JList-------------
-在已经选中列表项的基础上,增加选中从anchor到lead索引范围内的所有列表项
addSelectionInterval(int anchor, int lead)

-设置列表项的高度和宽度
setFixedCellHeight(int height) / setFixedCellWidth(int width)

-设置列表框的布局方向
setLayoutOrientation(int layoutOrientation)

-设置默认选中项
setSelectedIndex(int index)

-设置默认选中的多个列表项
setSelectedIndices(int[] indices)

-设置默认选中项,并滚动到该项显示
setSelectedValue(Object anObject, boolean shouldScroll)

-设置选中项的背景颜色
setSelectionBackground(Color selectionBackground)

-设置选中项的前景色
setSelectionForeground(Color selectionForeground)

-设置从anchor到lead范围内的所有列表项被选中
setSelectionInterval(int anchor, int lead)

-设置选中模式,默认没有限制,也可以设置为单选或者区域选中
setSelectionMode(int selectionMode)

-设置列表框的可视高度足以显示多少行列表项
setVisibleRowCount(int visibleRowCount)

-----------JComboBox-------------------
-设置是否可以直接修改列表文本框的值,默认为不可以
setEditable(boolean aFlag)

-设置列表框的可视高度足以显示多少行列表项
setMaximumRowCount(int count)

-设置默认选中项
setSelectedIndex(int anIndex)

-根据列表项的值,设置默认选中项
setSelectedItem(Object anObject)

3、设置监听器,监听列表项的变化,JList通过addListSelectionListener完成,JComboBox通过addItemListener完成。

代码演示:

Java图形化界面编程之——Swing_第14张图片

package Package2;

import javax.swing.*;
import javax.swing.border.EtchedBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.List;
import java.util.Vector;

public class JListDemo {
    JFrame jFrame = new JFrame("列表框测试");
    String[] names = {"张三","李四","王五","赵六"};

    //定义布局选择按钮所在的面板
    JPanel layoutPanel = new JPanel();
    ButtonGroup layoutGroup = new ButtonGroup();

    //定义选择模式按钮所在面板
    JPanel selectModePanel = new JPanel();
    ButtonGroup selectModeGroup = new ButtonGroup();

    //定义文本域
    JTextArea showNames = new JTextArea(4,40);

    //用一个字符串数组来创建一个JList对象
    JList nameList;
    JComboBox nameSelector;

    //组装视图
    public void init() {
        //组装JList相关内容
        nameList = new JList(names);
        addBtn2LayoutPanel("纵向滚动",JList.VERTICAL);
        addBtn2LayoutPanel("纵向换行",JList.VERTICAL_WRAP);
        addBtn2LayoutPanel("横向换行",JList.HORIZONTAL_WRAP);

        addBtn2SelectModePanel("无限制",ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        addBtn2SelectModePanel("单选",ListSelectionModel.SINGLE_SELECTION);
        addBtn2SelectModePanel("单范围",ListSelectionModel.SINGLE_INTERVAL_SELECTION);

        //对JList做设置
        nameList.setVisibleRowCount(3);
        nameList.setSelectionInterval(1,3);

        //处理条目选中事件
        nameList.addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                //获取当前选中的条目
                List selectedValuesList = nameList.getSelectedValuesList();
                //把当前条目的内容设置到文本域中
                showNames.setText("");
                for(String str: selectedValuesList) {
                    showNames.append(str+"\n");
                }
            }
        });

        Box nameListVBox = Box.createVerticalBox();
        nameListVBox.add(new JScrollPane(nameList));
        nameListVBox.add(layoutPanel);
        nameListVBox.add(selectModePanel);

        //组装JComboBox
        Vector vector = new Vector<>();
        nameSelector = new JComboBox<>();
        List list = List.of("张三", "李四", "王五", "赵六");
        vector.addAll(list);
        nameSelector = new JComboBox<>(vector);
        nameSelector.setEditable(true);
        nameSelector.setMaximumRowCount(3);
        nameSelector.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                //获取当前已经选中的条目,把内容设置到文本域中
                Object selectedItem = nameSelector.getSelectedItem();
                showNames.setText("");
                showNames.setText(selectedItem.toString());
            }
        });

        //组装顶部的左右两部分
        Box hBox = Box.createHorizontalBox();
        hBox.add(nameListVBox);
        JPanel nameSelectPanel = new JPanel();
        nameSelectPanel.add(nameSelector);
        hBox.add(nameSelectPanel);

        //组装底部
        JPanel bottomPanel = new JPanel();
        bottomPanel.setLayout(new BorderLayout());
        bottomPanel.add(new JLabel("输出名字:"),BorderLayout.NORTH);
        bottomPanel.add(showNames);

        //组装整体
        Box wholeBox = Box.createVerticalBox();
        wholeBox.add(hBox);
        wholeBox.add(bottomPanel);
        jFrame.add(wholeBox);

        //通过pack()方法设置最佳大小
        jFrame.pack();
        //设置Frame的位置和大小
        jFrame.setBounds(400,200,500,300);
        //设置Frame可见
        jFrame.setVisible(true);
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    //封装方法,往layoutPanel中添加单选按钮
    public void addBtn2LayoutPanel(String name, int layoutType) {
        //设置标题边框
        layoutPanel.setBorder(new TitledBorder(new EtchedBorder(),"确定选项布局"));
        //创建单选按钮
        JRadioButton button = new JRadioButton(name);
        layoutPanel.add(button);
        if(layoutGroup.getButtonCount() == 0) {
            button.setSelected(true);
        }
        layoutGroup.add(button);
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                nameList.setLayoutOrientation(layoutType);
            }
        });
    }

    //封装方法,往selectModePanel中添加按钮
    public void addBtn2SelectModePanel(String name, int selectionModel) {
        //设置标题边框
        selectModePanel.setBorder(new TitledBorder(new EtchedBorder(),"确定选择模式"));
        //创建单选按钮
        JRadioButton button = new JRadioButton(name);
        selectModePanel.add(button);
        if(selectModeGroup.getButtonCount() == 0) {
            button.setSelected(true);
        }
        selectModeGroup.add(button);
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                nameList.setSelectionMode(selectionModel);
            }
        });
    }

    public static void main(String[] args) {
        new JListDemo().init();
    }
}

5.2、不强制存储列表项的ListModel和ComboBoxModel

与JProgressBar一样,JList和JComboBox也采用了MVC的设计模式,JList和JComboBox只负责外观的显示,而组件底层的状态数据则由对应的Model来维护。JList对应的Model是ListModel接口,JComboBox对应的Model是ComboBox接口,其代码如下:

public interface ListModel {
    int getSize();
    E getElementAt(int index);
    void addListDataListener(ListDataListener l);
    void removeListDataListener(ListDataListener l);
}

public interface ComboBoxModel extends ListModel {
    void setSelectedItem(Object anItem);
    Object getSelectedItem();
}

从上面接口来看,这个ListModel不管JList里的所有列表项的存储形式,它甚至不强制存储所有的列表项,只要ListModel的实现类提供了getSize()和getElementAt()两个方法,JList就可以根据该ListModel对象来生成列表框。ComboBoxModel继承了ListModel,它添加了“选择项”的概念,选择项代表JComboBox显示区域内可见的列表项。

在使用JList和JComboBox时,除了可以使用jdk实现的Model实现类,程序员自己也可以根据需求,自己定义Model的实现类,实现对应的方法使用。

代码演示:

Java图形化界面编程之——Swing_第15张图片

package Package2;

import javax.swing.*;
import java.math.BigDecimal;
import java.math.RoundingMode;

public class NumberComboBoxListModel extends NumberListModel implements ComboBoxModel {
    //当前选中条目的索引
    private int selectedId;

    public NumberComboBoxListModel(BigDecimal start, BigDecimal step, BigDecimal end) {
        super(start, step, end);
    }

    @Override
    public void setSelectedItem(Object anItem) {
        if(anItem instanceof BigDecimal) {
            BigDecimal cur = (BigDecimal) anItem;
            selectedId = cur.subtract(super.start).divide(super.step,2, RoundingMode.HALF_DOWN).intValue();
        }
    }

    @Override
    public Object getSelectedItem() {
        BigDecimal item = new BigDecimal(selectedId).multiply(super.step).add(super.start).
                setScale(1,RoundingMode.HALF_DOWN);
        return item;
    }
}


package Package2;

import javax.swing.*;
import java.math.BigDecimal;
import java.math.RoundingMode;

public class NumberListModel extends AbstractListModel {
    BigDecimal start;
    BigDecimal step;
    BigDecimal end;

    public NumberListModel(BigDecimal start, BigDecimal step, BigDecimal end) {
        this.start = start;
        this.step = step;
        this.end = end;
    }

    @Override
    public int getSize() {
        //计算条目数量
        int floor = (int)Math.floor(end.subtract(start).
                divide(step,2, RoundingMode.HALF_DOWN).doubleValue());
        return floor+1;
    }

    @Override
    public BigDecimal getElementAt(int index) {
        //根据指定的索引获得指定的条目
        BigDecimal elem = new BigDecimal(index).multiply(step).add(start).
                setScale(2,RoundingMode.HALF_DOWN);
        return elem;
    }
}


package Package2;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.math.BigDecimal;
import java.util.List;

public class ListModelDemo {
    JFrame jFrame = new JFrame("测试ListModel");

    JList jList = new JList<>(new NumberListModel(
            new BigDecimal(1),new BigDecimal(21),new BigDecimal(2)));
    JComboBox jComboBox = new JComboBox<>(new NumberComboBoxListModel(
            new BigDecimal(0.1),new BigDecimal(1.2),new BigDecimal(0.1)));
    JLabel label = new JLabel("您选择的值是:");
    JTextField jTextField = new JTextField(15);

    //组装视图
    public void init() {
        //组装顶部
        jList.setVisibleRowCount(4);
        jList.setSelectionInterval(2,4);
        jList.setFixedCellWidth(90);
        jList.setFixedCellHeight(40);
        jList.addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                List selectedValuesList = jList.getSelectedValuesList();
                jTextField.setText("");
                for(BigDecimal item: selectedValuesList) {
                    jTextField.setText(jTextField.getText()+item.toString()+"");
                }
            }
        });

        jComboBox.setMaximumRowCount(4);
        jComboBox.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                Object selectedItem = jComboBox.getSelectedItem();
                jTextField.setText(selectedItem.toString());
            }
        });

        Box hBox = Box.createHorizontalBox();
        hBox.add(new JScrollPane(jList));
        JPanel tempPanel = new JPanel();
        tempPanel.add(jComboBox);
        hBox.add(tempPanel);
        jFrame.add(hBox);

        //组装底部
        JPanel bottomPanel = new JPanel();
        bottomPanel.add(label);
        bottomPanel.add(jTextField);
        jFrame.add(bottomPanel, BorderLayout.SOUTH);

        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //设置关闭窗口退出程序的功能
        //通过pack()方法设置最佳大小
        jFrame.pack();
        //设置Frame的位置和大小
        jFrame.setBounds(400,200,500,300);
        //设置Frame可见
        jFrame.setVisible(true);
    }

    public static void main(String[] args) {
        new ListModelDemo().init();
    }
}

5.3、强制存储列表项的DefaultListModel和DefaultComboBoxModel

前面只是介绍了如何创建JList、JComboBox对象,当调用JList和JComboBox构造方法时传入数组或Vector作为参数,这些数组元素或集合元素将会作为列表项。当使用JList或JComboBox时常常还需要动态地增加、删除列表项,例如JComboBox提供了下列方法完成增删操作:

-添加一个列表项
addItem(E item)

-向指定索引处插入一个列表项
insertItemAt(E item, int index)

-删除所有列表项
removeItem(Object anObject)

-删除指定列表项
removeItem(Object anObject)

-删除指定索引处的列表项
removeItemAt(int anIndex)

JList并没有提供这些类似的方法。如果需要创建一个可以添加列表项的JList对象,则应该在创建JList时显式使用DefaultListModel作为构造参数,因为DefaultListModel作为JList的Model,它负责维护JList组件的所有列表数据,所以可以通过向DefaultListModel中添加、删除元素来实现向JList对象中添加、删除列表项。DefaultListModel提供了如下几个方法来添加、删除元素:

-在该ListModel的指定位置处插入指定元素
add(int index, E element)

-将指定元素添加到该ListModel的末尾
addElement(E obj)

-在该ListModel的指定位置插入指定元素
insertElementAt(E obj, int index)

-删除该ListModel中指定位置处的元素
Object remove(int index)

-删除该ListModel中的所有元素,并将其大小设置为0
removeAllElements()

-删除该ListModel中第一个与参数匹配的元素
removeElement(E obj)

-删除该ListModel中指定索引处的元素
removeElementAt(int index)

-删除该ListModel中指定范围内的所有元素
removeRange(int fromIndex, int toIndex)

-将该ListModel指定索引处的元素替换成指定元素
set(int index, E element)

-将该ListModel指定索引处的元素替换成指定元素
setElementAt(E obj, int index)

代码演示:

Java图形化界面编程之——Swing_第16张图片

package Package2;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class DefaultListModelDemo {
    JFrame jFrame = new JFrame("测试DefaultListModel");
    JTextField bookName = new JTextField(20);
    JButton removeBtn = new JButton("删除选中图书");
    JButton addBtn = new JButton("添加指定图书");

    //创建JList
    JList bookList;
    DefaultListModel model = new DefaultListModel<>();

    //组装视图
    public void init() {
        model.addElement("三国演义");
        model.addElement("水浒传");
        model.addElement("红楼梦");
        model.addElement("西游记");
        bookList = new JList<>(model);

        //设置JList
        bookList.setVisibleRowCount(4);
        bookList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); //设置单选
        jFrame.add(new JScrollPane(bookList));

        //组装底部
        addBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String name = bookName.getText();
                if(!name.trim().equals("")) {
                    model.addElement(name);
                }
            }
        });
        removeBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                int selectedIndex = bookList.getSelectedIndex();
                if(selectedIndex >= 0) {
                    model.remove(selectedIndex);
                }
            }
        });
        JPanel bottomPanel = new JPanel();
        bottomPanel.add(bookName);
        bottomPanel.add(addBtn);
        bottomPanel.add(removeBtn);
        jFrame.add(bottomPanel, BorderLayout.SOUTH);

        //通过pack()方法设置最佳大小
        jFrame.pack();
        //设置Frame的位置和大小
        jFrame.setBounds(400,200,500,300);
        //设置Frame可见
        jFrame.setVisible(true);
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    public static void main(String[] args) {
        new DefaultListModelDemo().init();
    }
}

5.4、使用ListCellRenderer改变列表外观

前面程序中的JList和JComboBox采用的都是简单的字符串列表项,实际上,JList和JComboBox还可以支持图标列表项,如果在创建JList或JComboBox时传入图标数组,则创建的JList和JComboBox的列表项就是图标。如果希望列表项是更复杂的组件,例如,希望像QQ程序那样每个列表项既有图标,又有字符串,此时需要使用ListCellRenderer接口的实现类对象,自定义每个条目组件的渲染过程:

public interface ListCellRenderer{
    Component getListCellRendererComponent(
    JList list, //列表组件
    E value, //当前列表项的内容
    int index, //当前列表项的索引
    boolean isSelected, //当前列表项是否被选中
    boolean cellHasFocus //当前列表项是否获取了焦点 
    )
}

通过JList的setCellRenderer(ListCellRenderer cellRenderer)方法,把自定义的ListCellRenderer对象传递给Jlist,就可以按照自定义的规则绘制列表项组件了。

代码演示:

Java图形化界面编程之——Swing_第17张图片

package Package2;

import javax.swing.*;
import java.awt.*;

public class ListRenererDemo {
    JFrame jFrame = new JFrame("测试ListRenderer");
    private String[] names = {
            "张三",
            "李四",
            "王五",
            "赵六"
    };
    //定义一个JList对象
    JList nameList = new JList(names);

    private class MyRenrerer extends JPanel implements ListCellRenderer {
        private String name; //名字
        private ImageIcon icon; //对应的图标头像

        private Color backGround; //记录背景色
        private Color foreGround; //记录前景色(文字的颜色)

        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            //重置成员变量的值
            this.name = value.toString();
            this.icon = null;
            this.backGround = isSelected? list.getSelectionBackground() : list.getBackground();
            this.foreGround = isSelected? list.getSelectionForeground() : list.getForeground();
            return this;
        }
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(60,80);
        }

        @Override
        public void paint(Graphics g) {
            //绘制列表项的内容
//            int imageWidth = icon.getImage().getWidth(null);
//            int imageHeight = icon.getImage().getHeight(null);
            //填充背景矩形
            g.setColor(backGround);
            g.fillRect(0,0,getWidth(),getHeight());
//
//            //绘制头像
//            g.drawImage(icon.getImage(),this.getWidth()/2-imageWidth/2,
//                    10,null);
            int imageHeight = 30;
            //绘制名字
            g.setColor(foreGround);
            g.setFont(new Font("StSong",Font.BOLD,18));
            g.drawString(this.name,this.getWidth()/2-this.name.length()*20/2,
                    10+imageHeight+20);
        }
    }

    //组装视图
    public void init() {
        //给JList设置ListCellRenderer对象,指定列表项绘制的组件
        nameList.setCellRenderer(new MyRenrerer());
        jFrame.add(new JScrollPane(nameList));

        //通过pack()方法设置最佳大小
        jFrame.pack();
        //设置Frame的位置和大小
        jFrame.setBounds(400,200,500,300);
        //设置Frame可见
        jFrame.setVisible(true);
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {
        new ListRenererDemo().init();
    }
}

6、JTree、TreeModel实现树

树也是图形用户界面中使用非常广泛的GUI组件。计算机的树是由一系列具有严格父子关系的

结点组成的,每个结点既可以是上一级结点的子结点,也可以是下一级结点的父结点,因此同一结点既可以是父结点,也可以是子结点。

按照结点是否包含子结点,可以把结点分为下面两类:

  • 普通结点:包含子结点的结点。
  • 叶子结点:没有子结点的结点。

按照结点是否具有唯一的父结点,可以把结点分为下面两类:

  • 根结点:没有父结点的结点,计算机中,一棵树只能有一个根结点。
  • 普通结点:具有唯一父结点的结点。

使用Swing里的JTree、TreeModel及其相关的辅助类可以很轻松地开发出计算机世界里的树。

6.1、创建树

Swing使用JTree对象来代表一棵树,JTree树中结点可以使用TreePath来标识,该对象封装了当前结点及其所有的父结点。

当一个结点具有子结点时,该结点有两种状态:

  • 展开状态:当父结点处于展开状态时,其子结点是可见的。
  • 折叠状态:当父结点处于折叠状态时,其子结点都是不可见的。

如果某个结点是可见的,则该结点的父结点(包括直接的、间接的父结点)都必须处于展开状态,只要有任意一个父结点处于折叠状态,该结点就是不可见的。

JTree常用构造方法:

-使用指定的数据模型创建JTree对象,它默认显示根结点
JTree(TreeModel new Model)

-使用root作为根结点创建JTree对象,它默认显示根结点
JTree(TreeNode root)

-使用root作为根结点创建JTree对象,它默认显示根结点
JTree(TreeNode root, boolean asksAllowsChildren)
asksAllowsChildren参数控制怎样的结点才算子结点:
    如果该参数为true,则只有当程序使用setAllowsChildren(false)显式设置某个结点不允许添加子结点时(以后也不会拥有子结点),该结点才会被JTree当成叶子结点;
    如果该参数为false,则只要某个结点当时没有子结点(不管以后是否拥有子结点),该结点都会被JTree当成叶子结点。

TreeNode继承体系及使用:接口TreeNode <- 接口MutableTreeNode <- DefaultMutableTreeNode

在构建目录树时,可以先创建很多DefaultMutableTreeNode对象,并调用它们的add()方法构建好子父级结构,最后根据结点构建一个JTree即可。

代码演示:

Java图形化界面编程之——Swing_第18张图片

package Package2;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;

public class SimpleJTreeDemo {
    JFrame jFrame = new JFrame("简单树");
    public void init() {
        //创建DefaultMutableTreeNode对象代表结点
        DefaultMutableTreeNode root = new DefaultMutableTreeNode("中国");
        DefaultMutableTreeNode guangDong = new DefaultMutableTreeNode("广东");
        DefaultMutableTreeNode guangXi = new DefaultMutableTreeNode("广西");
        DefaultMutableTreeNode fuShan = new DefaultMutableTreeNode("佛山");
        DefaultMutableTreeNode shanTou = new DefaultMutableTreeNode("汕头");
        DefaultMutableTreeNode guiLin = new DefaultMutableTreeNode("桂林");
        DefaultMutableTreeNode nanNing = new DefaultMutableTreeNode("南宁");

        //组装结点之间的关系
        root.add(guangDong);
        root.add(guangXi);

        guangDong.add(fuShan);
        guangDong.add(shanTou);

        guangXi.add(guiLin);
        guangXi.add(nanNing);

        JTree tree = new JTree(root);
        JFrame jFrame = new JFrame("简单树测试");
        jFrame.add(tree);

        //通过pack()方法设置最佳大小
        jFrame.pack();
        //设置Frame的位置和大小
        jFrame.setBounds(400,200,500,300);
        //设置Frame可见
        jFrame.setVisible(true);
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {
        new SimpleJTreeDemo().init();
    }
}

JTree的其它外观设置方法:

-设置结点之间没有连接线
tree.putClientProperty("JTree.lineStyle", "None")

-设置结点之间只有水平分割线
tree.putClientProperty("JTree.lineStyle", "Horizontal")

DefaultMutableTreeNode其它成员方法:

-按广度优先的顺序遍历以此结点为根的子树,并返回所有结点组成的枚举对象
Enumeration breadthFirstEnumeration() / preorderEnumeration()

-按深度优先的顺序遍历以此结点为根的子树,并返回所有结点组成的枚举对象
Enumeration depthFirstEnumeration() / postorderEnumeration()

-返回此结点的下一个兄弟结点
DefaultMutableTreeNode getNextSibling()

-返回此结点的父结点,如果此结点没有父结点,则返回null
TreeNode getParent()

-返回从根结点到达此结点的所有结点组成的数组
TreeNode[] getPath()

-返回此结点的上一个兄弟结点
DefaultMutableTreeNode getPreviousSibling()

-返回包含此结点的树的根结点
TreeNode getRoot()

-返回此结点和aNode最近的共同祖先
TreeNode getSharedAncestor(DefaultMutableTreeNode aNode)

-返回此结点的兄弟结点数
int getSiblingCount()

-返回该结点是否是叶子结点
boolean isLeaf()

-判断anotherNode是否是当前结点的祖先结点(包括父结点)
boolean isNodeAncestor(TreeNode anotherNode)

-如果aNode是此结点的子结点,则返回true
boolean isNodeChild(TreeNode aNode)

-如果anotherNode是此结点的后代,包括是此结点本身、此结点的子结点或此结点的子结点的后代,都将返回true
boolean isNodeDescendant(DefaultMutableTreeNode anotherNode)

-当aNode和当前结点位于同一棵树中时返回true
boolean isNodeRelated(DefaultMutableTreeNode aNode)

-返回anotherNode是否是当前结点的兄弟结点
boolean isNodeSibling(TreeNode anotherNode)

-返回当前结点是否是根结点
boolean isRoot()

6.2、编辑树结点

JTree生成的树默认是不可编辑的,不可以添加、删除结点,也不可以改变结点数据。如果想让某个JTree对象编程可编辑状态,则可以调用JTree的setEditable(boolean b)方法,传入true即可把这棵树变成可编辑的树(可以添加、删除结点),也可以改变结点数据。

编辑树结点的步骤:

1、获取当前被选中的结点:

  • 方式1:通过JTree对象的某些方法,例如TreePath getSelectionPath()等,得到一个TreePath对象,包含了从根结点到当前结点路径上的所有结点;调用TreePath对象的Object getLastPathComponent()方法,得到当前选中的结点。
  • 方式2:调用JTree对象的Object getLastSelectedPathComponent()方法获取当前被选中的结点。

2、调用JTree对象的Object getLastSelectedPathComponent()方法获取当前被选中的结点。

6.3、监听结点事件

为JTree添加监听器:

  • addTreeExpansionListener(TreeExpansionListener tel):添加树结点展开/折叠事件的监听器。
  • addTreeSelectionListener(TreeSelectionListener tsl):添加树结点选择事件的监听器。

修改JTree的选择模式:JTree专门提供了一个TreeSelectionModel对象来保存该JTree选中状态的信息,也就是说,JTree组件背后隐藏了两个model对象,其中TreeModel用于保存该JTree的所有结点数据,而TreeSelectionModel用于保存该JTree的所有选中状态的信息。程序可以改变JTree的模式,但必须先花去该JTree对应的TreeSelectionModel对象,再调用该对象的setSelectionMode(int model)方法来设置该JTree的选择模式,其中model可以有如下3种取值:

  • TreeSelectionModel.CONTINUOUS_TREE_SELECTION:可以连续选中多个TreePath。
  • TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION:该选项对于选择没有任何限制。
  • TreeSelectionModel>SINGLE_TREE_SELECTION:每次只能选择一个TreePath。

6.4、使用DefaultTreeCellRenderer改变结点外观

JTree默认的外观是比较单一的,它提供了如下几种改变结点外观的方式:

  • 使用DefaultTreeCellRenderer直接改变结点的外观,这种方式可以改变整棵树所有结点的字体、颜色和图标。
  • 为JTree指定DefaultTreeCellRenderer的扩展类对象作为JTree的结点绘制器,该绘制器负责为不同结点使用不同的字体、颜色和图标。通常使用这种方式来改变结点的外观。
  • 为JTree指定一个实现TreeCellRenderer接口的结点绘制器,该绘制器可以为不同的结点自由绘制任意内容,这是最复杂但最灵活的结点绘制器。

DefaultTreeCellrenderer提供了如下几个方法来修改结点的外观:

-设置用于非选定结点的背景颜色
setBackGroundNonSelectionColor(Color newColor)

-设置结点在选中状态下的背景颜色
setBackgroundSelectionColor(Color newColor)

-设置选中状态下结点的边框颜色
setBorderSelectionColor(Color newColor)

-设置处于折叠状态下非叶子节点的图标
setClosedIcon(Icon newIcon)

-设置结点文本的字体
setFont(Font font)

-设置叶子结点的图标
setLeafIcon(Icon newIcon)

-设置处于展开状态下的非叶子节点的图标
setOpenIcon(Icon newIcon)

-设置绘制非选中状态下结点文本的颜色
setTextNonSelectionColor(Color newColor)

-设置绘制选中状态下结点文本的颜色
setTextSelectionColor(Color newColor)

6.5、扩展DefaultTreeCellRenderer改变结点外观

DefaultTreeCellRenderer实现类实现类TreeCellRenderer接口,该接口里只有一个用于绘制结点内容的方法:getTreeCellRednererComponent(),该方法负责绘制JTree结点。学习JList的时候,如果要绘制JList的列表项外观的内容,需要实现LIstCellRenderer接口,通过重写getTreeCellRendererComponent()方法返回一个Component对象,该对象就是JTree的节点组件。两者之间非常类似。

DefaultTreeCellRenderer类继承了JLabel,实现getTreeCellRendererComponent()方法时返回this,即返回一个特殊的JLabel对象。如果需要根据结点内容来改变结点的外观,则可以再次扩展DefaultTreeCellRenderer类,并再次重写它提供的getTreeCellRendererCompoennt()方法。

7、JTable、TableModel实现表格

表格也是GUI程序中常用的组件,表格是一个由多行、多列组成的二维显示区。Swing的JTable以及相关类提供了这种表格支持,通过使用JTable以及相关类,程序既可以使用简单的代码创建出表格来显示二维数据,也可以开发出功能丰富的表格,还可以为表格定制各种显示外观、编辑特性。

7.1、创建简单表格

使用JTable创建简单表格步骤:

  1. 创建一个一维数组,存储表格中每一列的标题;
  2. 创建一个二维数组,存储表格中的每一行数据,其中二维数组内部的每一个一维数组,代表表格中的一行数据;
  3. 根据第一步和第二部创建的一维数组和二维数组,创建JTable对象;
  4. 把JTable添加到其它容器中显示。

JTable调整列宽:JTable使用TableColumn来标识表格中的每一列,JTable中表格列的所有属性,如最佳宽度、是否可调整宽度、最小和最大宽度等都保存在该TableColumn中。

  • setMaxWidth(int maxWidth):设置该列的最大宽度。如果指定的maxWidth小于该列的最小宽度,则maxWidth被设置成最小宽度。
  • setMinWidth(int minWidth):设置该列的最小宽度。
  • setPreferredWidth(int preferredWidth):设置该列的最佳宽度。

JTable调整表格选择模式:与JList类似的是,JTable使用了一个ListSelectionModel标识该表格的选择状态,程序可以通过ListSelectionModel.setSelectionMode(int mode)控制JTable的选择模式。JTable的选择模式有如下三种:

  • ListSelectionMode.MULTIPLE_INTERVAL_SELECTION:没有任何限制。
  • ListSelectionMode.SINGLE_INTERVAL_SELECTION:选择单个连续区域。
  • ListSelectionMode.SINGLE_SELECTION:只能选择单行。

7.2、TableModel

与JList、JTree类似的是,JTable采用了TableModel来保存表格中的所有状态数据:与ListModel类似的是,TableModel也不强制保存该表格显示的数据。虽然在前面程序中看到的是直接利用一个二维数组来创建JTable对象,但也可以通过TableModel对象来创建表格。

自定义TableModel步骤:

1、自定义类,继承AbstractTableModel抽象类,重写下边几个方法:

-返回表格列的数量
int getColumnCount()

-返回表格行的数量
int getRowCount()

-返回rowIndex行,column列的单元格的值
Object getValueAt(int rowIndex, int columnIndex)

-返回columnIndex列的列名称
String getColumnName(int columnIndex)

-设置rowIndex行,columnIndex列单元格是否可编辑
boolean isCellEditable(int rowIndex, int columnIndex)

2、创建自定义类对象,根据该对象,创建JTable对象。

不仅用户可以扩展AbstractModel抽象类,Swing本身也为AbstractTableModel提供了一个DefaultTableModel实现类,程序可以通过使用DefaultTableModel实现类来创建JTable对象。通过DefaultTableModel对象创建JTable对象后,就可以调用它提供的方法来添加数据行、插入数据行、删除数据行和移动数据行。DefaultTableModel提供了如下几个方法来控制数据行操作:

-添加一列
addColumn(Object column) / addColumn(Object columnName, Object[] columnData)

-添加一行
addRow(Object[] rowData)

-指定位置处插入一行
insertRow(int row, Object[] rowData)

-删除一行
removeRow(int row)

-移动指定范围内的数据行
moveRow(int start, int end, int to)

你可能感兴趣的:(Java,GUI,java,开发语言,后端)