【Java基础】07-图形用户界面(上)

绘图

Graphics 类

图形环境和图形对象:

  • 坐标:GUI 组件的左上角为(0, 0),水平坐标 x 从左向右增加,垂直坐标 y 从上到下增加。(这里和图片的坐标一致,如 OpenCV 里面图片坐标的表示方法,而和数组、矩阵、张量的行列方式表示相反。)x 和 y 为整数值,以像素为单位。
  • Graphics 对象:Graphics 是专门管理图形环境的一个抽象类,在 java.awt 包下面,提供了一套与平台无关的绘图接口。各个平台上实现的其实是 Java 系统创建的 Graphics 类的一个子类,用来实现绘图的功能,但是这个子类对程序员是透明的。执行 paint 方法的时候,系统会传递一个指向特定平台的 Graphics 子类的图形对象。(这个子类是已经实现了 Graphics 类的抽象方法)

颜色:Java 中有关颜色的类是 Color 类,它在 java.awt 包中,声明了用于操作颜色的方法和常量。其中不仅有代表各种颜色的属性,还能自定义颜色,设置颜色和获取对象的颜色,Graphics 类中也有设置和获取组件颜色的方法。一些方法举例如下:
在这里插入图片描述
字体:与字体控制有关的类为 Font 类,在 java.awt 包中
在这里插入图片描述
图形:Graphics 类可以绘制多种图形,包括文本、线条、矩形、多边形、椭圆、弧等。
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
举个🌰:

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

class GraphicsTester extends JFrame {
    public GraphicsTester(){
        super("演示字体、颜色、绘图"); // 窗口名称
        setVisible(true); // 显示窗口
        setSize(480, 250);
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        g.setFont(new Font("SanSerif", Font.BOLD, 12));
        g.setColor(Color.blue);
        g.drawString("Hello, this is a sentence of SanSerif", 20, 50);

        g.setFont(new Font("Serif", Font.ITALIC, 14));
        g.setColor(new Color(255, 0, 0));
        g.drawString("And this is Serif", 250, 50);
        g.drawLine(20, 60, 460, 60);

        g.setColor(Color.green);
        g.drawRect(20, 70, 100, 50);
        g.fillRect(130, 70, 100, 50);

        g.setColor(Color.yellow);
        g.drawRoundRect(240, 70, 100, 50, 25, 25);
        g.fillRoundRect(350, 70, 100, 50, 25, 25);

        g.setColor(Color.cyan);
        g.draw3DRect(20, 130, 100, 50, true); // 凸起效果
        g.fill3DRect(130, 130, 100, 50, false); // 凹陷效果

        g.setColor(Color.pink);
        g.drawOval(240, 130, 100, 50);
        g.fillOval(350, 130, 100 ,50);

        g.setColor(new Color(0, 120, 120));
        g.drawArc(20, 190, 100, 50, 0, 90);
        g.fillArc(130, 190, 100, 50, 0, 90);

        g.setColor(Color.black);
        int[] xValues = {250, 280, 290, 300, 330, 310, 320, 290, 260, 270};
        int[] yValues = {210, 210, 190, 210, 210, 220, 230, 220, 230, 220};
        g.drawPolygon(xValues, yValues, 10);//绘制空心多边形

        int[] xValuesSecond = {360, 390, 400, 410, 440, 420, 430, 400, 370, 380};
        g.fillPolygon(xValuesSecond, yValues, 10); // 绘制实心多边形
    }
}

