「JavaSE」- GUI编程

421 阅读30分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

GUI 编程

Swing和AWT 是java开发GUI常用的技术,但是由于外观不太美观, 组件数量偏少, 并且运行需要JRE环境(动不动就上百M的JRE包....), 所以没有流行起来。但是 ,建议简单的学习和了解。

  1. 组件(JTable,JList等)很多都是MVC的经典示范,学习GUI可以了解mvc架构。
  1. 工作时,有可能遇见需要维护N年前awt/swing写的软件 ,虽然可能性很小。
  1. 可以写一些自己使用用的软件,还是相当的方便。

swing是建立在awt基础上的。有必要学习一下,原因如下:

  • 知识的关联性,比如布局、颜色、字体、事件机制等....这些都是awt里的内容,但在swing里也经常使用到。
  • 学习成本低,因为awt和swing在编码上区别不大,写法基本一致,组件使用上也差不多(只需要记住少数有区别的地方就可以了)。
  • 使用场景存在不同:awt消耗资源少,运行速度快,适合嵌入式等;swing跨平台,组件丰富。

虽然现在用Java做cs的很少,但是对于学习Java基础来说,还是很好的资源,可以利用它把以前的所有知识贯穿起来,做一些小应用、游戏等都可以,可以将自己的一些小想法,做成工具分享出来!

AWT

一、AWT介绍

  • AWT(Abstract Window Toolkit,抽象窗口工具)包括了很多类和接口,用于Java Application的GUI(Graphics User Interface 图形用户界面)编程。
  • GUI的各种元素(如:窗口、按钮、文本框等)由Java类来实现。
  • 使用AWT所涉及的类一般在Java.AWT包及其子包中。
  • Container(容器) 和 Component(组件) 是AWT中的两个核心类。

image-20220420204041017

所有的可以显示出来的图形元素都称为Component,Component代表了所有的可见的图形元素。

Component里有一种比较特殊的图形元素叫Container,Container(容器)在图形界面里面是一种可以容纳其它Component元素的一种容器,Container本身也是一种Component,Container里面也可以容纳别的Container。

Container里面又分为Window和Pannel,Window是可以独立显示出来的,平时看到的各种各样的应用程序的窗口都可以称为Window,Window作为一个应用程序窗口独立显示出来。

Pannel也可以容纳其它的图形元素,但一般看不见Pannel,Pannel不能作为应用程序的独立窗口显示出来,Pannel要想显示出来就必须得把自己装入到Window里面才能显示出来。Pannel应用比较典型的就是Applet(JAVA的页面小应用程序),现在基本已经不用了,AJAX和JAVASCRIPT完全取代了它的应用。

Window本身又可以分为Frame和Dialog,Frame就是平时看到的一般的窗口,而Dialog则是需要用户进行某些操作(如点击某个下拉菜单的项)才出现的对话框,这种对话框就是Dialog。

二、组件和容器(Component & Container)

  • Java的图形用户界面的最基本组成部分是Component,Component类及其子类的对象用来描述以图形化的方式显示在屏幕上并能与用户进行交互的GUI元素。例如,一个按钮,一个标签等。

  • 一般的Component对象不能独立地显示出来,必须将 “放在” 某一个Container对象中才可以显示出来。

    • Container是Component的子类,Container子类对象可以 “容纳” 别的Component对象。

    • Container对象可使用add(...)方法向其中添加其他的Component对象。

    • Container是Component的子类,因此Container对象也可以被当作Component对象添加到其他的Container对象中。

    • 有两种常用的Container:

      • Window:其对象表示自由停泊的顶级窗口。
      • Panel:其对象可作为容纳其它Component对象,但不能独立存在,必须被添加到其它Container中(如Window或Applet)

2.1 Fram

  • Frame是Window的子类,由Frame或其子类创建的对象为一个窗体。

  • Frame的常用构造方法:

    • Frame()
    • Frame(String s):创建标题栏为字符串s的窗口
  • Frame的属性方法

    • setBounds(int x,int y,int width,int height):设置窗体位置和大小。x、y是左上角坐标,width、height是宽度和高度。
    • setSize(int width,int height):设置窗体的大小,x、y是左上角坐标。
    • setLocation(int x,int y):设置窗体位置,x、y是左上角坐标。
    • setBackground(Color c):设置背景颜色,参数为Color对象。
    • setVisible(boolean b):设置是否可见。
    • setTitle(String name):设置标题栏。
    • setResizable(boolean b):设置窗体是否可以调整大小。

【Frame范例】

 package com.kuang;
 ​
 import java.awt.*;
 ​
 //GUI编程编写的第一个图形界面窗口
 public class TestFrame {
   public static void main(String[] args) {
     
     //只是在内存里面创建了一个窗口对象,还不能真正显示出来
     Frame frame = new Frame("我的第一个JAVA图形界面窗口");
     
     //设置窗体的背景颜色
     //三个参数分别代表RGB
     frame.setBackground(new Color(110, 217, 119));
     
     //设置窗体是否可见
     //要想看到在内存里面创建出来的窗口对象,必须调用setVisble()方法,并且把参数true传入才能看得见窗体
     //如果传入的参数是false,那窗体就是看不见的
     frame.setVisible(true);
     
     //设置窗体的初始大小
     frame.setSize(400,400);
     
     //设置窗体出现时的位置,如果不设置则默认在左上角(0,0)位置显示
     frame.setLocation(200,200);
     
     // 设置窗体能否被改变大小
     // 默认是true,设置为false后表示不能改变窗体的显示大小
     // 这里窗体显示的大小设置为200X200,不能再使用鼠标拖大或者缩小
     frame.setResizable(false);
   }
 }

运行结果:

image-20220421180449125

发现问题:关闭不掉。

解决方法:停止Java程序的运行

【演示二:展示多个窗口】

 package com.kuang;
 ​
 import java.awt.*;
 ​
 public class TestMultiFrame {
   public static void main(String[] args) {
     
     MyFrame f1 = new MyFrame(100,100,200,200,Color.blue);
     MyFrame f2 = new MyFrame(300,100,200,200,Color.yellow);
     MyFrame f3 = new MyFrame(100,300,200,200,Color.red);
     MyFrame f4 = new MyFrame(300,300,200,200,Color.MAGENTA);
     
   }
 }
 ​
 //自定义一个类MyFrame,并且从Frame类继承,这样自定义类MyFrame类就拥有了Frame类的一切属性和方法,并且MyFrame类还可以自定义属性和方法
 //使用从Frame类继承而来的自定义类来创建图形窗口比直接使用Frame类来创建图形窗口要灵活,所以一般使用从Frame类继承而来的自定义类创建图形窗口界面比较好,不推荐直接使用Frame类来创建图形窗口界面
 class MyFrame extends Frame{
   
   //定义一个静态成员变量id,用来记录创建出来的窗口的数目
   static int id = 0;
   
   //自定义构成方法,在构造方法体内使用super调用父类Frame的构造方法
   public MyFrame(int x,int y,int w,int h,Color color){
     super("MyFrame"+(++id));
     /*使用从父类Frame继承而来的方法设置窗体的相关属性*/
     setBackground(color);//设置背景色
     setLayout(null);//
     setBounds(x,y,w,h);//设置初始位置、大小
     setVisible(true);//设置是否可见
   }
 }

运行结果:

image-20220421190745274

2.2 Panel

  • Panel对象可以堪称可以容纳Component的空间

  • Panel对象可以拥有自己的布局管理器

  • Panel类拥有从其父类继承来的方法:

    • setBounds(int x,int y,int width,int height):设置窗体位置和大小。x、y是左上角坐标,width、height是宽度和高度。
    • setSize(int width,int height):设置窗体的大小,x、y是左上角坐标。
    • setLocation(int x,int y):设置窗体位置,x、y是左上角坐标。
    • setBackground(Color c):设置背景颜色,参数为Color对象。
    • setLayout(LayoutManager mgr):设置布局管理器
  • Panel的构造方法为:

    • Panel():使用默认的FlowLayout类布局管理器初始化。
    • Panel(LayoutManager layout):使用指定的布局管理器初始化。

