回归java14-java进阶-Swing图形用户界面编程

Swing图形用户界面编程

图形用户界面(Graphical User Interface,简称 GUI)编程对于某种语言来说非常重要。Java的应用主要方向是基于Web浏览器的应用,用户界面主要是HTML、CSS和JavaScript等基于Web的技术,这些介绍要到Java EE阶段才能学习到。
而本章介绍的Java图形用户界面技术是基于Java SE的Swing,事实上它们在实际应用中使用不多,因此本章的内容只做了解。
(那问什么还要学它,因为入门小项目要用啊…)

Java图形用户界面技术

Java图形用户界面技术主要有:AWT、Applet、Swing和JavaFX。

  1. AWT

    AWT(Abstract Window Toolkit)是抽象窗口工具包,AWT是Java 程序提供的建立图形用户界面最基础的工具集。AWT支持图形用户界面编程的功能包括:用户界面组件(控件)、事件处理模型、图形图像处理(形状和颜色)、字体、布局管理器和本地平台的剪贴板来进行剪切和粘贴等。AWT是Applet和Swing技术的基础。

    AWT在实际的运行过程中是调用所在平台的图形系统,因此同样一段AWT程序在不同的操作系统平台下运行所看到的样式是不同的。(在Windows下运行,显示的窗口是Windows风格的窗口;在UNIX下运行时,显示的是UNIX风格的窗口。)

  2. Applet

    Applet称为Java小应用程序,Applet基础是AWT,但它主要嵌入到HTML代码中,由浏览器加载和运行,由于存在安全隐患和运行速度慢等问题,已经很少使用了。

  3. Swing

    Swing是Java主要的图形用户界面技术,Swing提供跨平台的界面风格,用户可以自定义Swing的界面风格。Swing提供了比AWT更完整的组件,引入了许多新的特性。Swing API是围绕着实现AWT各个部分的API构筑的。Swing是由100%纯Java实现的,Swing组件没有本地代码,不依赖操作系统的支持,这是它与AWT组件的最大区别。(重点介绍)

  4. JavaFX

    JavaFX是开发丰富互联网应用程序(Rich Internet Application,缩写RIA)的图形用户界面技术,JavaFX期望能够在桌面应用的开发领域与Adobe公司的AIR、微软公司的Silverlight相竞争。传统的互联网应用程序基于Web,客户端是浏览器。而丰富互联网应用程序试图打造自己的客户端,替代浏览器。

Swing技术基础

AWT是Swing的基础,Swing事件处理和布局管理都是依赖于AWT,AWT内容来自java.awt包,Swing内容来自javax.swing包。AWT和Swing作为图形用户界面技术包括了4个主要的概念:组件 (Component)、容器(Container)、事件处理和布局管理器(LayoutManager)。

Swing类层次结构

容器和组件构成了Swing的主要内容。

Swing容器类主要有:JWindow、JFrame和JDialog,其他的不带“J”开头都是AWT提供的类,在Swing中大部分类都是以“J”开头。
Swing容器类层次结构:
在这里插入图片描述

Swing组件类层次结构,Swing所有组件继承自JComponent,JComponent又间接继承自AWT的java.awt.Component类。
Swing组件类层次结构:
在这里插入图片描述

Swing程序结构

图形用户界面主要是由窗口以及窗口中的组件构成的,编写Swing程序主要就是创建窗口和添加组件过程。Swing中的窗口主要是使用JFrame,很少使用JWindow。JFrame有标题、边框、菜单、大小和窗口管理按钮等窗口要素,而JWindow没有标题栏和窗口管理按钮。
构建Swing程序主要有两种方式:创建JFrame或继承JFrame。

示例:窗口标题是MyFrame,窗口中有显示字符串“Hello Swing!”。

创建JFrame方式

直接实例化JFrame对象,然后设置JFrame属性,添加窗口所需要的组件。

import java.awt.Container;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class SwingDemo1 {
    
    public static void main(String[] args) {
        //创建窗口对象
        JFrame frame = new JFrame("MyFrame");
        // 创建标签
        JLabel label = new JLabel("Hello Swing!");
        // 获得窗口的内容面板
        Container contentPane = frame.getContentPane();
        // 添加标签到内容面板
        contentPane.add(label);
        // 设置窗口大小
        frame.setSize(300, 300);
        // 设置窗口可见
        frame.setVisible(true);
    }
}

第9行使用JFrame的JFrame(String title)构造方法创建JFrame对象,title是设置创建的标题。默认情况下JFrame是没有大小且不可见的,因此创建JFrame对象后还需要设置大小和可见
设置JFrame窗口大小和可见这两条语句,应该在添加完成所有组件之后调用。否则在多个组件情况下,会导致有些组件没有显示。

创建好窗口后,就需要将其中的组件添加进来,第11行是创建标签对象,构造方法中字符串参数是标签要显示的文本。创建好组件之后需要把它添加到窗口的内容面板上,第15行调用容器的add()方法将组件添加到窗口上。
在Swing中添加到JFrame上的所有可见组件,除菜单栏外,全部添加到内容面板上,不要直接添加到JFrame上,这是Swing绘制系统所要求的。内容面板是JFrame中包含的一个子容器。

几乎所有的图形用户界面技术,在构建界面时都采用层级结构(树形结构),根是顶级容器(只能包含其他容器的容器),子容器有内容面板和菜单栏,然后其他的组件添加到内容面板容器中。所有的组件都有add()方法,通过调用add()方法将其他组件添加到容器中,作为当前容器的子组件。
在这里插入图片描述

继承JFrame方式

编写一个继承JFrame的子类,在构造方法中初始化窗口,添加窗口所需要的组件。

import java.awt.Container;
import javax.swing.JFrame;
import javax.swing.JLabel;

class MyFrame extends JFrame {
    
    public MyFrame(String title) {
        super(title);
        // 创建标签
        JLabel label = new JLabel("Hello Swing!");
        // 获得窗口的内容面板
        Container contentPane = getContentPane();
        // 添加标签到内容面板
        contentPane.add(label);
        // 设置窗口大小
        setSize(300, 300);
        // 设置窗口可见
        setVisible(true);
    }
}


public class SwingDemo2{
    
    public static void main(String[] args) {
        //创建窗口对象
        new MyFrame("MyFrame");
    }
}

创建JFrame方式适合于小项目,代码量少、窗口不多、组件少的情况。继承JFrame 方式,适合于大项目,可以针对不同界面自定义一个Frame类,属性可以在构造方法中进行设置;缺点是有很多类文件需要有效地管理。

事件处理模型

图形界面的组件要响应用户操作,就必须添加事件处理机制。Swing采用AWT的事件处理模型进行事件处理。在事件处理的过程中涉及三个要素:

  1. 事件:是用户对界面的操作,在Java中事件被封装称为事件类java.awt.AWTEvent及其子类,例如按钮单击事件类是java.awt.event.ActionEvent。
  2. 事件源:是事件发生的场所,就是各个组件,例如按钮单击事件的事件源是按钮(Button)。
  3. 事件处理者:是事件处理程序,在Java中事件处理者是实现特定接口的事件对象。