public class Exp1 {
    public static void main(String[] args) {
        GraphicsTester app = new GraphicsTester();
        app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

结果如下:
在这里插入图片描述
JFrame 的 对象在创建之后会自动调用其中的 paint 方法。这里会传入一个 Graphics 引用,实际上这是随平台不同的 Graphics 的一个子类的引用,由操作系统创建传入,这样不同的系统会传入和平台相适应的 Graphics 子类,通过多态由父类引用调用,这样即实现了平台无关性,又隐藏了实现的细节。

Java2D API

Java2D API 提供了许多高级的二维图形的绘制功能。里面的组件分布在 java.awt, java.awt.image, java.awt.color, java.awt.font, java.awt.geom, java.awt.print, java.awt.image.renderable 包中。

其可以很轻松地实现如下功能
在这里插入图片描述
Graphics2D 类是 Graphics 类的抽象子类,要使用 Java2D API,就必须建立 Graphics2D 对象。就和传递给 paint() 方法实际上 Graphics 的一个子类实例一样,传递给 paint() 也可以是 Graphics2D 的一个子类实例。然后要访问 Graphics2D 功能,必须将传递给 paint 的 Graphics 引用强制传唤为 Graphics2D 引用。

下面是一个 Graphics2D 的例子,实现字符串渐变色彩的显示

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

public class Test extends JApplet {
    @Override
    public void paint(Graphics g) {
        super.paint(g);
        Graphics2D g2d = (Graphics2D)g;
        g2d.setPaint(new GradientPaint(0, 0, Color.red, 180, 35, Color.yellow));
        g2d.drawString("This is a Java Applet!", 25, 25);
    }
}

applet 是一种可以嵌入网页中运行的小程序。创建一个 html 文件,作为 applet 存在的网页。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test</title>
    <hr>
    <applet code="Test.class" width="300" height="150"></applet>
    <hr>
</head>
<body>

</body>
</html>

运行结果如下:
在这里插入图片描述
目前 applet 应用很有局限了,所以在 IDEA 中你甚至需要在 plugin 中下载插件才能运行。

Swing 基础

前面介绍了如何使用 java.awt 包里面的 Graphics 类和 Graphics2D 类的子类绘制普通的图形,但是如果想要绘制一个按钮,并且对点击事件进行相应,需要使用 java.swing 包里面提供的组件。
前面提到的 JFrame、JApplet 都是 Swing 的组件,都是顶级的容器。分别代表窗口组件和 Applet 容器组件。

JFC 与 Swing

  • JFC(Java Foundation Classes)是关于 GUI 组件和服务的完整集合。作为 Java SE 的一个有机部分主要包括5个部分:awt, Java2D, Accessibility, Drag&Drop, Swing.
  • Swing 是 JFC 的一部本,提供包括按钮、窗口、表格等所有的组件,而且是纯 Java 组件,具有在不同平台上显示统一风格的特性。

Swing 和 awt

  • awt 组件在 java.awt 包中,也含有可一个绘制按钮、窗口等的工具,如 Button、Checkbox、Scrollbar 等,都是 Component 类的子类。它们大多数都含有 native code,会随着操作系统平台的不同显示不同的样子,并且不能更改,还是重量级的组件。
  • Swing 组件都是在原来 awt 组件的名称前面加上 J,例如 JButton、JCheckBox、JScrollbar 等,都是 JCompoent 的子类。是 awt 的扩展,并且是纯 Java 语言编写,有平台一只性,而且都是轻量级的组件(除了顶层容器 JFrame,JApplet,JDialog)。Swing 的组件可以提供更加丰富的视觉感受。

在 Applet 和 Application 中使用 Swing

在 Applet 中使用 Swing,救赎要将 Swing 组件加载到 Applet 容器上(通常是 JApplet),这通常在 init() 方法中完成
在 Application 中应用 Swing,也是将 Swing 组件加载到这个 Application 的顶层容器(通常是 JFrame)中

一个在 applet 中使用按钮的例子

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

public class SwingTest extends JApplet {
    @Override
    public void init() {
        super.init();
        Container contentPane = getContentPane(); // 定义顶级的内容面板
        contentPane.setLayout(new GridLayout(2, 1)); // 定义布局
        JButton button = new JButton("Click me");
        final JLabel label = new JLabel();
        contentPane.add(button); contentPane.add(label);
        // 在按钮上添加时间监听器
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                // 设置对话框
                String information = JOptionPane.showInputDialog("Please input a string");
                // 将对话框中的内容显示到标签上
                label.setText(information);
            }
        });
    }
}

当 applet 程勋运行的时候,首先就睡执行其中的 init() 方法
运行结果如下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在 JFrame 容器中实现一样的功能,注意两者的区别

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

public class SwingApplication {
    public static void main(String[] args) {
        JFrame f = new JFrame("Simple Swing Application");
        Container contenPane = f.getContentPane();
        contenPane.setLayout(new GridLayout(2, 1));
        JButton button = new JButton("Click me");
        final JLabel label = new JLabel();
        contenPane.add(button); contenPane.add(label);
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String info = JOptionPane.showInputDialog("Please input a string");
                label.setText(info);
            }
        });

        f.setSize(200, 100);
        f.setVisible(true);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