【演示】

 package com.wang.gui.awt;
 ​
 import java.awt.*;
 import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
 ​
 /**
  * @Author wangjin
  * @Date 2022/04/21 20:30
  * @Description
  */
 ​
 //Panel 可以看成是一个空间,但是不能单独存在
 public class PanelTest {
     public static void main(String[] args) {
 ​
         Frame frame = new Frame();
         Panel panel = new Panel();
 ​
         //设置布局
         frame.setLayout(null);
 ​
         //设置位置和大小
         frame.setBounds(300,300,500,500);
         //设置背景颜色
         frame.setBackground(new Color(83, 218, 84));
 ​
         //panel 设置坐标,相对于Frame
         panel.setBounds(50,50,400,400);
         panel.setBackground(new Color(232, 78, 78));
 ​
         //frame.add(panel)
         frame.add(panel);
 ​
         //设置可见性
         frame.setVisible(true);
 ​
         //监听事件解决窗口关闭问题
         //监听窗口关闭事件 System.exit(0)
         //适配器模式
         frame.addWindowListener(new WindowAdapter() {
             //窗口点击关闭时需要做的事情
             @Override
             public void windowClosing(WindowEvent e) {
                 //结束程序
                 System.exit(0);
             }
         });
 ​
     }
 }

结果如下:

image-20220421205100052

三、布局管理器

  • Java语言中,提供了布局管理器类的对象可以管理

    • 管理Component在Container中的布局,不必直接设置Component的位置和大小。
    • 每个Container都有一个布局管理器对象,当容器需要对某个组件进行定位或判断其大小尺寸时,就会调用其对应的布局管理器,调用Container的setLayout()方法改变其布局管理器对象。
  • AWT提供了5中布局管理器:

    • FlowLayout
    • BorderLayout
    • GridLayout
    • CardLayout
    • GridBagLayout

3.1 第一种布局管理器——FlowLayout

  • FlowLayout时Panel类的默认布局管理器。

    • FlowLayout布局管理器对组件逐行定位,行内从左到右,一行排满后换行。
    • 不改变组件的大小,按组件原有尺寸显示组件,可设置不同组件间距、行距以及对齐方式。
  • FlowLayout布局管理器默认的对齐方式是居中。

  • FlowLayout的构造方法

    • new FlowLayout(FlowLayout.RIGHT,20,40):右对齐,组件之间水平间距20个像素,垂直间距40个像素。
    • new FlowLayout(FlowLayout.LEFT):左对齐,水平和垂直间距为缺省值(5)。
    • new FlowLayout():使用默认的居中对齐方式,水平和垂直间距为缺省值(5)。

【演示】

 package com.kuang;
 ​
 import java.awt.*;
 ​
 public class TestFlowLayout {
   public static void main(String[] args) {
     Frame frame = new Frame("FlowLayout");
     
     //使用Button类创建按钮
     //按钮类的其中一个构造方法:Button(String label) label为按钮显示的文本
     Button button1 = new Button("button1");
     Button button2 = new Button("button2");
     Button button3 = new Button("button3");
     
     // setLayout方法的定义:public void setLayout(LayoutManager mgr)
     // 使用流式布局管理器 - FlowLayout
     //默认水平居中
     frame.setLayout(new FlowLayout());
     
     //布局时使用FlowLayout.LEFT常量,将按钮设置为左对齐
     // frame.setLayout(new FlowLayout(FlowLayout.LEFT));
     
     //布局时使用FlowLayout.RIGHT常量,就将按钮设置为右对齐
     // frame.setLayout(new FlowLayout(FlowLayout.RIGHT));
     
     frame.setSize(200,200);
     
     frame.add(button1); // 把创建出来的按钮放置到Frame窗体中
     frame.add(button2); // 这里并没有设置按钮的大小与位置
     frame.add(button3); // 设置按钮的大小与位置都是由布局管理器来做的
     
     frame.setVisible(true);
   }
 }

运行结果:

image-20220421220532362

3.2 第二种布局管理器——BorderLayout

  • BorderLayout时Frame类的默认布局管理器

  • BorderLayout将整个容器的布局划分成:

    • 东(EAST)
    • 西(WEST)
    • 南(SOUTH)
    • 北(NORTH)
    • 中(CENTER)五个区域,组件只能被添加到指定的区域。
  • 如不指定组件的加入部位,则默认加入到CENTER区。

  • 每个区域只能加入一个组件,如加入多个,则先前加入的会被覆盖。

  • BorderLayout型布局管理器尺寸缩放原则:

    • 北、南两个区域在水平方向缩放。
    • 东、西两个区域在垂直方向缩放。
    • 中部可在两个方向上缩放。
 package com.kuang;
 ​
 import java.awt.*;
 ​
 public class BorderLayoutTest{
   public static void main(String[] args) {
     Frame frame = new Frame("BorderLayoutTest");
     
     Button east = new Button("East");
     Button west = new Button("West");
     Button south = new Button("South");
     Button north = new Button("North");
     Button center = new Button("Center");
     
     //把按钮放置到Frame窗体时按照东西南北中五个方向排列好,推荐使用这种方式去排列窗体元素
     //这样容易检查出错误 因为这样写如果写错了编译器会提示出错
     
     frame.add(east,BorderLayout.EAST);
     frame.add(west,BorderLayout.WEST);
     frame.add(south,BorderLayout.SOUTH);
     frame.add(north,BorderLayout.NORTH);
     frame.add(center,BorderLayout.CENTER);
     
     //也可以使用这样的方式排列按钮,在把按钮放置到Frame窗体时使用方向定位的字符串指定按钮的放置位置
    //这种使用方向定位的字符串指定按钮的放置方式不推荐使用 一旦写错了方向字符串就不好检查出来
    //因为即使是写错了仍然可以编译通过
       /*
     frame.add(east,"EAST");
     frame.add(west,"West");
     frame.add(south,"South");
     frame.add(north,"North");
     frame.add(center,"Center");
     */
     
     frame.setSize(200,200);
     frame.setVisible(true);
   }
 }

运行结果:

image-20220421221511966

3.3 第三种布局管理器——GridLayout(表格布局管理器)

  • GridLayout型布局管理器将空间划分成规则的矩形网格,每个单元格区域大小相等。组件被添加到每个单元格中,先从左到右填满一行后换行,再从上到下。

  • 在GridLayout构造方法中指定分割的行数和列数:

    • 如:GridLayout(3,4);

【演示】

 package com.kuang;
 ​
 import java.awt.*;
 ​
 public class GridLayoutTest {
   public static void main(String[] args) {
     Frame frame = new Frame("GridLayoutTest");
     
     Button btn1 = new Button("btn1");
     Button btn2 = new Button("btn2");
     Button btn3 = new Button("btn3");
     Button btn4 = new Button("btn4");
     Button btn5 = new Button("btn5");
     Button btn6 = new Button("bnt6");
     
     // 把布局划分成3行2列的表格布局形式
     frame.setLayout(new GridLayout(3,2));
     
     frame.add(btn1);
     frame.add(btn2);
     frame.add(btn3);
     frame.add(btn4);
     frame.add(btn5);
     frame.add(btn6);
     
     // Frame.pack()是JAVA语言的一个函数
     // 这个函数的作用就是根据窗口里面的布局及组件的preferredSize来确定frame的最佳大小。
     frame.pack();
     frame.setVisible(true);
   }
 }

运行结果:

image-20220421222215800

3.4 布局练习

image-20220420205215778

这几种布局管理器可以设置在Frame里面,也可以设置在Panel里面,而Panel本身也可以加入到Frame里面,因此通过Frame与Panel的嵌套就可以实现比较复杂的布局;