在事件处理模型中最重要的是事件处理者,它根据事件(XXXEvent)的不同会实现不同的接口,这些接口命名为XXXListener,所以事件处理者也称为事件监听器。最后事件源通过addXXXListener()方法添加事件监听,监听XXXEvent事件。

各种事件和对应的监听器接口:

事件类型相应监听器接口监听器接口中的方法
ActionActionListeneractionPerformed(ActionEvent)
ItemItemListeneritemStateChanged(ItemEvent)
MouseMouseListenermousePressed(MouseEvent)
mouseReleased(MouseEvent)
mouseEntered(MouseEvent)
mouseExited(MouseEvent)
mouseClicked(MouseEvent)
MouseMotionMouseMotionListenermouseDragged(MouseEvent)
mouseMoved(MouseEvent)
KeyKeyListenerKeyPressed(KeyEvent)
KeyReleased(KeyEvent)
KeyTyped(KeyEvent)
FocusFocusListenerfocusGained(FocusEvent)
focusLost(FocusEvent)
AdjustmentAdjustmentListeneradjustmentValueChanged(AdjustmentEvent)
ComponentComponentListenercomponentMoved(ComponentEvent)
componentHidden(ComponentEvent)
componentResized(ComponentEvent)
componentShown(ComponentEvent)
WindowWindowListenerwindowClosing(WindowEvent)
windowOpened(WindowEvent)
windowIconified(WindowEvent)
windowJDeiconified(WindowEvent)
windowClosed(WindowEvent)
windowActivated(WindowEvent)
windowDeactivated(WindowEvent)
ContainerContainerListenercomponentAdded(ContainerEvent)
componentAdded(ContainerEvent)
TextTextListenertextValueChanged(TextEvent)

事件处理者可以实现XXXListener接口任何形式,即:外部类、内部类、匿名内部类和Lambda表达式;如果XXXListener接口只有一个抽象方法,事件处理者还可以是Lambda表达式。为了访问窗口中的组件方便,往往使用内部类、匿名内部类和Lambda表达式。

采用内部类处理事件

示例:界面中有两个按钮和一个标签,当单击Button1或Button2时会改变标签显示的内容。

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;

class MyFrame extends JFrame {
    
    // 声明标签
    JLabel label;
    
    public MyFrame(String title) {
        super(title);
        // 创建标签
        label = new JLabel("Label");
        // 添加标签到内容面板
        getContentPane().add(label, BorderLayout.NORTH);
        // 创建Button1
        JButton button1 = new JButton("Button1");
        // 添加Button1到内容面板
        getContentPane().add(button1, BorderLayout.CENTER);
        // 创建Button2
        JButton button2 = new JButton("Button2");
        // 添加Button2到内容面板
        getContentPane().add(button2, BorderLayout.SOUTH);
        // 设置窗口大小
        setSize(350, 120);
        // 设置窗口可见
        setVisible(true);
        
        // 注册事件监听器,监听Button2单击事件
        button2.addActionListener(new ActionEventHandler());
        // 注册事件监听器,监听Button1单击事件
        button1.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                label.setText("Hello Swing!");
            }
        });
    }
    
    // Button2事件处理者
    class ActionEventHandler implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            label.setText("Hello World!");
        }
    }
}

第18行通过add(button1, BorderLayout.NORTH)方法将标签添加到内容面板,这个add()方法与前面介绍的有所不同,它的第二个参数是指定组件的位置。

在事件处理模型中,内部类实现的模型,内部类会定义为成员内部类,因此不能访问其他方法中的局部变量组件,只能访问成员变量组件,所以代码第11行将标签组件声明为成员变量, 否则ActionEventHandler内部类无法访问该组件。而匿名内部类既可以访问所在方法的局部变量组件,也可以访问成员变量组件。

采用Lambda表达式处理事件

如果一个事件监听器接口只有一个抽象方法,则可以使用Lambda表达式实现事件处理,这些接口主要有:ActionListener、AdjustmentListener、ItemListener、MouseWheelListener、TextListener和 WindowStateListener等。

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class MyFrame extends JFrame implements ActionListener {
    
    // 声明标签
    JLabel label;
    
    public MyFrame(String title) {
        super(title);
        // 创建标签
        label = new JLabel("Label");
        // 添加标签到内容面板
        getContentPane().add(label, BorderLayout.NORTH);
        // 创建Button1
        JButton button1 = new JButton("Button1");
        // 添加Button1到内容面板
        getContentPane().add(button1, BorderLayout.CENTER);
        // 创建Button2
        JButton button2 = new JButton("Button2");
        // 添加Button2到内容面板
        getContentPane().add(button2, BorderLayout.SOUTH);
        // 设置窗口大小
        setSize(350, 120);
        // 设置窗口可见
        setVisible(true);
        // 注册事件监听器,监听Button2单击事件
        button2.addActionListener(this);
        // 注册事件监听器,监听Button1单击事件
        button1.addActionListener((event) -> {
            label.setText("Hello World!");
        });
    }
    
    @Override
    public void actionPerformed(ActionEvent event) {
        label.setText("Hello Swing!");
    }
}

代码第34行采用Lambda表达式实现的事件监听器,可见代码非常简单。另外,当前窗口本身也可以是事件处理者,代码第8行声明窗口实现ActionListener接口,代码第40行是实现抽象方法,那么注册事件监听器参数就是this了,见代码第32行。

使用适配器

事件监听器都是接口,在Java接口中定义的抽象方法必须全部实现(哪怕对某些方法并不关心,也要给一对空的大括号表示实现)。例如WindowListener是窗口事件(WindowEvent)监听器接口,为了在窗口中接收到窗口事件,需要在窗口中注册WindowListener事件监听器。

this.addWindowListener(new WindowListener() {
    
    @Override
    public void windowActivated(WindowEvent e) {
    }
    
    @Override
    public void windowClosed(WindowEvent e) {
    }
    
    @Override
    public void windowClosing(WindowEvent e) {
        // 退出系统
        System.exit(0);
    }
    
    @Override
    public void windowDeactivated(WindowEvent e) {
    }
    
    @Override
    public void windowDeiconified(WindowEvent e) {
    }
    
    @Override
    public void windowIconified(WindowEvent e) {
    }
    
    @Override
    public void windowOpened(WindowEvent e) {
    }
});

Java提供了一些与监听器相配套的适配器。监听器是接口,命名采用XXXListener,而适配器是类,命名采用XXX Adapter。在使用时通过继承事件所对应的适配器类,覆盖所需要的方法,无关方法不用实现。

this.addWindowListener(new WindowAdapter(){
    @Override
    public void windowClosing(WindowEvent e) {
        // 退出系统
        System.exit(0);
    }
});

由于Java的单一继承机制,当需要多种监听器或此类已有父类时,就无法采用事件适配器了。