Swing 的层次

多数 Swing 组件的继承层次
在这里插入图片描述

  • Component:包含 paint(), repaint() 方法,可以在屏幕上绘制组件。大多数 GUI 直接或者间接扩展 Component。
  • Container: 用来容纳相关组件。包含 add() 方法,用来添加组件。包括 setLayout 方法,用来设置布局,帮助 Container 对象对其中的组件进行定位和设置组件大小。
  • JComponent:这是多数 Swing 组件的超类,可以用来定制观感,使用键鼠直接访问 GUI 组件,进行交互。对一般的时间进行响应。

Swing 的组件分为 3 个层次:

  1. 顶级容器
  2. 中间层容器
  3. 原子组件

顶层容器

顶层容器包括:JFrame、JDialog、JApplet。
JFrame 用来创建单个主窗口,JDialog 用来实现一个二级窗口(对话框),JApplet 用来在浏览器中实现一个 applet 显示区域。这些必须和 OS 打交道,所以都是重量级组件。它们分别从 AWT 的 Frame、Dialog、Applet 类继承而来。每个使用 Swing 组件的 Java 程序必须至少有一个顶层容器,别的组件都必须在这个顶层容器上才能显现出来。

中间层组件

中间层组件是用来容纳更第一级的组件的,分为两类

  • 一般用途的
    • JPanel
    • JScrollPane
    • JSplitPane
    • JTabbedPane
    • JToolBar
  • 特殊用途的
    • JInternalFrame
    • JRootPane
      【注】JRootPane 对象可以直接从顶层容器中获得,而别的中间容器在使用的时候都需要构建一个对象。

原子组件

原子组件是 GUI 中和用户进行交互的组件,包括

  • 显示不可编辑信息的
    • JLabel、JProgressBar、JToolTip
  • 有控制功能,可以用来输入信息的
    • JButton、JCheckBox、JRadioButton、JComboBox、JList、JMenu、JSlider、JSpinner、JTexComonent
  • 能提供格式化信息并允许用户选择的
    • JColorChooser、JFileChooser、JTable、JTree

三层结构的例子:

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