【演示】

 package com.kuang;
 ​
 import java.awt.*;
 ​
 public class TestTenButtons {
   public static void main(String[] args) {
     //对显示窗体进行设置
     Frame frame = new Frame("布局管理器的嵌套使用");
     
     //把整个窗体分成2行1列的表格布局
     frame.setLayout(new GridLayout(2,1));
     
     frame.setLocation(300,400);
     frame.setSize(400,300);
     frame.setVisible(true);
     frame.setBackground(new Color(204,204,255));
     
     //对Panel进行布局的设置
     Panel p1 = new Panel(new BorderLayout());
     //p2使用2行1列的表格布局
     Panel p2 = new Panel(new GridLayout(2,1));
     Panel p3 = new Panel(new BorderLayout());
     //p4使用2行2列的表格布局
     Panel p4 = new Panel(new GridLayout(2,2));
     
     //这里主要是把按钮元素加入到Panel里面
     p1.add(new Button("East-1"),BorderLayout.EAST);
     p1.add(new Button("West-1"),BorderLayout.WEST);
     p2.add(new Button("p2-btn-1"));
     p2.add(new Button("p2-btn-2"));
     
     //p1里面嵌套p2,把p2里面的按钮作为p的中间部分装入到p1里面
     //把p2作为元素加入到p1里面
     p1.add(p2,BorderLayout.CENTER);
     
     p3.add(new Button("East-2"),BorderLayout.EAST);
     p3.add(new Button("West-2"),BorderLayout.WEST);
     for(int i=0;i<4;i++){
       p4.add(new Button("p4-btn-"+i));
     }
     
     //p3里面嵌套p4,把p4里面的按钮作为p的中间部分装入到p3里面
     p3.add(p4,BorderLayout.CENTER);
     
     //把Panel装入Frame里面,以便于在Frame窗体中显示出来
     frame.add(p1);
     frame.add(p3);
     
     //事件监听,实现窗口关闭
     frame.addWindowListener(new WindowAdapter() {
             @Override
             public void windowClosing(WindowEvent e) {
                 System.exit(0);
             }
         });
   }
 }

运行结果 :

image-20220421224639750

四、布局管理器总结

  • Frame是一个顶级窗口,Frame的默认布局管理器为BorderLayout。

  • Panel无法单独显示,必须添加到某个容器中。

    • Panel的默认布局管理器为FlowLayout
  • 布局管理器:

    • FlowLayout -- 流式布局管理器
    • BorderLayout -- 东西南北中布局管理器
    • GridLayout -- 表格布局管理器
  • 当把Panel作为一个组件添加到某个容器中后,该Panel仍然可以有自己的布局管理器。

  • 使用布局管理器时,布局管理器负责各个组件的大小和位置,因此用户无法在这种情况下设置组件的大小和位置属性,如果试图使用Java语言的setLocation()setSize()setBounds()等方法,则会被布局管理器覆盖。

  • 如果用户确实需要亲自设置组件大小或位置,则应取消该容器的布局管理器,方法为:

    • setLayout(null);

五、事件监听

image-20220420205646908

【测试代码一】

 package com.kuang;
 ​
 import java.awt.*;
 import java.awt.event.*;
 ​
 public class ActionEventTest{
   public static void main(String[] args) {
     Frame frame = new Frame("TestActionEvent");
     
     Button button = new Button("Press Me");
     // 创建一个监听对象
     MyActionListener listener = new MyActionListener();
     
     // 把监听加入到按钮里面,监听按钮的动作,
     // 当按钮触发打击事件时,就会返回一个监听对象e,然后会自动执行actionPerformed方法
     button.addActionListener(listener);
     
     frame.add(button, BorderLayout.CENTER);
     frame.pack();
     
     addWindowClosingEvent(frame);
     
     frame.setVisible(true);
   }
   
   //点击窗体上的关闭按钮关闭窗体
   private static void addWindowClosingEvent(Frame frame){
     frame.addWindowListener(new WindowAdapter() {
       @Override
       public void windowClosing(WindowEvent e) {
         System.exit(0);
       }
     });
   }
 }
 ​
 // 自定义Monitor(监听)类实现事件监听接口ActionListener
 // 一个类要想成为监听类,那么必须实现ActionListener接口
 class MyActionListener implements ActionListener{
   
   //重写ActionListener接口里面的actionPerformed(ActionEvent e)方法
   @Override
   public void actionPerformed(ActionEvent e) {
     System.out.println("A Button has been Pressed");
   }
 }

image-20220422131454844

点击窗口Press Me按钮,触发事件,点击窗口关闭按钮可以实现窗口的关闭。

image-20220422131719337

【测试代码二】

多个按钮共用同一个监听事件

package com.kuang;

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

public class ActionEventTest02 {
  public static void main(String[] args) {
    Frame frame = new Frame("ActionEventTest");
    Button btn1 = new Button("start");
    Button btn2 = new Button("stop");
    
    //创建监听对象
    MyMonitor monitor = new MyMonitor();
    
    //一个监听对象同时监听两个按钮的动作
    btn1.addActionListener(monitor);
    btn2.addActionListener(monitor);
    
    //可以显示设置btn2的执行单击命令后的返回信息,如果不显示定义,则会返回默认信息
    //可以多个按钮只写一个事件监听类
    btn2.setActionCommand("GameOver");
    frame.add(btn1,BorderLayout.NORTH);
    frame.add(btn2,BorderLayout.CENTER);
    
    frame.pack();
    frame.setVisible(true);
    
  }
}

class MyMonitor implements ActionListener{
  @Override
  public void actionPerformed(ActionEvent e) {
    //使用返回的监听对象e调用getActionCommand()方法获取两个按钮执行单击命令后的返回信息,根据返回信息的不同区分当前操作的是哪一个按钮
    //btn1没有使用setActionCommand()方法设置,则btn1返回的信息就是按钮上显示的文本
    System.out.println("a button has been pressed,"+"the relative info is:\n"
                         + e.getActionCommand());
  }
}

image-20220422133501052

分别点击start按钮和stop按钮出发监听事件,点击关闭窗口按钮实现窗口的关闭。

image-20220422133545054

六、TexField事件监听

  • TexField对象可能发生Action(光标在文本框内敲回车)事件。与该事件对应的事件类是 java.awt.event.ActionEvent

  • 用来处理 ActionEvent 事件是实现了 java.awt.event.ActionListener 接口的类的对象。ActionListener接口定义有方法:

    • public void actionPerformed(ActionEvent e)
  • 实现该接口的类要在该方法中添加处理该事件(Action)的语句。

  • 实现 addActionListener(ActionListener I)方法为TexField对象注册一个ActionListener对象,当TexField对象发生Action事件时,会生成一个ActionEvent对象,该对象作为参数传递给ActionListener对象的actionPerformed方法在方法中可以获取该对象的信息,并做相应的处理。

package com.kuang;

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

public class TextFieldTest{
  public static void main(String[] args) {
    new MyFrameTextField();
  }
}

class MyFrameTextField extends Frame{
  MyFrameTextField(){
    TextField textField = new TextField();
    add(textField);
    textField.addActionListener(new MyMonitor2());
    
    //setEchoChar()方法:设置文本框输入时显示的字符,这里设置为*,
    //这样输入任何内容就都以*显示出来,不过打印出来时依然可以看到输入的内容
    textField.setEchoChar('*');
    setVisible(true);
    pack();
  }
}

class MyMonitor2 implements ActionListener{
  
  //接口里面的所有方法都是public(公共的)
  //从API文档复制void actionPerformed(ActionEvent e)时,要在void前面加上public
    @Override
    public void actionPerformed(ActionEvent e) {
    //事件的相关信息都封装在了对象e里面,通过对象e的相关方法可以获取事件的相关信息
      
    //getSource()方法:拿到事件源,获得一些资源,返回一个对象
    //注意:拿到这个事件源时,把它当作TextField的父类来对待
      
    //getSource()方法的定义是:“public Object getSource()”返回值是一个Object对象
    //所以要强制转换成TextField类型的对象
    //在一个类里面想访问另一个类的事件源对象可以通过getSource()方法
    TextField textField = (TextField) e.getSource();
      
    // textField.getText()是取得文本框里面的内容
    System.out.println(textField.getText());
      
    // 把文本框里面的内容清空
    textField.setText("");
  }
}

image-20220422140401414

image-20220422140434674

image-20220422140509145

【使用TextField类实现简单的计算器】

package com.kuang2;

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

public class TestMath {
  public static void main(String[] args) {
    new Calculator();
  }
}