并非所有的监听器接口都有对应的适配器类,一般定义了多个方法的监听器接口,例如 WindowListener有多个方法对应多种不同的窗口事件时,才需要配套的适配器,主要的适配器如下:
ComponentAdapter 组件适配器
ContainerAdapter 容器适配器
FocusAdapter 焦点适配器
KeyAdapter 键盘适配器
MouseAdapter 鼠标适配器
MouseMotionAdapter 鼠标运动适配器
WindowAdapter 窗口适配器

布局管理

Java为了实现图形用户界面的跨平台,并实现动态布局等效果,Java将容器内的所有组件布局交给布局管理器管理。布局管理器负责组件的排列顺序、大小、位置,当窗口移动或调整大小后组件如何变化等。

Java SE提供了7种布局管理器:FlowLayout、BorderLayout、GridLayout、BoxLayout、 CardLayout、SpringLayout和GridBagLayout,其中最基础的是FlowLayout、BorderLayout和GridLayout 布局管理器。

FlowLayout布局

FlowLayout布局摆放组件的规律是:从上到下、从左到右进行摆放,如果容器足够宽,第一个组件先添加到容器中第一行的最左边,后续的组件依次添加到上一个组件的右边,如果当前行已摆放不下该组件,则摆放到下一行的最左边。

FlowLayout构造方法:
FlowLayout(int align, int hgap, int vgap) 创建一个FlowLayout对象,它具有指定的对齐方式以及指定的水平和垂直间隙,hgap参数是组件之间的水平间隙,vgap参数是组件之间的垂直间隙, 单位是像素。
FlowLayout(int align) 创建一个FlowLayout对象,指定的对齐方式,默认的水平和垂直间隙是5个单位。
FlowLayout() 创建一个FlowLayout对象,它是居中对齐的,默认的水平和垂直间隙是5个单位。

上述参数align是对齐方式,它是通过FlowLayout的常量指定的,这些常量说明如下:

FlowLayout.CENTER 指示每一行组件都应该是居中的。
FlowLayout.LEADING 指示每一行组件都应该与容器方向的开始边对齐,例如,对于从左到右的方向,则与左边对齐。
FlowLayout.LEFT 指示每一行组件都应该是左对齐的。
FlowLayout.RIGHT 指示每一行组件都应该是右对齐的。
FlowLayout.TRAILING 指示每行组件都应该与容器方向的结束边对齐,例如,对于从左到右的方向,则与右边对齐。

import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class MyFrame extends JFrame {
    
    // 声明标签
    JLabel label;
    
    public MyFrame(String title) {
        super(title);
        setLayout(new FlowLayout(FlowLayout.LEFT, 20, 20));
        // 创建标签
        label = new JLabel("Label");
        // 添加标签到内容面板
        getContentPane().add(label);
        // 创建Button1
        JButton button1 = new JButton("Button1");
        // 添加Button1到内容面板
        getContentPane().add(button1);
        // 创建Button2
        JButton button2 = new JButton("Button2");
        // 添加Button2到内容面板
        getContentPane().add(button2);
        // 设置窗口大小
        setSize(350, 120);
        // 设置窗口可见
        setVisible(true);
        // 注册事件监听器,监听Button2单击事件
        button2.addActionListener((event) -> {
            label.setText("Hello Swing!");
        });
        // 注册事件监听器,监听Button1单击事件
        button1.addActionListener((event) -> {
            label.setText("Hello World!");
        });
    }
}

第13行是设置当前窗口的布局是FlowLayout布局,采用FlowLayout(int align, int hgap, int vgap) 构造方法。一旦设置了FlowLayout布局,就可以通过add(Component comp)方法添加组件到窗口的内容面板。
采用FlowLayout布局如果水平空间比较小,组件会垂直摆放(拖曳窗口的边缘使窗口变窄,最后一个组件换行)。

BorderLayout布局

BorderLayout布局是窗口的默认布局管理器。
BorderLayout是JWindow、JFrame和JDialog的默认布局管理器。BorderLayout布局管理器把容器分成5个区域:North、South、East、West和Center,每个区域只能放置一个组件。(North与South占整行,其余横向中间部分分为West、Center、East)

BorderLayout构造方法:
BorderLayout(int hgap, int vgap) 创建一个BorderLayout对象,指定水平和垂直间隙,hgap参数是组件之间的水平间隙,vgap参数是组件之间的垂直间隙,单位是像素。
BorderLayout() 创建一个BorderLayout对象,组件之间没有间隙。

BorderLayout布局有5个区域,为此BorderLayout中定义了5个约束常量,说明如下:

BorderLayout.CENTER 中间区域的布局约束(容器中央)
BorderLayout.EAST 东区域的布局约束(容器右边)
BorderLayout.NORTH 北区域的布局约束(容器顶部)
BorderLayout.SOUTH 南区域的布局约束(容器底部)
BorderLayout.WEST 西区域的布局约束(容器左边)

import java.awt.BorderLayout;
import java.awt.Button;
import javax.swing.JFrame;

public class MyFrame extends JFrame {
    
    public MyFrame(String title) {
        super(title);
        // 设置BorderLayout布局
        setLayout(new BorderLayout(10, 10));
        // 添加按钮到容器的North区域
        getContentPane().add(new Button("北"), BorderLayout.NORTH);
        // 添加按钮到容器的South区域
        getContentPane().add(new Button("南"), BorderLayout.SOUTH);
        // 添加按钮到容器的East区域
        getContentPane().add(new Button("东"), BorderLayout.EAST);
        // 添加按钮到容器的West区域
        getContentPane().add(new Button("西"), BorderLayout.WEST);
        // 添加按钮到容器的Center区域
        getContentPane().add(new Button("中"), BorderLayout.CENTER);
        setSize(300, 300);
        setVisible(true);
    }
}

添加方法是add(Component comp, Object constraints),第二个参数constraints指定约束。

当使用BorderLayout时,如果容器的大小发生变化,其变化规律为:组件的相对位置不变,大小发生变化。如果容器变高或矮,则North和South不变,West、Center和East变高或矮;如果容器变宽或窄,West和East区域不变,North、Center和South变宽或窄。

在5个区域中不一定都放置了组件,如果某个区域缺少组件,界面布局会有比较大的影响。

GridLayout布局

GridLayout布局以网格形式对组件进行摆放,容器被分成大小相等的矩形,一个矩形中放置一个组件。

GridLayout构造方法:
GridLayout() 创建具有默认值的GridLayout对象,即每个组件占据一行一列。
GridLayout(int rows, int cols) 创建具有指定行数和列数的GridLayout对象。
GridLayout(int rows, int cols, int hgap, int vgap) 创建具有指定行数和列数的GridLayout对象,并指定水平和垂直间隙。

import java.awt.Button;
import java.awt.GridLayout;
import javax.swing.JFrame;

public class MyFrame extends JFrame {
    