public class ComponentTester {
    public static void main(String[] args) {
        // 是否设置一些基础的装饰
        JFrame.setDefaultLookAndFeelDecorated(true);
        JFrame frame = new JFrame("Swing Frame");
        Container container = frame.getContentPane();
        JPanel panel = new JPanel();
        // 设置边框
        panel.setBorder(BorderFactory.createLineBorder(Color.black, 5));
        panel.setLayout(new GridLayout(2, 1));
        JLabel label = new JLabel("Label", SwingConstants.CENTER); // 位置为居中
        JButton button = new JButton("Button");
        panel.add(label); panel.add(button);
        container.add(panel);
        frame.setSize(200, 100);

        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

结果如下:
在这里插入图片描述
这里展示一下常见的 Swing 组件

在这里插入图片描述

布局管理

之前我们使用了 GridLayout 管理顶级内容面板的布局,这些实现了布局方式的类叫做布局管理器(Interface LayoutManager)。布局管理器可以有序地将组件安排到指定的位置。

调用布局管理器的使用方法如下:

Container contentPane = frame.getContentPane();
contentPane.setLayout(new FlowLayout());

使用布局管理器可以更加容易地进行布局,并且当改变窗口大小的时候,它还会自动更新版面来配合窗口的大小,不用担心版面会因此混乱

常用的布局管理器类

布局管理器类就是要实现 LayoutManager 接口。常用的布局管理器类如下所示:

  • BorderLayout
  • FlowLayout
  • GridLayout
  • CardLayout
  • GridBagLayout
  • BoxLayout
  • SpringLayout

内容面板默认使用的是 BorderLayout
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

内部类

内部类的特性

  • 所谓内部类就是在另一个类、方法或直接在一个大括号内的代码块中定义的类
  • 可以访问其外部类中的所有数据成员和方法成员
  • 将相联系的几个类作为同一个外部类的内部类,外界又不需要直接使用这些内部类,这是一种十分方便的对逻辑上相联系的类进行分组的方式
  • 内部类除了外部类可见以外,在外界是隐藏的,即使是同一个包里面的其它类,也是不可见的
  • 内部类非常适合编写事件驱动程序,因此在 GUI 编程中十分方便
  • 声明方式
    • 命名的内部类:可以在类的内部多次使用,就和正常的创建类的方式相同
    • 匿名的内部类:在 new 关键字后面声明,没有类名,而实际上是直接在创建对象时继承超类或者实现接口。
  • 内部类的命名
    • java 编译的时候会为每一个类生成一个 .class 文件,内部类也不例外。假设外部类的类名为 Myclass,则内部类的类名为:
    • Myclass$c1.class (命名内部类,c1是内部类的类名)
    • Myclass$1.class (匿名的内部类,1 表示类中声明的第一个匿名内部类)

一个内部类的简单应用:

public class Parcel1 {

    class Contents{
        private int i = 11;
        public int value() {
            return i;
        }
    }

    class Destination{
        private String label;
        Destination(String whereTo){
            label = whereTo;
        }
        String readLable(){
            return label;
        }
    }

    public void ship(String dest){
        Contents c = new Contents();
        Destination d = new Destination(dest);
        System.out.println(d.readLable());
    }

    public static void main(String[] args) {
        Parcel1 p = new Parcel1();
        p.ship("Tanzania");
    }
}

如下是一个外部类方法返回内部类引用的例子,里面的 to(), cont() 方法相当于工厂方法,一般存在于工厂类中,用于生产对象,这样可以降低类与使用这个类的类之间的耦合度。在别的类中或者外部类的静态方法中想要定义内部类的引用,需要使用 [外部类].[内部类] 的方式。

public class Parcel2 {

    class Contents{
        private int i=11;
        public int value() {
            return i;
        }
    }

    class Destination{
        private String label;
        Destination(String whereTo){
            label = whereTo;
        }
        String readLabel(){
            return label;
        }
    }

    public Destination to(String s){
        return new Destination(s);
    }

    public Contents cont(){
        return new Contents();
    }

    public void ship(String dest){
        Contents c = cont();
        Destination d = to(dest);
        System.out.println(d.readLabel());
    }

    public static void main(String[] args) {
        Parcel2 p = new Parcel2();
        p.ship("Tanzania");
        Parcel2 q = new Parcel2();
        Parcel2.Contents c = q.cont();
        Parcel2.Destination d = q.to("Borneo") ;
        System.out.println(d.readLabel());
    }
}

内部类可以实现接口、继承抽象类

我们可以在外部定义抽象类或者接口,使用内部类实现接口或者抽象类,然后设置访问权限为外界无法访问,然后留一个外界可以访问的方法返回接口或者抽象类的引用,而实际上可以在这样的方法里面设计为返回实现接口或者抽象类的子类的对象。

举例如下:

abstract class Contents{
    abstract public int value();
}

interface Destination{
    String readLabel();
}

public class Parcel3 {
    private class PContents extends Contents{
        private int i = 11;

        @Override
        public int value() {
            return i;
        }
    }

    protected class PDestination implements Destination{
        private String label;
        private PDestination(String whereTo){
            label = whereTo;
        }

        @Override
        public String readLabel() {
            return label;
        }
    }

    public Destination dest(String s){
        return new PDestination(s);
    }

    public Contents cont(){
        return new PContents();
    }
}

class Test{
    public static void main(String[] args) {
        Parcel3 p = new Parcel3();
        Contents c = p.cont();
        c.value();
        Destination d = p.dest("Tanzania");
    }
}

这里 PContents 类设置为私有类,PDestination 类的构造方法设置为私有方法,外界无法创建对象。实现了对 PContents 和 PDestination 类的隐藏。

局部作用域中的内部类

这种方式通过实现一个可见的接口,或者继承一个课件的抽象类,在局部作用域中定义一个类,然后返回这个类的对象指向父类或者接口的引用,可以实现局部作用域中类的隐藏。局部作用域一般指在方法中定义的类或者块作用域中的内部类。

方法中的内部类

interface Destination{
    String readLabel();
}

public class Parcel4 {
    public Destination dest(String s){
        class PDestination implements Destination{
            private String label;
            private PDestination(String whereTo){
                label = whereTo;
            }
            public String readLabel(){return label;}
        }
        return new PDestination(s);
    }

    public static void main(String[] args) {
        Parcel4 p = new Parcel4();
        Destination d = p.dest("Tanzania");
    }
}

块作用域中的内部类

public class Parcel5 {
    private void internalTracking(boolean b){
        if(b){
            class TrackingSlip{
                private String id;
                TrackingSlip(String s){id = s;}
                String getSlip(){return id;}
            }
            TrackingSlip ts = new TrackingSlip("slip");
            String s = ts.getSlip();
            System.out.println(s);
        }
    }
    public void track(){
        internalTracking(true);
    }
    public static void main(String[] args) {
        Parcel5 p = new Parcel5();
        p.track();
    }
}

匿名内部类

匿名的内部类实际上创建的是一个继承了父类的子类或者实现了某一接口的类,new 关键字后面跟的是父类名或者接口名

abstract class Contents{
    abstract public int value();
}

public class Parcel6 {
    public Contents cont(){
        return new Contents(){
            private int i = 11;

            @Override
            public int value() {
                return i;
            }
        };
    }

    public static void main(String[] args) {
        Parcel6 p = new Parcel6();
        Contents c = p.cont();
    }
}

匿名内部类能够出现在任何对象能出现的位置,甚至包括参数表中。

事件驱动

GUI 应用是时间驱动的。常见的事件包括

  • 移动鼠标
  • 单双击鼠标的各个按键
  • 单击 GUI 面板上的按钮
  • 在文本框中输入
  • 在菜单栏中选择菜单项
  • 在组合框中单选、多选
  • 拖动滚动条
  • 关闭窗口
    Swing 中有事件对象代表事件,程序通过事件对象获取关于事件的信息。

和事件相关的几个要素

事件源

  • 事件源是与用于进行交互的 GUI 组件,表示事件来自于哪个组件或者对象
  • 例:如果一个按钮被按下,需要程序对这个事件进行响应,则按钮就是事件源

事件监听器

  • 负责监听事件并且做出响应
  • 一旦监听到事件发生,就会自动调用响应的时间处理程序进行响应

事件对象

  • 封装了与有关已经发生的事件的信息
  • 例如按钮按下是一个需要处理的事件,则在按钮被按下的时候就会产生一个事件对象。事件对象中包含事件相关的信息和事件源。

事件处理

程序员需要为事件源注册一个事件监听器实现事件处理方法

监听器注册

  • 事件源提供注册监听器或者取消监听器的方法
  • 如果事件发生,已经注册的监听器就会被通知
  • 一个事件源可以注册多个事件监听器,每个监听器又可以和多个事件进行对应
    在这里插入图片描述

事件监听器

  • 事件监听器是一个实现了事件监听器接口的对象,一般通过形如 addxxxListener() 的方法注册到某个事件源上。
  • 不同的 Swing 组件可以注册不同的事件监听器
  • 一个事件监听器接口中可以包含多个对多种具体时间的专用处理方法
    在这里插入图片描述

常见的事件对象

在这里插入图片描述

常见的 Swing 事件源

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

接口与适配器

之前说过的事件监听器里面包含许多要实现的功能接口,有时候直接实现事件监听器接口非常不方便。例如MouseListener 接口,当我们只想对鼠标的点击事件响应的时候,即只想实现 mouseClicked() 方法的时候也需要实现其他的抽象方法为空方法。非常的不方便。这时候就需要与监听器接口对应的适配器类(以 Adapter 结尾)。实际上适配器类就是将监听器中需要实现的方法实现为空方法,我们继承适配器类只需要将我们需要处理的事件的方法覆盖即可。

事件处理的几种方法

  • 实现事件监听器接口
  • 继承事件监听器适配器类
  • 使用匿名内部类
  • lambda 表达式

前面两个已经介绍过,这里介绍一下第三种和第四种的适用情概。

  • 第三种特别适合某个类已经继承了别的类,然后还想使用适配器类。但是适配器类毕竟是类, 根据 Java 的规则,一个类只能单继承,所以这时候就不适合用继承类的方式实现其中的接口方法了。所以使用匿名内部类会显得比较合适
  • 第四种适用于只有一个抽象方法的函数式监听器接口。

创建一个窗口,在标题栏中显示鼠标点击的位置坐标

实现 MouseListener 接口


import javax.swing.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

public class ImplementMouseListener implements MouseListener {
    JFrame f;
    public ImplementMouseListener(){
        f = new JFrame();
        f.setSize(800, 450);
        f.setVisible(true);
        f.addMouseListener(this);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        f.setTitle("The coordinate of the clicked place is "+e.getX()+", "+e.getY());
    }

    @Override
    public void mouseEntered(MouseEvent e) {

    }

    @Override
    public void mouseExited(MouseEvent e) {

    }

    @Override
    public void mousePressed(MouseEvent e) {

    }

    @Override
    public void mouseReleased(MouseEvent e) {

    }

    public static void main(String[] args) {
        new ImplementMouseListener();
    }
}

继承 MouseAdapter 类

import javax.swing.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class ExtendMouseAdapter extends MouseAdapter {
    JFrame f;
    public ExtendMouseAdapter(){
        f = new JFrame();
        f.setSize(800, 450);
        f.setVisible(true);
        f.addMouseListener(this);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        super.mouseClicked(e);
        f.setTitle("The coordinate of the clicked place is "+e.getX()+", "+e.getY());
    }

    public static void main(String[] args) {
        new ExtendMouseAdapter();
    }
}

使用匿名内部类

import javax.swing.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class UserInnerClass {
    JFrame f;
    public UserInnerClass(){
        f = new JFrame();
        f.setSize(800, 450);
        f.setVisible(true);
        f.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                super.mouseClicked(e);
                f.setTitle("The coordinate of the clicked place is "+e.getX()+", "+e.getY());
            }
        });

        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {
        new UserInnerClass();
    }
}