//主要是完成计算器元素的布局
class Calculator extends Frame{
  Calculator(){
    //创建3个文本框,并指定其初始大小分别为10个字符和15个字符的大小 
    //使用的是TextField类的另外一种构造方法:public TextField(int columns)
    TextField num1 = new TextField(10);
    TextField num2 = new TextField(10);
    TextField num3 = new TextField(15);
    
    //创建等号按钮
    Button btnEqual = new Button("=");
    
    //给等号按钮加上监听,让点击按钮后有响应事件发生
    btnEqual.addActionListener(
      new MyMonitor(num1, num2, num3)
    );
    
    //“+”是一个静态文本,所以使用Label类创建一个静态文本对象
    Label lblPlus = new Label("+");
    
    //把Frame默认的BorderLayout布局改成FlowLayout布局
    setLayout(new FlowLayout());
    
    add(num1);
    add(lblPlus);
    add(num2);
    add(btnEqual);
    add(num3);
    pack();
    setVisible(true);

  }
}

class MyMonitor implements ActionListener{
  //为了使对按钮的监听能够对文本框也起作用,在自定义类MyMonitor里面定义三个TextField类型的对象 num1,num2,num3,
  //并且定义了MyMonitor类的一个构造方法,构造方法带有三个TextField类型的参数,用于接收从TFFrame类传递过来的三个TextField类型的参数
  //然后把接收到的三个TextField类型的参数赋值给在本类中声明的三个TextField类型的参数num1,num2,num3,再在actionPerformed()方法里面处理num1,num2,num3
    TextField num1, num2, num3;
  
  	public MyMonitor(TextField num1, TextField num2, TextField num3) {
    	this.num1 = num1;
    	this.num2 = num2;
    	this.num3 = num3;
  }
  
  //事件的相关信息都封装在了对象e里面,通过对象e的相关方法就可以获取事件的相关信息
  @Override
  public void actionPerformed(ActionEvent e) {
    // num对象调用getText()方法取得显示的文本字符串
    int n1 = Integer.parseInt(num1.getText());
    int n2 = Integer.parseInt(num2.getText());
    
    //num3对象调用setText()方法设置自己的显示文本
    //字符串与任意类型的数据使用“+”连接时得到的一定是字符串,这里使用一个空字符串与int类型的数连接,就可以直接把(n1+n2)得到的int类型的数隐式地转换成字符串了,这是一种把别的基本数据类型转换成字符串的一个小技巧。
    //也可使用“String.valueOf((n1+n2))”把(n1+n2)的和转换成字符串
    num3.setText("" + (n1 + n2));
    //num3.setText(String.valueOf((n1+n2)));
    
    //计算结束后清空num1,num2文本框里面的内容
    num1.setText("");
    num2.setText("");
  }
}

image-20220422192427507

image-20220422192452610

【JAVA里面的经典用法:在一个类里面持有另外一个类的引用】

完全改造成面向对象写法

package com.kuang2;

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

public class TestMath1 {
  
  public static void main(String[] args) {
    new Calculator().launchFrame();
  }
}

//做好计算器的窗体界面
class Calculator extends Frame {
  //把设计计算器窗体的代码封装成一个方法
  TextField num1, num2, num3;
  
  public void launchFrame() {
    num1 = new TextField(10);
    num2 = new TextField(10);
    num3 = new TextField(15);
    Label lblPlus = new Label("+");
    Button btnEqual = new Button("=");
    
    btnEqual.addActionListener(new MyMonitorbtnEqual(this));
    
    setLayout(new FlowLayout());
    add(num1);
    add(lblPlus);
    add(num2);
    add(btnEqual);
    add(num3);
    pack();
    setVisible(true);
  }
}

//通过取得Calculator2类的引用,然后使用这个引用去访问Calculator2类里面的成员变量
//这种做法比上一种直接去访问Calculator2类里面的成员变量要好得多
//因为现在不需要知道 Calculator2类里面有哪些成员变量了,现在要访问Calculator2类里面的成员变量,直接使用 Calculator2类对象的引用去访问即可
//这个Calculator2类的对象好比是一个大管家, 而我告诉大管家,我要访问Calculator2类里面的那些成员变量,大管家的引用就会去帮我找,不再需要我自己去找了。
  //这种在一个类里面持有另一个类的引用的用法是一种非常典型的用法,使用获取到的引用就可以在一个类里面访问另一个类的所有成员了

class MyMonitorbtnEqual implements ActionListener {
    Calculator2 calculator2 = null;
  
    public MyMonitorbtnEqual(Calculator2 calculator2) {
      	this.calculator2 = calculator2;
    }
  
    @Override
    public void actionPerformed(ActionEvent e) {
      int n1 = Integer.parseInt(calculator2.num1.getText());
      int n2 = Integer.parseInt(calculator2.num2.getText());
      calculator2.num3.setText("" + (n1 + n2));
      
      calculator2.num1.setText("");
      calculator2.num2.setText("");
    }
}

结果:

image-20220422194305393

image-20220422194323075

七、内部类

  • 好处:

    • 可以方便的访问包装类的成员
    • 可以更清楚的组织逻辑,防止不应该被其他类访问的类进行访问
  • 何时使用:

    • 该类不允许或不需要其它类进行访问时

【内部类的使用范例】

package com.kuang2;

import java.awt.*;
import java.awt.event.*;

public class TestMath3 {
  
  public static void main(String args[]) {
    new MyMathFrame().launchFrame();
  }
}

class MyMathFrame extends Frame {
  TextField num1, num2, num3;
  
  public void launchFrame() {
    num1 = new TextField(10);
    num2 = new TextField(15);
    num3 = new TextField(15);
    Label lblPlus = new Label("+");
    Button btnEqual = new Button("=");
    btnEqual.addActionListener(new MyMonitor());
    setLayout(new FlowLayout());
    add(num1);
    add(lblPlus);
    add(num2);
    add(btnEqual);
    add(num3);
    pack();
    setVisible(true);
  }
  
  /*
	* 这个MyMonitor类是内部类,它在MyFrame类里面定义MyFrame类称为MyMonitor类的包装类
	*/
  
  /*
	* 使用内部类的好处:
	* 第一个巨大的好处就是可以畅通无阻地访问外部类(即内部类的包装类)的所有成员变量和方法
	* 如:这里在MyFrame类(外部类)定义的三个成员变量num1,num2,num3,在MyMonitor(内部类)里面就可以直接访问
	* 这相当于在创建外部类对象时内部类对象默认就拥有了一个外部类对象的引用
	*/
  private class MyMonitor implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      int n1 = Integer.parseInt(num1.getText());
      int n2 = Integer.parseInt(num2.getText());
      num3.setText("" + (n1 + n2));
      num1.setText("");
      num2.setText("");
    }
  }
}

内部类带来的巨大好处是:

  1. 可以很方便地访问外部类定义的成员变量和方法
  1. 当某一个类不需要其他类访问的时候就把这个类声明为内部类。

八、Graphics类

每个Component都有一个paint(Graphics g)用于实现绘图目的,每次重画该Component时都自动调用paint方法。

Graphics类中提供了许多绘图方法,如:

image-20220420213345732

【测试代码】

package com.kuang3;

import java.awt.*;

public class TestPaint {
  public static void main(String[] args) {
    new MyPaint().launchFrame();
    //在main()方法里面并没有显示调用paint(Graphics g)方法,可是当创建出Frame窗体后却可以看到Frame窗体上画出了圆和矩形,这是因为paint()方法是一个比较特殊的方法
    //在创建Frame窗体时会自动隐式调用,当我们把Frame窗体最小化再次打开时,会再次调用paint()方法重新把圆和矩形在Frame窗体上画出来,即每次需要重画Frame窗体的时候就会自动调用paint()方法
  }
}

class MyPaint extends Frame{
  public void launchFrame(){
    setBounds(200,200,600,500);
    setVisible(true);
  }
  
  public void paint(Graphics g){
    //paint(Graphics g)方法有一个Graphics类型的参数g
    //可以把这个g当作是一个画家,这个画家手里拿着一只画笔,我们通过设置画笔的颜色与形状来画出我们想要的各种各样的图像
    
    /*设置画笔的颜色*/
    g.setColor(Color.red);
    g.drawOval(100, 100, 100, 100);//画一个红色空心圆
    g.fillOval(100, 300, 100, 100);//画一个红色实心圆
   
    g.setColor(Color.green);
    g.fillRect(300,300,100,100);//画一个绿色实心圆
    
    //这下面的两行代码是为了写程序的良好编程习惯而写的
    //前面设置了画笔的颜色,现在就应该把画笔的初始颜色恢复过来
    //就相当于是画家用完画笔之后把画笔上的颜色清理掉一样
    Color c = g.getColor();
    g.setColor(c);
    
  }
}