    public MyFrame(String title) {
        super(title);
        // 设置3行3列的GridLayout布局管理器
        setLayout(new GridLayout(3, 3));
        // 添加按钮到第一行的第一格
        getContentPane().add(new Button("1"));
        // 添加按钮到第一行的第二格
        getContentPane().add(new Button("2"));
        // 添加按钮到第一行的第三格
        getContentPane().add(new Button("3"));
        // 添加按钮到第二行的第一格
        getContentPane().add(new Button("4"));
        // 添加按钮到第二行的第二格
        getContentPane().add(new Button("5"));
        // 添加按钮到第二行的第三格
        getContentPane().add(new Button("6"));
        // 添加按钮到第三行的第一格
        getContentPane().add(new Button("7"));
        // 添加按钮到第三行的第二格
        getContentPane().add(new Button("8"));
        // 添加按钮到第三行的第三格
        getContentPane().add(new Button("9"));
        setSize(400, 400);
        setVisible(true);
    }
}

第10行是设置当前窗口布局采用3行3列的GridLayout布局,它有9个区域,分别从左到右,从上到下摆放。

GridLayout布局将容器分成几个区域,也会出现某个区域缺少组件情况,GridLayout布局会根据行列划分的不同,会平均占据容器的空间,实际情况比较复杂。

不使用布局管理器

如果要开发的图形用户界面应用不考虑跨平台,不考虑动态布局,窗口大小不变的,那么布局管理器就失去使用的意义。容器也可以不设置布局管理器,那么此时的布局是由开发人员自己管理的。
组件有三个与布局有关的方法setLocation()、setSize()和setBounds(),在设置了布局管理的容器中组件的这几个方法不起作用,不设置布局管理时它们才起作用。

void setLocation(int x, int y) 方法是设置组件的位置。
void setSize(int width, int height) 方法是设置组件的大小。
void setBounds(int x, int y, int width, int height) 方法是设置组件的大小和位置。

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingConstants;

public class MyFrame extends JFrame {
    
    public MyFrame(String title) {
        super(title);
        //设置窗口大小不变的
        setResizable(false);
        // 不设置布局管理器
        getContentPane().setLayout(null);
        // 创建标签
        JLabel label = new JLabel("Label");
        // 设置标签的位置和大小
        label.setBounds(89, 13, 100, 30);
        // 设置标签文本水平居中
        label.setHorizontalAlignment(SwingConstants.CENTER);
        // 添加标签到内容面板
        getContentPane().add(label);
        
        // 创建Button1
        JButton button1 = new JButton("Button1");
        // 设置Button1的位置和大小
        button1.setBounds(89, 59, 100, 30);
        // 添加Button1到内容面板
        getContentPane().add(button1);
        
        // 创建Button2
        JButton button2 = new JButton("Button2");
        // 设置Button2的位置
        button2.setLocation(89, 102);
        // 设置Button2的大小
        button2.setSize(100, 30);
        // 添加Button2到内容面板
        getContentPane().add(button2);
        // 设置窗口大小
        setSize(300, 200);
        // 设置窗口可见
        setVisible(true);
        // 注册事件监听器,监听Button2单击事件
        button2.addActionListener((event) -> {
            label.setText("Hello Swing!");
        });
        // 注册事件监听器,监听Button1单击事件
        button1.addActionListener((event) -> {
            label.setText("Hello World!");
        });
    }
}

第11行是设置不能调整窗口大小,没有设置布局管理器后,容器中的组件都绝对布局,容器大小如果变化,那么其中的组件大小和位置都不会变化,将窗口拉大后,组件还是在原来的位置。

(我要放弃Eclipse了…IDEA我来了)

Swing组件

Swing所有组件都继承自JComponent,主要有文本处理、按钮、标签、列表、面板、组合框、滚动条、滚动面板、菜单、表格和树等组件。

标签和按钮

Swing中标签类是JLabel,它不仅可以显示文本还可以显示图标。

JLabel构造方法:
JLabel() 创建一个无图标无标题标签对象。
JLabel(Icon image) 创建一个具有图标的标签对象。
JLabel(Icon image, int horizontalAlignment) 通过指定图标和水平对齐方式创建标签对象。
JLabel(String text) 创建一个标签对象,并指定显示的文本。
JLabel(String text, Icon icon, int horizontalAlignment) 通过指定显示的文本、图标和水平对齐方式创建标签对象。
JLabel(String text, int horizontalAlignment) 通过指定显示的文本和水平对齐方式创建标签对象。

上述构造方法horizontalAlignment参数是水平对齐方式,它的取值是SwingConstants中定义的以下常量之一:LEFT、CENTER、RIGHT、LEADING 或 TRAILING。

Swing中的按钮类是JButton,JButton不仅可以显示文本还可以显示图标。

JButton构造方法:
JButton() 创建不带文本或图标的按钮对象。
JButton(Icon icon) 创建一个带图标的按钮对象。
JButton(String text):创建一个带文本的按钮对象。
JButton(String text, Icon icon) 创建一个带初始文本和图标的按钮对象。

示例:界面中上面图标是标签, 下面两个图标是按钮,当单击按钮时标签可以切换图标。

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingConstants;

public class MyFrame extends JFrame {
    
    // 用于标签切换的图标
    private static Icon images[] = {
        new ImageIcon("./icon/0.png"),
        new ImageIcon("./icon/1.png"),
        new ImageIcon("./icon/2.png"),
        new ImageIcon("./icon/3.png"),
        new ImageIcon("./icon/4.png"),
        new ImageIcon("./icon/5.png") };
    
    // 当前页索引
    private static int currentPage = 0;
    
    public MyFrame(String title) {
        super(title);
        
        // 设置窗口大小不变
        setResizable(false);
        
        // 不设置布局管理器
        getContentPane().setLayout(null);
        
        // 创建标签
        JLabel label = new JLabel(images[0]);
        // 设置标签的位置和大小
        label.setBounds(94, 27, 100, 50);
        // 设置标签文本水平居中
        label.setHorizontalAlignment(SwingConstants.CENTER);
        // 添加标签到内容面板
        getContentPane().add(label);
        
        // 创建向后翻页按钮
        JButton backButton = new JButton(new ImageIcon("./icon/ic_menu_back.png"));
        // 设置按钮的位置和大小
        backButton.setBounds(77, 90, 47, 30);
        // 添加按钮到内容面板
        getContentPane().add(backButton);
        
        // 创建向前翻页按钮
        JButton forwardButton = 
            new JButton(new ImageIcon("./icon/ic_menu_forward.png"));
        // 设置按钮的位置和大小
        forwardButton.setBounds(179, 90, 47, 30);
        // 添加按钮到内容面板
        getContentPane().add(forwardButton);
        
        // 设置窗口大小
        setSize(300, 200);
        // 设置窗口可见
        setVisible(true);
        // 注册事件监听器,监听向后翻页按钮单击事件
        backButton.addActionListener((event) -> {
            if (currentPage < images.length - 1) {
                currentPage++;
            }
            label.setIcon(images[currentPage]);
        });
        
        // 注册事件监听器,监听向前翻页按钮单击事件
        forwardButton.addActionListener((event) -> {
            if (currentPage > 0) {
                currentPage--;
            }
            label.setIcon(images[currentPage]);
        });
    }
}