事件派发机制

Swing 中的组件是非线程安全的,所以 Swing 中专门提供了一个事件派发线程 (EDT)用于保证组件的安全控制。

  • 用来执行组件事件处理程序的线程,依次从系统的事件队列中取出事件处理,一定要执行完上一个事件的处理程序后才会处理下一个事件。
  • 事件监听器中的方法都是在事件派发线程中执行的。

可以调用 invokeLater() 或者 invokeAndWait() 请事件分发线程运行某段代码。使用的方法是将一个实现了 Runnable 接口的对象作为参数传给 invokeLater() 或者 invokeAndWait() 方法,将需要执行的代码方法对象的 run() 方法中。

  • invokeLater 是异步的,不等代码执行完就返回,将线程作为后台线程运行
  • invokeAndWait 是同步的,要等代码执行完之后才返回,调用的时候要避免死锁。

举一个例子:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

public class CardLayoutDemo implements ItemListener { // 下拉列表事件监听器接口
    JPanel cards;
    final static String BUTTONPANEL = "JPanel with JButton";
    final static String TEXTPANEL = "JPanel with JTextField";

    public void addComponentToPane(Container pane){
        JPanel comboBoxPane = new JPanel(); // 默认使用 FlowLayout 布局
        String[] comboBoxItems = {BUTTONPANEL, TEXTPANEL};
        JComboBox cb = new JComboBox(comboBoxItems); // 下拉列表组件
        cb.setEditable(false); // 内容不可修改
        cb.addItemListener(this); // 根据事件响应——使用itemStateChanged()方法响应
        comboBoxPane.add(cb); // 将下拉组件加入到中间容器中

        JPanel card1 = new JPanel();
        card1.add(new JButton("Button 1"));
        card1.add(new JButton("Button 2"));
        card1.add(new JButton("Button 3"));

        JPanel card2 = new JPanel();
        card2.add(new JTextField("TextField", 20));

        cards = new JPanel(new CardLayout());
        cards.add(card1, BUTTONPANEL); // 用和 JComboBox 相同的名字命名 card
        cards.add(card2, TEXTPANEL);

        pane.add(comboBoxPane, BorderLayout.PAGE_START); // 顶层容器中加入 ComboBox
        pane.add(cards, BorderLayout.CENTER); // 顶层容器中加入卡片
    }

    @Override
    public void itemStateChanged(ItemEvent e) {
        CardLayout cl = (CardLayout)(cards.getLayout());
        cl.show(cards, (String)e.getItem()); // 事件的 getItem()方法就返回 ComboBox 被点击的文本的内容
    }

    private static void createAndShowGUI(){
        JFrame frame = new JFrame("CardLayoutDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        CardLayoutDemo demo = new CardLayoutDemo();
        demo.addComponentToPane(frame.getContentPane());

        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

结果如下,可以使用鼠标选择下拉列表达到显示不同组件的目的
在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值