image-20220422204100866

九、鼠标事件适配器

  • 抽象类java.awt.event.MouseAdapter实现了MouseListener接口,可以使用其子类作为MouseEvent的监听器,只要重写其相应的方法即可。
  • 对于其他的监听器,也有对应的适配器。
  • 适用适配器可以避免监听器定义没有必要的空方法。

【测试代码:实现鼠标画点】

package com.wang.gui.awt;

import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Iterator;

//鼠标监听事件
public class MouseListenerTest {
    public static void main(String[] args) {
        new MyFrame1("画图");

    }
}

//自己的类
class MyFrame1 extends Frame {
    //画画需要画笔,需要监听鼠标当前的位置,需要集合来存储这个点
    ArrayList points;


    public MyFrame1(String title) {
        super(title);
        setBounds(200, 200, 400, 300);

        //存鼠标点击的点
        points = new ArrayList<>();

        setVisible(true);

        //鼠标监听器,针对这个窗口
        this.addMouseListener(new MyMouseListener());

    }

    @Override
    public void paint(Graphics g) {
        //画画,监听鼠标事件
        Iterator iterator = points.iterator();
        while (iterator.hasNext()) {
            Point point = (Point) iterator.next();
            g.setColor(Color.BLUE);
            g.fillOval(point.x, point.y, 10, 10);
        }

    }

    //添加一个点到界面上
    public void addPoint(Point point) {
        points.add(point);

    }


    //适配器模式
    private class MyMouseListener extends MouseAdapter {
        //鼠标三种情况:按下、弹起、按住不放

        @Override
        public void mousePressed(MouseEvent e) {
            MyFrame1 myFrame1 = (MyFrame1) e.getSource();

            //鼠标点击,就会在界面上出现一个点
            //这个点就是鼠标的点
            myFrame1.addPoint(new Point(e.getX(), e.getY()));

            //每次点击鼠标都需要重画一遍,执行一次刷新一遍
            myFrame1.repaint();
        }
    }
}

image-20220422211620916

分析:

image-20220422212033572

十、窗口事件

  • 窗口事件所对应的事件类为WindowEvent,所对应的事件监听接口为WindowListener。

  • WindowListener定义的方法有:

    • public void windowOpened(WindowEvent e)
    • public void windowClosing(WindowEvent e)
    • public void windowClosed(WindowEvent e)
    • public void windowIconified(WindowEvent e)
    • public void windowDeiconified(WindowEvent e)
    • public void windowActivated(WindowEvent e)
    • public void windowDeactivated(WindowEvent e)
  • 与WindowListener对应的适配器为WindowAdapter。

package com.kuang3;

import java.awt.*;
import java.awt.event.*;

public class TestWindowClose{
  public static void main(String args[]){
    new WindowFrame("关闭WindowFrame");
  }
}

class WindowFrame extends Frame{
  public WindowFrame(String s){
    super(s);
    setBounds(200,200,400,300);
    setLayout(null);
    setBackground(new Color(204,204,255));
    setVisible(true);
    this.addWindowListener(new WindowMonitor());
    /*监听本窗体的动作,把所有的动作信息封装成一个对象传递到监听类里面*/
    
    this.addWindowListener(
    /*在一个方法里面定义一个类,这个类称为局部类,也叫匿名的内部类,这里的{……代码……}里面的代码很像一个类的类体,只不过这个类没有名字,所以叫匿名类。
		在这里是把这个匿名类当成WindowAdapter类来使用,这样写的本质意义是相当于这个匿名类从WindowAdapter类继承,现在new了一个匿名类的对象出来,然后把这个对象当成WindowAdapter来使用,这个匿名类出了()就没有人认识了*/
      new WindowAdapter(){
        public void windowClosing(WindowEvent e){
          setVisible(false);
          System.exit(-1);
        }
      }
    );
  }
  
  /*这里也是将监听类定义为内部类*/
  class WindowMonitor extends WindowAdapter{
    /*WindowAdapter(Window适配器)类实现了WindowListener监听接口,重写了WindowListener接口里面的所有方法。
    如果直接使用自定义WindowMonitor类去实现WindowListener接口,就得要重写WindowListener接口里面的所有方法,但现在只需要用到这些方法里面的其中一个方法,所以采用继承实现WindowListener监听接口的一个子类并重写这个子类里面需要用到的方法即可。
		这种做法比直接实现WindowListener监听接口要重写很多个用不到的方法要简洁方便得多。
		*/
    
    /*重写需要用到的windowClosing(WindowEvent e)方法*/
    public void windowClosing(WindowEvent e){
      setVisible(false);/*将窗体设置为不显示,即可实现窗体关闭*/
      System.exit(0);/*正常退出*/
    }
  }
}

十一、键盘响应事件

【键盘响应事件——KeyEvent】

package com.kuang3;

import java.awt.*;
import java.awt.event.*;

public class TestKeyEvent{
  public static void main(String args[]){
    new KeyFrame("键盘响应事件");
  }
}

class KeyFrame extends Frame{
  public KeyFrame(String s){
    super(s);
    setBounds(200,200,400,300);
    setLayout(null);
    setVisible(true);
    addKeyListener(new KeyMonitor());
  }
  /*把自定义的键盘监听类定义为内部类,这个监听类从键盘适配器KeyAdapter类继承。
	  从KeyAdapter类继承是为了简洁方便,只需要重写用到的方法即可。这种做法比直接实现KeyListener接口要简单方便,如果直接实现KeyListener接口,就要把KeyListener接口里面的所有方法重写一遍。但真正用到的只有一个方法,这样重写其他的方法但又用不到难免会做无用功*/
  class KeyMonitor extends KeyAdapter{
    public void keyPressed(KeyEvent e){
      int keycode = e.getKeyCode();
      /*使用getKeyCode()方法获取按键的虚拟码*/
      
      /*如果获取到的键的虚拟码等于up键的虚拟码,则表示当前按下的键是up键。
				KeyEvent.VK_UP表示取得up键的虚拟码
				键盘中的每一个键都对应一个虚拟码,这些虚拟码在KeyEvent类里被定义为静态常量,所以可以使用“类名.静态常量名”的形式访问得到这些静态常量*/
      if(keycode == KeyEvent.VK_UP){
        System.out.println("你按的是up键");
      }
    }
  }
}
/*
	键盘的处理事件是这样的:每一个键都对应一个虚拟码,当按下某一个键时,系统就会去找这个键对应的虚拟码,以此来确定当前按下的是哪个键
*/

Swing

Swing是GUI(图形用户界面)开发工具包,想深入学习的可查阅有关资料或图书,比如《Java Swing图形界面开发与案例详解》——清华大学出版社。

早期的AWT(抽象窗口工具包)组件开发的图形用户界面,要依赖本地系统,当把AWT组件开发的应用程序移植到其他平台的系统上运行时,不能保证其外观风格,因此AWT是依赖于本地系统平台的。而使用Swing开发的Java应用程序,其界面是不受本地系统平台限制的,即Swing开发的Java应用程序移植到其他系统平台上时,其界面外观是不会改变的。

但要注意的是,虽然Swing提供的组件可以方便开发Java应用程序,但是Swing并不能取代AWT,在开发Swing程序时通常要借助与AWT的一些对象来共同完成应用程序的设计。

一、常用窗体

Swing窗体是Swing的一个组件,同时也是创建图形化用户界面的容器,可以将其它组件放置在窗体容器中。

1.1 JFrame框架窗体

JFrame窗体是一个容器,在Swing开发中经常要用到,它是Swing程序中各个组件的载体。

语法格式如下:

JFrame jf = new JFrame(title);

当然,在开发中更常用的方式是通过继承java.swing.JFrame类创建一个窗体,可通过this关键字调用其方法。

在JFrame对象创建完成后,需要调用getContentPane()方法将窗体转换为容器,然后在容器中添加组件或设置布局管理器,通常这个容器用来包含和显示组件。如果需要将组件添加至容器,可以使用来自Container类的add()方法进行设置。

【下面举一个JFrame窗体的例子】

package com.kuang4;

import javax.swing.JFrame;
import javax.swing.WindowConstants;