第11行定义ImageIcon数组,用于标签切换图标,注意Icon是接口,ImageIcon是实现Icon接口。代码第20行currentPage变量记录了当前页索引,前后翻页按钮会改变前页索引。

文本输入组件

文本输入组件主要有:文本框(JTextField)、密码框(JPasswordField)和文本区(JTextArea)。文本框和密码框都只能输入和显示单行文本。当按下Enter键时,可以触发ActionEvent事件。而文本区可以输入和显示多行多列文本。

文本框(JTextField)构造方法:
JTextField() 创建一个空的文本框对象。
JTextField(int columns) 指定列数,创建一个空的文本框对象,列数是文本框显示的宽度,列数主要用于FlowLayout布局。
JTextField(String text) 创建文本框对象,并指定初始化文本。
JTextField(String text, int columns) 创建文本框对象,并指定初始化文本和列数。

JPasswordField继承自JTextField,构造方法类似。

文本区(JTextArea)构造方法:
JTextArea() 创建一个空的文本区对象。
JTextArea(int rows, int columns) 创建文本区对象,并指定行数和列数。
JTextArea(String text) 创建文本区对象,并指定初始化文本。
JTextArea(String text, int rows, int columns) 创建文本区对象,并指定初始化文本、行数和列数。

示例:界面中有三个标签(TextField、Password、TextArea)。
可以采用布局嵌套,将TextField标签、Password标签、文本框和密码框都放到一个面板(panel1) 中;将TextArea和文本区放到一个面板(panel2)中。两个面板panel1和panel2放到内容视图中,内容视图采用BorderLayout布局,每个面板内部采用FlowLayout布局。

import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextArea;
import javax.swing.JTextField;

public class MyFrame extends JFrame {
    
    private JTextField textField;
    private JPasswordField passwordField;
    
    public MyFrame(String title) {
        super(title);
        // 设置布局管理BorderLayout
        getContentPane().setLayout(new BorderLayout());
        
        // 创建一个面板panel1放置TextField和Password
        JPanel panel1 = new JPanel();
        // 将面板panel1添加到内容视图
        getContentPane().add(panel1, BorderLayout.NORTH);
        
        // 创建标签
        JLabel lblTextFieldLabel = new JLabel("TextField:");
        // 添加标签到面板panel1
        panel1.add(lblTextFieldLabel);
        
        // 创建文本框
        textField = new JTextField(12); // 指定列数12
        // 添加文本框到面板panel1
        panel1.add(textField);
        
        // 创建标签
        JLabel lblPasswordLabel = new JLabel("Password:");
        // 添加标签到面板panel1
        panel1.add(lblPasswordLabel);
        
        // 创建密码框
        passwordField = new JPasswordField(12); // 指定列数12
        // 添加密码框到面板panel1
        panel1.add(passwordField);
        
        // 创建一个面板panel2放置TextArea
        JPanel panel2 = new JPanel();
        getContentPane().add(panel2, BorderLayout.SOUTH);
        
        // 创建标签
        JLabel lblTextAreaLabel = new JLabel("TextArea:");
        // 添加标签面板panel2
        panel2.add(lblTextAreaLabel);
        
        // 创建文本区
        JTextArea textArea = new JTextArea(3, 20); // 指定行数3,列数20
        // 添加文本区到面板panel2
        panel2.add(textArea);
        
        // 设置窗口大小
        pack(); // 紧凑排列(将容器中所有组件刚好包裹),其作用相当于setSize()
        
        // 设置窗口可见
        setVisible(true);
        
        textField.addActionListener((event)->{
            textArea.setText("在文本框上按下Enter键");
        });
    }
}

第20、45行,面板(JPanel)是一种没有标题栏和边框的容器,经常用于嵌套布局。然后再将这两个面板,添加到内容视图中。

复选框和单选按钮

Swing中提供了用于多选和单选功能的组件。

多选组件是复选框(JCheckBox),有时也单独使用,能提供两种状态的开和关。
单选组件是单选按钮(JRadioButton),同一组的多个单选按钮应该具有互斥特性,又名收音机按钮(RadioButton),一个按钮按下时,其他按钮一定抬起。同一组多个单选按钮应该放到同一个ButtonGroup对象,ButtonGroup对象不属于容器,它会创建一个互斥作用范围。

JCheckBox构造方法:
JCheckBox() 创建一个没有文本、没有图标并且最初未被选定的复选框对象。
JCheckBox(Icon icon) 创建有一个图标、最初未被选定的复选框对象。
JCheckBox(Icon icon, boolean selected) 创建一个带图标的复选框对象,并指定其最初是否处于选定状态。
JCheckBox(String text) 创建一个带文本的、最初未被选定的复选框对象。
JCheckBox(String text, boolean selected) 创建一个带文本的复选框对象,并指定其最初是否处于选定状态。
JCheckBox(String text, Icon icon) 创建带有指定文本和图标的、最初未被选定的复选框对象。
JCheckBox(String text, Icon icon, boolean selected) 创建一个带文本和图标的复选框对象,并指定其最初是否处于选定状态。

JCheckBox和JRadioButton它们有着相同的父类JToggleButton,有着相同方法和类似的构造方法。

示例:界面中有一组复选框和一组单选按钮。

public class MyFrame extends JFrame implements ItemListener {
    
    // 声明并创建RadioButton对象
    private JRadioButton radioButton1 = new JRadioButton("男");
    private JRadioButton radioButton2 = new JRadioButton("女");
    
    public MyFrame(String title) {
        super(title);
        
        // 设置布局管理BorderLayout
        getContentPane().setLayout(new BorderLayout());
        
        // 创建一个面板panel1放置TextField和Password
        JPanel panel1 = new JPanel();
        FlowLayout flowLayout_1 = (FlowLayout) panel1.getLayout();
        flowLayout_1.setAlignment(FlowLayout.LEFT);
        // 将面板panel1添加到内容视图
        getContentPane().add(panel1, BorderLayout.NORTH);
        
        // 创建标签
        JLabel lblTextFieldLabel = new JLabel("选择你喜欢的编程语言:");
        // 添加标签到面板panel1
        panel1.add(lblTextFieldLabel);
        
        JCheckBox checkBox1 = new JCheckBox("Java");
        panel1.add(checkBox1);
        
        JCheckBox checkBox2 = new JCheckBox("C++");
        panel1.add(checkBox2);
        
        JCheckBox checkBox3 = new JCheckBox("Objective-C");
        // 注册checkBox3对ActionLEvent事件监听
        checkBox3.addActionListener((event) -> {
            // 打印checkBox3状态
            System.out.println(checkBox3.isSelected());
        });
        panel1.add(checkBox3);
        
        // 创建一个面板panel2放置TextArea
        JPanel panel2 = new JPanel();
        FlowLayout flowLayout = (FlowLayout) panel2.getLayout();
        flowLayout.setAlignment(FlowLayout.LEFT);
        getContentPane().add(panel2, BorderLayout.SOUTH);
        
        // 创建标签
        JLabel lblTextAreaLabel = new JLabel("选择性别:");
        // 添加标签到面板panel2
        panel2.add(lblTextAreaLabel);
        
        // 创建ButtonGroup对象
        ButtonGroup buttonGroup = new ButtonGroup();
        // 添加RadioButton到ButtonGroup对象
        buttonGroup.add(radioButton1);
        buttonGroup.add(radioButton2);
        
        // 添加RadioButton到面板panel2
        panel2.add(radioButton1);
        panel2.add(radioButton2);
        
        //注册ItemEvent事件监听器
        radioButton1.addItemListener(this);
        radioButton2.addItemListener(this);
        
        // 设置窗口大小
        pack(); // 紧凑排列,其作用相当于setSize()
        
        // 设置窗口可见
        setVisible(true);
    }
    