public class JFrameDemo {
  public void CreateJFrame() {
    // 实例化一个JFrame对象
    JFrame jf = new JFrame("这是一个JFrame窗体");
    // 设置窗体可视
    jf.setVisible(true);
    // 设置窗体大小
    jf.setSize(100100500, 600);
    // 设置窗体关闭方式
    jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
  }
  
  public static void main(String[] args) {
    new JFrameDemo().CreateJFrame(); // 调用CreateJFrame()方法
  }
  
}

结果:

image-20220423125719241

这就是一个500*600的窗体,用的是setSize()方法;

标题为“这是一个JFrame窗体”,在实例化对象时就可以定义;

窗体关闭方式见窗体右上角为“EXIT_ON_CLOSE”;

窗体可视setVisible()方法中的参数为“false”或不写setVisible()方法时,此窗体不可见。

常用的窗体关闭方式有四种:

  • DO_NOTHING_ON_CLOSE :什么也不做就将窗体关闭;
  • DISPOSE_ON_CLOSE”:任何注册监听程序对象后会自动隐藏并释放窗体;
  • HIDE_ON_CLOSE”: 隐藏窗口的默认窗口关闭;
  • EXIT_ON_CLOSE:退出应用程序默认窗口关闭。

【下面再举一个用继承JFrame的方式编写的代码,并加入Container容器及JLabel标签(后面会提到),来看一下具体的流程。】

package com.kuang4;

import java.awt.Color;
import java.awt.Container;

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

public class JFrameDemo2 extends JFrame{
  
  public void init() {
    // 可视化
    this.setVisible(true);
    // 大小
    this.setSize(500, 350);
    // 标题
    this.setTitle("王车车");
    // 关闭方式
    this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    
    // 创建一个JLabel标签
    JLabel jl = new JLabel("欢迎来到我的世界!");
    
    // 使标签文字居中
    jl.setHorizontalAlignment(SwingConstants.CENTER);
    
    // 获取一个容器
    Container container = this.getContentPane();
    // 将标签添加至容器
    container.add(jl);
    // 设置容器背景颜色
    container.setBackground(Color.YELLOW);
  }
  
  public static void main(String[] args) {
    new JFrameDemo2().init();
  }
  
}

运行结果:

image-20220423131916348

这里继承了JFrame类,所以方法中实现时用this关键字即可(或直接实现,不加this)。

1.2 JDialog弹窗

JDialog窗体是Swing组件中的对话框,继承了AWT组件中的java.awt.Dialog类。功能是从一个窗体中弹出另一个窗体。

【下面来看一个实例】

package com.kuang4;

import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.WindowConstants;

// 继承JDialog类
public class JDialogDemo extends JDialog {
  
  // 实例化一个JDialog类对象,指定其父窗体、窗口标题和类型
  public JDialogDemo() {
    super(new MyJFrame(), "这是一个JDialog窗体", true);
    Container container = this.getContentPane();
    container.add(new JLabel("王车车爱学习!"));
    this.setSize(500, 350);
  }
  
  public static void main(String[] args) {
    new JDialogDemo();
  }
  
}

// 下面这部分内容包含监听器,可自行查阅资料
class MyJFrame extends JFrame {
  public MyJFrame() {
    this.setVisible(true);
    this.setSize(700, 500);
    this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    
    Container container = this.getContentPane();
    container.setLayout(null);
    
    JButton jb = new JButton("点击弹出对话框"); // 创建按钮
    jb.setBounds(30, 30, 200, 50); // 按钮位置及大小
    
    jb.addActionListener(new ActionListener() { // 监听器,用于监听点击事件
        @Override
        public void actionPerformed(ActionEvent e) {
        new JDialogDemo().setVisible(true);
      }
    });
    container.add(jb);
  }
}

当点击按钮时,触发点击事件,创建一个JDialog的实例化对象,弹出一个窗口。

image-20220423134035910

这里出现了许多之前学过的知识,比如super关键字,相当于使用了JDialog(Frame f, String title,boolean model)形式的构造方法;监听器的实现就是一个匿名内部类,之前也提到过。

二、标签组件

在Swing中显示文本或提示信息的方法是使用标签,它支持文本字符串和图标。上面我们提到的JLabel就是这里的内容。

2.1 标签

标签由JLabel类定义,可以显示一行只读文本、一个图像或带图像的文本。

JLabel类提供了许多构造方法,可查看API选择需要的使用,如显示只有文本的标签、只有图标的标签或包含文本与图标的标签等。因为上面已经出现过了,这里就不再举例了。常用语法格式如下,创建的是一个不带图标和文本的JLabel对象:

JLabel jl = new JLabel();

image-20220420220108182

2.2 图标

Swing中的图标可以放置在按钮、标签等组件上,用于描述组件的用途。图标可以用Java支持的图片文件类型进行创建,也可以使用java.awt.Graphics类提供的功能方法来创建。

在Swing中通过Icon接口来创建图标,可以在创建时给定图标的大小、颜色等特性。

注意,Icon是接口,在使用Icon接口的时候,必须实现Icon接口的三个方法:

public int getIconHeight()
public int getIconWidth()
public void paintIcon(Component arg0, Graphics arg1, int arg2, int arg3)
  
//前两个方法用于获取图片的长宽
//paintIcon()方法用于实现在指定坐标位置画图。

下面看一个用Icon接口创建图标的实例:

package com.kuang4;

import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;

import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;

public class IconDemo extends JFrame implements Icon {
  
  private int width; // 声明图标的宽
  private int height; // 声明图标的长
  
  public IconDemo() {} // 定义无参构造方法
  
  public IconDemo(int width, int height) { // 定义有参构造方法
    this.width = width;
    this.height = height;
  }
  
  @Override
  public int getIconHeight() { // 实现getIconHeight()方法
    return this.height;
  }
  
  @Override
  public int getIconWidth() { // 实现getIconWidth()方法
    return this.width;
  }
  
  @Override
  public void paintIcon(Component arg0, Graphics arg1, int arg2, int arg3)
  { // 实现paintIcon()方法
    arg1.fillOval(arg2, arg3, width, height); // 绘制一个圆形
  }
  
  public void init() { // 定义一个方法用于实现界面
    IconDemo iconDemo = new IconDemo(15, 15); // 定义图标的长和宽
    JLabel jb = new JLabel("icontest", iconDemo, SwingConstants.CENTER);
    // 设置标签上的文字在标签正中间
    
    Container container = getContentPane();
    container.add(jb);
    
    this.setVisible(true);
    this.setSize(500, 350);
    this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
  }
  
  public static void main(String[] args) {
    new IconDemo().init();
  }
  
}

运行结果如下:

image-20220423140452396

这样如果需要在窗体中使用图标,就可以用如下代码创建图标:

IconDemo iconDemo = new IconDemo(15, 15);

2.3 图片图标

Swing中的图标除了可以绘制之外,还可以使用某个特定的图片创建。利用javax.swing.ImageIcon类根据现有图片创建图标。

下面看一个实例,先在包下放一个图片(注意放置位置,不同位置路径不同),如下:

image-20220420220756983

【下面是实现的代码】

package com.kuang4;

import java.awt.Container;
import java.net.URL;

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

public class ImageIconDemo extends JFrame {
  
  public ImageIconDemo() {
    JLabel jl = new JLabel("这是一个JFrame窗体,旁边是一个图片");
    URL url = ImageIconDemo.class.getResource("tx-old.jpg"); //获得图片所在URL
    
    Icon icon = new ImageIcon(url); // 实例化Icon对象
    jl.setIcon(icon); // 为标签设置图片
    jl.setHorizontalAlignment(SwingConstants.CENTER);
    jl.setOpaque(true); // 设置标签为不透明状态
    
    Container container = getContentPane();
    container.add(jl);
    
    setVisible(true);
    setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    setSize(500, 350);
  }
  
  public static void main(String[] args) {
    new ImageIconDemo();
  }
  
}

image-20220423150316936

对于图片标签,我们经常将图片放置在标签上,用JLabel中的setIcon()方法即可,当然也可以在初始化JLabel对象时为标签指定图标,这需要获取一个Icon实例。

而getResource()方法可以获得资源文件的URL路径,这里的路径是相对于前面的那个类的,所以可将该图片与该类放在同一个文件夹下;如果不在同一个文件夹下,需通过其它方法获取路径。

三、布局管理器

Swing中,每个组件在容器中都有一个具体的位置和大小,在容器中摆放各自组件时很难判断其具体位置和大小,这里我们就要引入布局管理器了,它提供了基本的布局功能,可以有效的处理整个窗体的布局。常用的布局管理器包括流布局管理器、边界布局管理器、网格布局管理器等。

3.1 绝对布局

绝对布局在上一篇的例子中已经出现过了,是硬性指定组件在容器中的位置和大小,可以使用绝对坐标的方式来指定组件的位置。步骤如下:

  1. 使用Container.setLayout(null)方法取消布局管理器
  1. 使用Container.setBounds()方法设置每个组件的位置和大小

【举一个简单的例子】

Container container = getContentPane(); // 创建容器
JButton jb = new JButton("按钮"); // 创建按钮
jb.setBounds(10, 30, 100, 30); // 设置按钮位置和大小
container.add(jb); // 将按钮添加到容器中

setBounds()方法中,前两个参数是位置的xy坐标,后两个参数是按钮的长和宽。

3.2 流布局管理器

流布局管理器是布局管理器中最基本的布局管理器,使用FlowLayout类,像“流”一样从左到右摆放组件,直到占据了这一行的所有空间,再向下移动一行。组件在每一行的位置默认居中排列,要更改位置可自行设置。

在FlowLayout的有参构造方法中,alignment设置为0时,每一行的组件将被指定左对齐排列;当alignment被设置为2时,每一行的组件将被指定右对齐排列;而为1时是默认的居中排列。

下面举个例子,创建10个按钮并用流布局管理器排列。

package com.kuang5;

import java.awt.Container;
import java.awt.FlowLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.WindowConstants;

public class FlowLayoutDemo extends JFrame {
  
  public FlowLayoutDemo() {
    Container container = this.getContentPane();
    // 设置流布局管理器,2是右对齐,后两个参数分别为组件间的水平间隔和垂直间隔
    this.setLayout(new FlowLayout(2, 10, 10));
    
    // 循环添加按钮
    for(int i=0; i<10; i++) {
      container.add(new JButton("按钮" + i));
    }
    
    this.setSize(300, 200);
    this.setVisible(true);
    this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
  }
  
  public static void main(String[] args) {
    new FlowLayoutDemo();
  }
  
}

第一个参数为2是右对齐,每个按钮间的水平、垂直间隔都为10。后两个图分别为参数为1居中排列和参数为0左对齐。运行结果如下:

image-20220420224410202

image-20220420224417310

3.3 边界布局管理器

在不指定窗体布局时,Swing组件默认的布局管理器是边界布局管理器,使用的是BorderLayout类。在上篇例子中,一个JLabel标签占据了整个空间,实质上是默认使用了边界布局管理器。边界布局管理器还可以容器分为东、南、西、北、中五个区域,可以将组件加入这五个区域中。

【演示】

package com.kuang5;

import java.awt.BorderLayout;
import java.awt.Container;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.WindowConstants;

public class BorderLayoutDemo extends JFrame {
  private String[] border = {BorderLayout.CENTER, BorderLayout.NORTH,
                             BorderLayout.SOUTH, BorderLayout.WEST, BorderLayout.EAST}; // 此数组用于存放组件摆放位置
  private String[] button = {"中", "北", "南", "西", "东"}; // 此数组用于存放按钮名称
  
  public BorderLayoutDemo() {
    Container container = this.getContentPane();
    this.setLayout(new BorderLayout()); // 设置容器为边界布局管理器
    
    // 循环添加按钮
    for(int i=0; i<button.length ; i++) {
      container.add(border[i], new JButton(button[i])); // 左参数为设置布局,右参数为创建按钮
    }
    
    this.setVisible(true);
    this.setSize(300, 200);
    this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
  }
  
  public static void main(String[] args) {
    new BorderLayoutDemo();
  }
  
}

image-20220420234523753

3.4 网络布局管理器

网格布局管理器将容器划分为网格,组件按行按列排列,使用GridLayout类。在此布局管理器中,每个组件的大小都相同,且会填满整个网格,改变窗体大小,组件也会随之改变。

【演示】

package com.kuang5;

import java.awt.Container;
import java.awt.GridLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.WindowConstants;

class GirdLayoutDemo extends JFrame {
  public GirdLayoutDemo() {
    Container container = this.getContentPane();
    this.setLayout(new GridLayout(7, 3, 5, 5)); // 前两个参数为7行3列,后两个参数为网格间的间距
    
      for(int i=0; i<20; i++) {
        container.add(new JButton("按钮" + i));
      }
    
    	this.setVisible(true);
    	this.setSize(300, 300);
    	this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
  }
  
  public static void main(String[] args) {
    new GirdLayoutDemo();
  }
  
}

image-20220420234647723

四、面板

面板也是一个容器,可作为容器容纳其他组件,但也必须被添加到其他容器中。Swing中常用面板有JPanel面板和JScrollPane面板。

4.1 JPanel

JPanel面板可以聚集一些组件来布局。继承自java.awt.Container类。

【演示】

package com.kuang5;

import java.awt.Container;
import java.awt.GridLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;

public class JPanelDemo extends JFrame {
  
  public JPanelDemo() {
    Container container = this.getContentPane();
    container.setLayout(new GridLayout(2, 1, 10, 10)); // 整个容器为2行1列,后面的参数为间距
    
    JPanel p1 = new JPanel(new GridLayout(1, 3)); // 初始化一个面板,设置1行3列的网格布局
    JPanel p2 = new JPanel(new GridLayout(1, 2)); // 初始化一个面板,设置1行2列的网格布局
    JPanel p3 = new JPanel(new GridLayout(2, 1)); // 初始化一个面板,设置2行1列的网格布局
    JPanel p4 = new JPanel(new GridLayout(3, 2)); // 初始化一个面板,设置3行2列的网格布局
    
    p1.add(new JButton("1")); // 在JPanel面板中添加按钮
    p1.add(new JButton("1")); // 在JPanel面板中添加按钮
    p1.add(new JButton("1")); // 在JPanel面板中添加按钮
    
    p2.add(new JButton("2")); // 在JPanel面板中添加按钮
    p2.add(new JButton("2")); // 在JPanel面板中添加按钮
    
    p3.add(new JButton("3")); // 在JPanel面板中添加按钮
    p3.add(new JButton("3")); // 在JPanel面板中添加按钮
    
    p4.add(new JButton("4")); // 在JPanel面板中添加按钮
    p4.add(new JButton("4")); // 在JPanel面板中添加按钮
    p4.add(new JButton("4")); // 在JPanel面板中添加按钮
    p4.add(new JButton("4")); // 在JPanel面板中添加按钮
    p4.add(new JButton("4")); // 在JPanel面板中添加按钮
    p4.add(new JButton("4")); // 在JPanel面板中添加按钮
    
    container.add(p1); // 在容器中添加面板
    container.add(p2); // 在容器中添加面板
    container.add(p3); // 在容器中添加面板
    container.add(p4); // 在容器中添加面板
    
    this.setVisible(true);
    this.setSize(500, 350);
    this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
  }
  
  public static void main(String[] args) {
    new JPanelDemo();
  }
  
}

运行结果如下,可自行对比代码与结果理解JPanel。其中,容器的GridLayout布局设置了横纵都为10的间距,JPanel的GridLayout布局没有设置网格间距。

image-20220423153349904

4.2 JScrollPanel

若遇到一个较小的容器窗体中显示一个较大部分内容的情况,可用JScrollPane面板。这是一个带滚动条的面板,就像平时浏览网页,经常遇到的滚动条一样。

如果需要在JScrollPane面板中放置多个组件,需将这多个组件放置在JPanel面板上,然后将JPanel面板作为一个整体组件添加在JScrollPane面板上。

【演示】

package com.kuang5;

import java.awt.Container;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.WindowConstants;

public class JScrollPaneDemo extends JFrame {
  