    // 实现ItemListener接口方法
    @Override
    public void itemStateChanged(ItemEvent) {
        if (e.getStateChange() == ItemEvent.SELECTED) {
            JRadioButton button = (JRadioButton) e.getItem();
            System.out.println(button.getText());
        }
    }
}

第4、5行创建了两个单选按钮对象,为了能让这两个单选按钮互斥,需要把它们添加到一个ButtonGroup对象(51行),并把它们添加进来。为了监听两单选按钮的选择状态,注册ItemEvent事件监听器(61、62行),为了一起处理两个单选按钮事件,它们需要使用同一个事件处理者,本例是this,这说明当前窗口是事件处理者,它实现了ItemListener接口,见代码第1行代码第73行实现了ItemListener接口的抽象方法。两个单选按钮使用同一个事件处理者,第74行是判断按钮是否被选中,如果选中通过e.getItem() 方法获得按钮引用,然后再通过getText()方法获得按钮的文本标签。

第25行是创建了一个复选框对象,并且应该把它添加到面板panel1中。复选框和单选按钮都属于按钮,也能响应ActionEvent事件,代码第33行是注册checkBox3对ActionLEvent事件监听。

下拉列表

Swing中提供了下拉列表(JComboBox)组件,每次只能选择其中的一项。

JComboBox构造方法:
JComboBox() 创建一个下拉列表对象。
JComboBox(Object [] items) 创建一个下拉列表对象,items设置下拉列表中选项。下拉列表中选项内容可以是任意类,而不再局限于String。

public class MyFrame extends JFrame {
    
    // 声明下拉列表JComboBox
    private JComboBox choice1;
    private JComboBox choice2;
    
    private String[] s1 = { "Java", "C++", "Objective-C" };
    private String[] s2 = { "男", "女" };
    
    public MyFrame(String title) {
        super(title);
        
        getContentPane().setLayout(new GridLayout(2, 2, 0, 0));
        
        // 创建标签
        JLabel lblTextFieldLabel = new JLabel("选择你喜欢的编程语言:");
        lblTextFieldLabel.setHorizontalAlignment(SwingConstants.RIGHT);
        getContentPane().add(lblTextFieldLabel);
        
        // 实例化JComboBox对象
        choice1 = new JComboBox(s1);
        // 注册Action事件监听器,采用Lambda表达式
        choice1.addActionListener(e -> {
            JComboBox cb = (JComboBox) e.getSource(); // 通过e事件参数获得事件源
            // 获得选择的项目
            String itemString = (String) cb.getSelectedItem();
            System.out.println(itemString);
        });
        
        getContentPane().add(choice1);
        
        // 创建标签
        JLabel lblTextAreaLabel = new JLabel("选择性别:");
        lblTextAreaLabel.setHorizontalAlignment(SwingConstants.RIGHT);
        getContentPane().add(lblTextAreaLabel);
        
        // 实例化JComboBox对象,采用Lambda表达式
        choice2 = new JComboBox(s2);
        // 注册项目选择事件侦听器
        choice2.addItemListener(e -> {
            // 项目选择
            if (e.getStateChange() == ItemEvent.SELECTED) {
                // 获得选择的项目
                String itemString = (String) e.getItem(); // 从e事件参数中取出项目对象
                System.out.println(itemString);
            }
        });
        getContentPane().add(choice2);
        
        // 设置窗口大小
        setSize(400, 150);
        
        // 设置窗口可见
        setVisible(true);
    }
}

下拉列表组件在进行事件处理时,可以注册两种事件监听器:ActionListener和ItemListener,这两个监听器都只有一 个抽象方法需要实现,因此可以采用Lambda表达式作为事件处理者。

列表

Swing中提供了列表(JList)组件,可以单选或多选。

JList构造方法:
JList() 创建一个列表对象。
JList(Object [] listData) 创建一个列表对象,listData设置列表中选项。列表中选项内容可以是任意类,而不再局限于String。

public class MyFrame extends JFrame {
    
    private String[] s1 = { "Java", "C++", "Objective-C" };
    
    public MyFrame(String title) {
        super(title);
        // 创建标签
        JLabel lblTextFieldLabel = new JLabel("选择你喜欢的编程语言:");
        getContentPane().add(lblTextFieldLabel, BorderLayout.NORTH);
        
        // 列表组件JList
        JList list1 = new JList(s1);
        list1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // 设置列表为单选
        // 注册项目选择事件监听器,采用Lambda表达式
        list1.addListSelectionListener(e -> {
            if (e.getValueIsAdjusting() == false) { // false鼠标释放,true鼠标按下
                // 获得选择的内容
                String itemString = (String) list1.getSelectedValue();
                System.out.println(itemString);
            }
        });
        getContentPane().add(list1);
        
        // 设置窗口大小
        setSize(300, 200);
        // 设置窗口可见
        setVisible(true);
    }
}

18行取出getSelectedValue()获得选中的项目值,如果是多选时可以通过getSelectedValues()获得选中的项目值。

分隔面板

Swing中提供了一种分隔面板(JSplitPane)组件,可以将屏幕分成左右或上下两部分。

JSplitPane构造方法:
JSplitPane(int newOrientation) 创建一个分隔面板,参数newOrientation指定布局方向,newOrientation取值是JSplitPane.HORIZONTAL_SPLIT(水平)或JSplitPane.VERTICAL_SPLIT(垂直)。
JSplitPane(int newOrientation, Component newLeftComponent, Component newRightComponent) 创建一个分隔面板,参数newOrientation指定布局方向,newLeftComponent指定左侧面板组件, newRightComponent指定右侧面板组件。

示例:界面分左右两部分,左边有列表组件, 选中列表项目时右边会显示相应的图片。

public class MyFrame extends JFrame {
    
    private String[] data = { "bird1.gif", "bird2.gif", "bird3.gif",                 "bird4.gif", "bird5.gif", "bird6.gif" };
    
    public MyFrame(String title) {
        super(title);
        // 右边面板
        JPanel rightPane = new JPanel();
        rightPane.setLayout(new BorderLayout(0, 0));
        JLabel lblImage = new JLabel();
        lblImage.setHorizontalAlignment(SwingConstants.CENTER);
        rightPane.add(lblImage, BorderLayout.CENTER);
        
        // 左边面板
        JPanel leftPane = new JPanel();
        leftPane.setLayout(new BorderLayout(0, 0));
        JLabel lblTextFieldLabel = new JLabel("选择鸟儿:");
        leftPane.add(lblTextFieldLabel, BorderLayout.NORTH);
        
        // 列表组件JList
        JList list1 = new JList(data);
        list1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        // 注册项目选择事件监听器,采用Lambda表达式
        list1.addListSelectionListener(e -> {
            if (e.getValueIsAdjusting() == false) {
                // 获得选择的内容
                String itemString = (String) list1.getSelectedValue();
                // 获得图片的相对路径
                String petImage = String.format("/images/%s", itemString);
                // 创建图片ImageIcon对象
                // MyFrame.class.getResource(petImage)获取资源图片的绝对路径
                Icon icon = new ImageIcon(MyFrame.class.getResource(petImage));
                lblImage.setIcon(icon);
            }
        });
        leftPane.add(list1, BorderLayout.CENTER);
        
        // 分隔面板
        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,                                                     leftPane, rightPane);
        splitPane.setDividerLocation(100); // 设置分隔条位置
        
        // 将分隔面板添加到内容面板中
        getContentPane().add(splitPane, BorderLayout.CENTER);
        
        // 设置窗口大小
        setSize(300, 200);
        // 设置窗口可见
        setVisible(true);
    }
}

(32行)资源文件是放在字节码文件夹中的文件,可通过XXX.class.getResource()方法获得它的运行时绝对路径。

表格

当有大量数据需要展示时,可以使用二维表格,有时也可以使用表格修改数据。表格是非常重要的组件。
Swing提供了表格组件JTable类,但是表格组件比较复杂,它的表现形式与数据是分离的,Swing的很多组件都是按照MVC设计模式进行设计的,JTable最有代表性。
按照MVC设计理念JTable属于视图,对应的模型是javax.swing.table.TableModel接口,根据自己的业务逻辑和数据实现TableModel接口实现类。TableModel接口要求实现所有抽象方法,使用起来比较麻烦,有时只是使用很简单的表格, 此时可以使用AbstractTableModel抽象类。实际开发时需要继承AbstractTableModel抽象类。

MVC是一种设计理念,将一个应用分为:模型(Model)、视图(View)和控制器(Controller),它将业务逻辑、数据、界面表示进行分离的方法组织代码,界面表示的变化不会影响到业务逻辑组件,不需要重新编写业务逻辑。

JTable构造方法:
JTable(TableModel dm) 通过模型创建表格,dm是模型对象,其中包含了表格要显示的数据。
JTable(Object[][] rowData, Object[] columnNames) 通过二维数组和指定列名,创建一个表格对象,rowData是表格中的数据,columnNames是列名。
JTable(int numRows, int numColumns) 指定行和列数创建一个空的表格对象。

先介绍通过二维数组和列名实现表格。这种方式创建表格不需要模型,实现起来比较简单。但是表格只能接受二维数组作为数据。

public class MyFrameTable extends JFrame {
    
    // 获得当前屏幕的宽高
    private double screenWidth = Toolkit.getDefaultToolkit().
        getScreenSize().getWidth();
    private double screenHeight = Toolkit.getDefaultToolkit().
        getScreenSize().getHeight();
    
    private JTable table;
    
    public MyFrameTable(String title) {
        super(title);
        table = new JTable(rowData, columnNames);
        // 设置表中内容字体
        table.setFont(new Font("微软雅黑", Font.PLAIN, 16));
        // 设置表列标题字体
        table.getTableHeader().setFont(new Font("微软雅黑", Font.BOLD, 16));
        // 设置表行高
        table.setRowHeight(40);
        // 设置为单行选中模式
        table.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
        // 返回当前行的状态模型
        ListSelectionModel rowSM = table.getSelectionModel();
        
        // 注册侦听器,选中行发生更改时触发
        rowSM.addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                // 只处理鼠标按下
                if (e.getValueIsAdjusting() == false) {
                    return;
                }
                ListSelectionModel lsm = (ListSelectionModel) e.getSource();
                if (lsm.isSelectionEmpty()) {
                    System.out.println("没有选中行");
                } else {
                    int selectedRow = lsm.getMinSelectionIndex();
                    System.out.println("第" + selectedRow + "行被选中");
                }
            }
        });
        
        JScrollPane scrollPane = new JScrollPane();
        scrollPane.setViewportView(table);
        getContentPane().add(scrollPane, BorderLayout.CENTER);
        
        // 设置窗口大小
        setSize(960, 640);
        // 计算窗口位于屏幕中心的坐标
        int x = (int) (screenWidth - 960) / 2; // (屏幕宽度-窗口宽度) / 2
        int y = (int) (screenHeight - 640) / 2;
        // 设置窗口位于屏幕中心
        setLocation(x, y);
        
        // 设置窗口可见
        setVisible(true);
    }
    
    // 表格列标题
    String[] columnNames = { "书籍编号", "书籍名称", "作者", "出版社", "出版日期", "库存数量" };
    // 表格数据
    Object[][] rowData = {{ "0036", "高等数学", "李放", "人民邮电出版社", "20000812", 1}, 
                       { "0004", "FLASH精选", "刘扬", "中国纺织出版社", "19990312", 2 },
                       { "0026", "软件工程", "牛田", "经济科学出版社", "20000328", 4 },
                       { "0015", "人工智能", "周未", "机械工业出版社", "19991223", 3 },
                       { "0037", "南方周末", "邓光明", "南方出版社", "20000923", 3 },
                          ...,
                       { "0032", "SOL使用手册", "贺民", "电子工业出版社", "19990425", 2 } 	};
}

第26~40行注册事件监听器,监听器当行选择变化时触发。由于ListSelectionListener接口虽然不是函数式接口,但只有一个方法,所以可以使用Lambda表达式实现该接口:

// 也可换成Lambda表达式
rowSM.addListSelectionListener(e -> {
    ListSelectionModel lsm = (ListSelectionModel) e.getSource();
    if (lsm.isSelectionEmpty()) {
        System.out.println("没有选中行");
    } else {
        int selectedRow = lsm.getMinSelectionIndex();
        System.out.println("第" + selectedRow + "行被选中");
    }
});

表格一般都会放到一个滚动面板(JScrollPane)中,这可以保证数据很多超出屏幕时,能够出现滚动条。把表格添加到滚动面板并不是使用add()方法,而是使用第43行的 scrollPane.setViewportView(table)语句。滚动面板是非常特殊的面板,它管理着一个视口或窗口,当里面的内容超出视口会出现滚动条,setViewportView()方法可以设置一个容器或组件作为滚动面板的视口。

案例:图书库存

在进行数据库设计时,数据库中每一个表对应Java一个实体类,实体类是系统的“人”、“事”、“物”等一些名词。
图书(Book)就是一个实体类了,实体类Book代码:

// 图书实体类
public class Book {
    
    // 图书编号
    private String bookid;
    // 图书名称
    private String bookname;
    // 图书作者
    private String author;
    // 出版社
    private String publisher;
    // 出版日期
    private String pubtime;
    // 库存数量
    private int inventory;
    