  public JScrollPaneDemo() {
    Container container = this.getContentPane();
    
    // 创建文本区域组件
    JTextArea textArea = new JTextArea(20, 50); 
    textArea.setText("欢迎学习Java");
    
    //ScrollPanel面板
    JScrollPane sp = new JScrollPane(textArea);
    container.add(sp);
    
    this.setVisible(true);
    this.setSize(300, 150);
    this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
  }
  
  public static void main(String[] args) {
    new JScrollPaneDemo();
  }
  
}

结果:

image-20220423155506926

其中JTextArea是创建一个文本区域组件,大小为20*50,setText()方法是给该文本区域填值。

这里在new一个JScrollPane时,就将文本区域组件添加到其上。

五、按钮组件

5.1 提交按钮组件(JButton)

JButton在之前的例子中已经出现多次,是较为常用的组件,用于触发特定动作。可以在按钮上显示文本标签,还可以显示图标,如下:

package com.kuang5;

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

public class JButtonDemo extends JFrame {

    public JButtonDemo() {
        Container container = this.getContentPane();
        //将一个图片变成一个图标
        URL url = JButtonDemo.class.getResource("wj.jpg");
        Icon icon = new ImageIcon(url);

        //把这个图标放在按钮上
        JButton button = new JButton();
        button.setIcon(icon);
        button.setToolTipText("图片按钮");

        //把按钮就在容器上
        container.add(button);

        this.setVisible(true);
        this.setSize(500,300);
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

    }

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

    }
}

5.2 单选按钮组件(JRadioButton)

默认情况下,单选按钮显示一个圆形图标,通常在其旁放置一些说明性文字。当用户选中某个单选按钮后,按钮组中其它按钮将被自动取消,这时就需要按钮组(ButtonGroup)来将同组按钮放在一起,该按钮组中的按钮只能选择一个,而不在此按钮中的按钮不受影响。语法格式如下:

package com.kuang5;

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

public class JRadioButtonDemo extends JFrame {

    public JRadioButtonDemo() {
        Container container = this.getContentPane();
        //将一个图片变成图标
        URL resource = JRadioButtonDemo.class.getResource("wj.jpg");
        Icon icon = new ImageIcon(resource);

        //单选框
        JRadioButton radioButton1 = new JRadioButton("JRadioButton1");
        JRadioButton radioButton2 = new JRadioButton("JRadioButton2");
        JRadioButton radioButton3 = new JRadioButton("JRadioButton3");

        //由于单选框只能选择一个,所以进行分组,组内只能选一个
        ButtonGroup group = new ButtonGroup();
        group.add(radioButton1);
        group.add(radioButton2);
        group.add(radioButton3);

        container.add(radioButton1,BorderLayout.CENTER);
        container.add(radioButton2,BorderLayout.NORTH);
        container.add(radioButton3,BorderLayout.SOUTH);

        this.setVisible(true);
        this.setBounds(100,100,300,350);
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }

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

image-20220423163824157

5.3 复选框组件(JCheckBox)

复选框是一个方块图标,外加一段描述性文字,与单选按钮的区别就是可以多选。每一个复选框都提供“选中”与“不选中”两种状态。语法格式如下:

package com.kuang5;

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

public class JCheckBoxDemo extends JFrame {

    public JCheckBoxDemo() {
        Container container = this.getContentPane();
        //将一个图片变成图标
        URL resource = JRadioButtonDemo.class.getResource("wj.jpg");
        Icon icon = new ImageIcon(resource);

        //多选框
        JCheckBox checkBox01 = new JCheckBox("checkBox01");
        JCheckBox checkBox02 = new JCheckBox("checkBox02");

        container.add(checkBox01,BorderLayout.NORTH);
        container.add(checkBox02,BorderLayout.SOUTH);

        this.setVisible(true);
        this.setBounds(100,100,300,350);
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }

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

image-20220423164548057

六、列表组件

6.1 下拉列表(JComboBox)

下拉列表框使用JComboBox类对象来表示,如下方代码:

package com.kuang5;

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

public class JComboBoxDemo extends JFrame {

    public JComboBoxDemo() {
        Container container = this.getContentPane();
        JComboBox status = new JComboBox();

        status.addItem(null);
        status.addItem("正在热映");
        status.addItem("已下架");
        status.addItem("即将上映");

        container.add(status);

        this.setVisible(true);
        this.setSize(500,350);
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

    }

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

显示的样式如下:

截屏2022-04-23 16.56.15

6.2 列表框(JList)

列表框只是在窗体上占据固定的大小,如果要使列表框具有滚动效果,可以将列表框放入滚动面板中。使用数组初始化列表框的参数如下。

package com.kuang5;

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

public class JListDemo extends JFrame {

    public JListDemo() {
        Container container = this.getContentPane();

        //生成列表的内容
        //使用数组初始化列表框的参数
        String[] contents = {"1", "2", "3"};
        //列表中需要放入内容
        JList jlist = new JList(contents);

        container.add(jlist);
        
        this.setVisible(true);
        this.setSize(500, 350);
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

    }

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

image-20220423170523668

将Vector类型的数据作为初始化JList的参数如下。

package com.kuang5;

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

public class JListDemo extends JFrame {

    public JListDemo() {
        Container container = this.getContentPane();

        //生成列表的内容
        //使用数组初始化列表框的参数
        //String[] contents = {"1", "2", "3"};

        //将Vector类型的数据作为初始化JList的参数
        Vector contents = new Vector();

        //列表中需要放入内容
        JList jlist = new JList(contents);

        contents.add("zhangsan");
        contents.add("lisi");
        contents.add("wangwu");

        container.add(jlist);

        this.setVisible(true);
        this.setSize(500, 350);
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

    }

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

image-20220423170905896

应用场景:

  • 下拉框:选择地区或者一些单个选项。
  • 列表:用来展示信息,一般是动态扩容的。

七、文本组件

7.1 文本框(JTexField)

文本框用来显示或编辑一个单行文本,语法格式如下:

JTextField jtext = new JTextField("aaa"); // 创建一个文本框,值为aaa
JTextField jtext2 = new JTextField("aaa", 20); // 创建一个长度为20的文本框,值为aaa
jtext.setText(""); // 将文本框置空

其余构造方法可参考API或源码。

package com.wang.gui.swing;

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

public class TextDemo extends JFrame {

    public TextDemo() {
        Container container = this.getContentPane();

        TextField textField = new TextField("hello");
        //文本框默认显示文字,columns为最多可以放的字符数
        TextField textField2 = new TextField("world",20);

        container.add(textField,BorderLayout.NORTH);
        container.add(textField2,BorderLayout.SOUTH);

        this.setVisible(true);
        this.setSize(500, 350);
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

    }

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

image-20220423183540226

7.2 密码框(JPasswordField)

密码框与文本框的定义与用法类似,但会使用户输入的字符串以某种符号进行加密。如下方代码:

JPasswordField jpassword = new JPasswordField();
jpassword.setEchoChar('#'); // 设置回显符号

代码演示

package com.wang.gui.swing;

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

public class JPasswordFieldDemo extends JFrame {

    public JPasswordFieldDemo() {
        Container container = this.getContentPane();

        JPasswordField passwordField = new JPasswordField();
        passwordField.setEchoChar('*');

        container.add(passwordField);

        this.setVisible(true);
        this.setSize(500, 350);
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

    }

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

image-20220423193426844

7.3 文本域(JTexField)

文本域组件在上面的代码中已经出现了,如下方代码所示:

JTextArea textarea = new JTextArea(20, 50); // 创建文本区域组件
textarea.setText("欢迎来到西部开源学Java");

代码演示

package com.wang.gui.swing;

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

/**
 * @Author wangjin
 * @Date 2022/04/23 15:36
 * @Description
 */
public class JScrollPanelDemo extends JFrame {

    public JScrollPanelDemo() {
        Container container = this.getContentPane();

        //文本域
        JTextArea textArea = new JTextArea(20, 50);
        textArea.setText("欢迎学习java");

        //ScrollPanel面板
        JScrollPane scrollPane = new JScrollPane(textArea);
        container.add(scrollPane);

        this.setVisible(true);
        this.setBounds(100,100,300,350);
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }

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

image-20220423194140054

对GUI编程就讲到这里,授人以鱼不如授人以渔,经过这一小段的学习已经能掌握看方法和源码学习的能力了,之后有一些小游戏专题来巩固JavaSE阶段的学习。