    public String getBookid() {
        return bookid;
    }
    public void setBookid(String bookid) {
        this.bookid = bookid;
    }
    public String getBookname() {
        return bookname;
    }
    public void setBookname(String bookname) {
        this.bookname = bookname;
    }
    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author
    }
    
    // ...
    // 省略Getter和Setter方法
}

实体类有很多私有属性(成员变量),为了在类外部能够访问它们,一般都会提供公有的Getter和Setter方法。

本例表格中的数据是从JSON文件Books.json中读取的,Books.json位于项目的db目录中,JSON文件Books.json的内容:

 [{"bookid":"0036","bookname":"高等数学","author":"李放","publisher":"人民邮电出版社","pubtime":"20000812","inventory":1},
  {"bookid":"0004","bookname":"FLASH精选","author":"刘扬","publisher":"中国纺织出版社","pubtime":"19990312","inventory":2},
  ...
  {"bookid":"0005","bookname":"java基础","author":"王一","publisher":"电子工业出版社","pubtime":"19990528","inventory":3},
  {"bookid":"0032","bookname":"SOL使用手册","author":"贺民","publisher":"电子工业出版社","pubtime":"19990425","inventory":2}]
public class HelloWorld {
    
    public static void main(String[] args) {
        List<Book> data = readData();
        new MyFrameTable("图书库存", data); 
    }
    
    // 从文件中读取数据
    private static List<Book> readData() {
        // 返回的数据列表
        List<Book> list = new ArrayList<Book>();
        // 数据文件
        String dbFile = "./db/Books.json";
        
        try (FileInputStream fis = new FileInputStream(dbFile);
             InputStreamReader ir = new InputStreamReader(fis);
             BufferedReader in = new BufferedReader(ir)) {
            
            // 1.读取文件
            StringBuilder sbuilder = new StringBuilder();
            String line = in.readLine();
            
            while (line != null) {
                sbuilder.append(line);
                line = in.readLine();
            }
            
            // 2.JSON解码
            // 读取JSON字符完成
            System.out.println("读取JSON字符完成...");
            // JSON解码,解码成功返回JSON数组
            JSONArray jsonArray = new JSONArray(sbuilder.toString());
            System.out.println("JSON解码成功完成...");
            
            // 3.将JSON数组放到List<Book>集合中
            // 遍历集合
            for (Object item : jsonArray) {
                JSONObject row = (JSONObject) item;
                Book book = new Book();
                book.setBookid((String) row.get("bookid"));
                book.setBookname((String) row.get("bookname"));
                book.setAuthor((String) row.get("author"));
                book.setPublisher((String) row.get("publisher"));
                book.setPubtime((String) row.get("pubtime"));
                book.setInventory((Integer) row.get("inventory"));
                list.add(book);
            }
        } catch (Exception e) {
        }
        return list;
    }
}
  1. 读取文件。通过Java IO取得文件./db/Books.json,每次读取的字符串保存到StringBuilder的sbuilder对象中。文件读完sbuilder中就是全部的JSON字符串。
  2. JSON解码:读取JSON字符完成后,需要对其进行解码。由于JSON字符串是数组结构,因此解码时候使用JSONArray,创建JSONArray对象过程就是对字符串进行解码的过程,如果没有发生异常,说明成功解码。
  3. 将JSON数组放到List<Book>集合中。本例表格使用的数据格式不是JSON数组形式,而是 List<Book>,这种结构就是List集合中每一个元素都是Book类型。这个过程需要遍历JSON数组,把数据重新组装到Book对象中。

模型BookTableModel:

import java.util.List;
import javax.swing.table.AbstractTableModel;

public class BookTableModel extends AbstractTableModel {
    
    // 列名数组
    private String[] columnNames = { "书籍编号", "书籍名称", "作者", "出版社", "出版日期", "库存数量" };
    
    // data保存了表格中数据,data类型是List集合
    private List<Book> data = null;
    
    public BookTableModel(List<Book> data) {
        this.data = data;
    }
    
    // 获得列数
    @Override
    public int getColumnCount() {
        return columnNames.length;
    }
    
    // 获得行数
    @Override
    public int getRowCount() {
        return data.size();
    }
    
    // 获得某行某列的数据
    @Override
    public Object getValueAt(int row, int col) {
        Book book = (Book) data.get(row);
        switch (col) {
            case 0:
                return book.getBookid();
            case 1:
                return book.getBookname();
            case 2:
                return book.getAuthor();
            case 3:
                return book.getPublisher();
            case 4:
                return book.getPubtime();
            case 5:
                return new Integer(book.getInventory());
        }
        return null;
    }
    
    // 获得某列的名字
    @Override
    public String getColumnName(int col) {
        return columnNames[col];
    }
}

抽象类AbstractTableModel要求必须实现getColumnCount()(提供表格列数)、getRowCount()(提供表格行数)和getValueAt()(指定行和列时单元格内容)三个抽象方法。第51行的getColumnName()方法不是抽象类要求实现的方法,重写该方法能够给表格提供有意义的列名。

窗口代码:

public class MyFrameTable extends JFrame {
    
    // 获得当前屏幕的宽高
    private double screenWidth = Toolkit.getDefaultToolkit().
        getScreenSize().getWidth();
    private double screenHeight = Toolkit.getDefaultToolkit().
        getScreenSize().getHeight();
    
    private JTable table;
    // 图书列表
    private List<Book> data;
    
    public MyFrameTable(String title, List<Book> data) {
        super(title);
        
        this.data = data;
        TableModel model = new BookTableModel(data);
        
        table = new JTable(model);
        // 设置表中内容字体
        table.setFont(new Font("微软雅黑", Font.PLAIN, 16));
        // 设置表列标题字体
        table.getTableHeader().setFont(new Font("微软雅黑", Font.BOLD, 16));
        // 设置表行高
        table.setRowHeight(40);
        // 设置为单行选中模式
        table.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
        // 返回当前行的状态模型
        ListSelectionModel rowSM = table.getSelectionModel();
        // 注册侦听器,选中行发生更改时触发
        rowSM.addListSelectionListener(e -> {
            // 只处理鼠标按下
            if (e.getValueIsAdjusting() == false) {
                return;
            }
            ListSelectionModel lsm = (ListSelectionModel) e.getSource();
            if (lsm.isSelectionEmpty()) {
                System.out.println("没有选中行");
            } else {
                int selectedRow = lsm.getMinSelectionIndex();
                System.out.println("第" + selectedRow + "行被选中");
            }
        });
        
        JScrollPane scrollPane = new JScrollPane();
        scrollPane.setViewportView(table);
        getContentPane().add(scrollPane, BorderLayout.CENTER);
        
        // 设置窗口大小
        setSize(960, 640);
        // 计算窗口位于屏幕中心的坐标
        int x = (int) (screenWidth - 960) / 2;
        int y = (int) (screenHeight - 640) / 2;
        // 设置窗口位于屏幕中心
        setLocation(x, y);
        // 设置窗口可见
        setVisible(true);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值