【Java】学校还在教Swing,一些简单功能实现的总结——显示窗体,绘制2D图形,监听点击事件

咱们学校的Java教学中需要掌握一些Swing的基础操作,虽然Swing已经没人用了。。。
但既然要学,就要学好,毕竟技多不压身,还能拿个好成绩。
本文总结自《Java核心技术 卷Ⅰ》第十章,实现了一些简单的基础操作——显示窗体,改变窗体属性,画图,对用户点击进行响应等等。
源码都给上了,如果你的复习时间紧张,不妨简单看看。

用户界面工具包历史

抽象窗口工具包 (Abstract Window Toolkit, AWT) ->Swing用户界面库 ->JavaFX。

AWT
将处理用户界面元素的任务委托给目标平台上的原生GUI工具包。

  • 优点:所得到的程序可以在任何平台上运行,并且有目标平台的观感。
  • 缺点:不同平台的一些用户界面元素存在差别、有些图形环境的用户组键集合匮乏,不同平台存在不同bug。

Swing
底层窗口系统只需显示一个空白窗口,将用户界面元素绘制在空白的窗口上。

  • 优点:在不同平台上具有相同外观和行为;是标准库的一部分。
  • 缺点:Swing必须绘制用户界面的每一个像素——速度慢;与其他很多原生部件相比,不够美观。

JavaFx:

  • 优点:为实现动画与华丽效果做出了优化;JavaFx 2.0具有了Java API。
  • 缺点:从Java 11开始,JavaFX不与Java一起打包。

显示窗体

  • 什么是窗体(frame):顶层窗口(没有包含在其他窗口的窗口)。
  • 如何显示与描述:使用Swing中的JFrame类(拓展于原先的AWT库的Frame类)。

JFrame作为其他组件的画布,它的修饰部件是由用户的窗口系统绘制的,而不是Swing绘制。

创建一个最简单的可见窗体:

package simpleFrame;

import java.awt.*;
import javax.swing.*; //java的拓展包

public class SimpleFrameTest 
{
	public static void main(String[] args)
	{
		EventQueue.invokeLater(()-> // 事件分派线程(event dispatch thread)
			{
				var frame = new SimpleFrame(); // 构造一个Simple Frame对象
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//定义用户点击关闭时退出。
				frame.setVisible(true);//将窗体显示出来
			});
	} //主程序已经结束,事件分派线程仍会保持激活状态

}

class SimpleFrame extends JFrame //拓展于JFrame
{
	private static final int DEFAULT_WIDTH = 300;
	private static final int DEFAULT_HEIGHT = 200;
	
	public SimpleFrame() 
	{
		setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
	}
	
}

代码解释:

  • javax.swing.* : javax表示这是一个java的拓展包,而非核心包。
  • EventQueue.invokeLater(()->{}); : 事件分派线程(event dispatch thread),这是一个控制线程,将鼠标点击和按键等事件传递给用户接口组件。虽然可以只在主线程中完成Swing组件的初始化,但越来越复杂的Swing组键可能导致发生未知错误。
  • frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); : 定义用户点击关闭时的响应动作,这里是退出。
  • frame.setVisible(true); : 窗体在构造时是不可见的,这样可以让程序员在窗体显示之前完成对窗体的设计。调用setVisible函数使得窗体可见。
  • setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); :在构造函数中设置创造出的窗体的大小,不设置的话默认窗口大小为 0x0。
  • 注意 : 我们是在事件分派线程里面执行程序,即使主程序已经结束,事件分派线程仍会保持激活状态,除非用户关闭窗体或者调用System.exit方法来终止。

窗体外观的改变:

JFrame类本身只定义了若干个改变窗体外观的方法,但由于其超类众多,因此拥有非常丰富的设置窗体属性的方法。以下列出几项:

  • setLocation(x, y) 来自于超类Component,可以使窗体显示在水平向右x像素,垂直向下y像素的位置。
  • setBounds(x, y, width, height) 来自于超类Component,调整窗体的出现位置与大小。
  • getTitle(),setTitle(String title) 来自于超类Frame,获取/设置标题。
  • setLocationByPlatform(boolean b) 来自于超类Window,设置为true后,窗体位置由平台选择。
  • setResizable(boolean b) 来自于超类Frame,设置为false后,用户不允许调节窗口大小。
  • Image getIconImage(), setIconImage(Image image) 来自于超类Frame,获取/设置窗体的iconImage,系统可能将这个图标显示在任何位置。

在组件中显示信息——2D图形

虽然可以将内容直接绘制在窗体中,但一般不这么做。正确的做法是:将一个组件添加到窗体上,再在组件上绘制信息。

  • 如何设置一个组件:定义一个拓展与JComponent的类,并覆盖其中的paintComponent方法。
  • 如何将组件添加到窗体中:使用frame.add()函数。这个方法将组件添加至窗体的内容窗格中。
  • 如何绘制图案、图像和文本:使用Graphics类。
  • paintComponent()方法什么时候会自动调用:
    1.用户扩大窗口或极小化后恢复原来大小
    2.窗口被覆盖后重新显示

只要窗口需要重新绘制,事件处理器会通知组件从而引发执行所有组件的paintComponent方法,不要自己调用该方法! 否则可能会对这个自动处理过程造成破坏。

1.在窗体中显示一个组件,并在组件上绘制一条文本信息:

package notHelloWorld;

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

public class NotHelloWorld 
{
	public static void main(String args[])
	{
		EventQueue.invokeLater(()->
				{
					var frame = new NotHelloWorldFrame();
					frame.setTitle("Not a Hello World");
					frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
					frame.setVisible(true);
				});
	}
}

class NotHelloWorldFrame extends JFrame
{
	public NotHelloWorldFrame()
	{
		add(new NotHelloWorldComponent());//添加一个组件至窗体中
		pack();//使用组件的首选大小,首先大小在getPreferredSize中定义
	}
}

class NotHelloWorldComponent extends JComponent 
{
	public static final int MESSAGE_X = 75;
	public static final int MESSAGE_Y = 100;
	
	private static final int DEFAULT_WIDTH = 300;
	private static final int DEFAULT_HEIGHT = 200;
	
	public void paintComponent(Graphics g) //覆盖此方法来绘制内容
	{
		g.drawString("Not a Hello World program!", MESSAGE_X, MESSAGE_Y);
	}
	
	public Dimension getPreferredSize() //设置组件的首选大小
	{
		return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
	}
}

代码解释:

  • pack() : 调整窗口大小为添加至窗体的组件的首选大小。
  • void paintComponent(Graphics g) : 覆盖此方法来确定所需绘制的内容。每次需要重新绘制时,线程会自动调用该函数。
  • Dimension getPreferredSize() :设置组件的首选大小,窗体可以调用 pack() 函数将组件调整为该大小。

总结 ,在窗体中显示信息三大步骤:

  1. 定义组件类,确定绘制内容与首选大小。
  2. 定义窗体类,将组件添加至窗体中。
  3. 创建事件分派线程,创建一个窗体对象并设置窗体属性。

2.绘制2D图形:

Graphics类:绘制直线,矩形,椭圆等,功能有限。

Java 2D库的图形类:它是Graphics类的一个子类,功能更加丰富,除了一些基本图形外,还可以绘制圆弧、二次曲线、三次曲线等等,这里我们重点介绍Java 2D库的图形类。

绘制图形的步骤:

  1. 创建一个实现了shape接口的类的对象(譬如Rectangle2D,Ellipse2D,Line2D等等)。
  2. 使用Graphics2D类的draw方法绘画出图形。
import java.awt.*;
import javax.swing.*;
import java.awt.geom.*; //包含了众多实现了shape接口的类,如Ellipse2D等等

public class DrawTest {
	public static void main(String[] args) 
	{
		EventQueue.invokeLater(()-> //事件分派线程
				{
					var frame = new DrawFrame();
					frame.setTitle("DrawTest");
					frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
					frame.setBounds(300, 300, 800, 800);
					frame.setVisible(true);
				});
	}

}

class DrawFrame extends JFrame //创建一个窗体
{
	public DrawFrame()
	{
		add(new DrawComponent());//将组件添加至窗体中
		pack();					//使用组件的默认大小
	}	
}

class DrawComponent extends JComponent // 定义组件完成所需任务
{
	private static final int DEFAULT_WIDTH = 400;
	private static final int DEFAULT_HEIGHT = 400;
	
	public void paintComponent(Graphics g) //必须覆盖的方法
	{
		var g2 = (Graphics2D) g;//Graphics2D是Graphics的子类,可以直接强制类型转换
		
		double leftX = 100;
		double topY = 100;
		double width = 200;
		double height = 150;
		
		var rect = new Rectangle2D.Double(leftX, topY, width,height);//绘制一个长方形,参数有:左上角坐标,宽,高
		g2.draw(rect);
		
		var ellipse = new Ellipse2D.Double();
		ellipse.setFrame(rect);//以一个长方形为外接矩形绘制一个椭圆
		g2.draw(ellipse);
		
		var line = new Line2D.Double(leftX, topY, leftX + width, topY + height);//绘制一条线段,参数有:起点坐标,终点坐标
		g2.draw(line);
		
		double centerX = rect.getCenterX();
		double centerY = rect.getCenterY();
		double radius = 150;
		var circle = new Ellipse2D.Double();
		circle.setFrameFromCenter(centerX, centerY, centerX + radius, centerY + radius);//通过中点坐标和四角坐标之一绘制图像
		g2.draw(circle);
	}
	
	public Dimension getPerredSize() //覆盖以确定组件的默认大小
	{
		return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
	}
}

几个补充说明:

  • Rectangle2D.Double() / Rectangle2D.Float() : 我们只需要在屏幕上打印像素,Float的精度完全可以满足我们的需求。但是Java语言不能自动将Double转换为Float,因此2D库的设计者为每一个图形类提供了两个版本:Double版本使用double类型的坐标,更方便;Float版本使用float类型的坐标,能够节省空间。
  • Ellipse2D类与Rectangle2D类继承于RectangleShape类,而RectangleShape类又继承于Shape类,Line2D类直接继承于Shape类。

监听点击事件

任何支持GUI的操作环境都需要和用户进行互动,换一种说法,操作环境必须要不断地监视按键与鼠标点击的事件。
若有相关事件的发生,操作环境会将其报告给程序,程序在做出相应的动作。
先解释以下几个名词:

  1. 事件源: 譬如一个按钮,一个滑动条等等,事件源可以装载一个事件监听器。
  2. 监听器接口(ActionListener):定义一个监听器类必须实现监听器接口,重写接口中的actionPerformed函数,函数体为事件源接收到事件(譬如点击)时的响应。
  3. 事件监听器: 实现了监听器接口的类。它的对象可以装载入事件源,用来监听一个事件是否发生。

具体操作过程如下:

  1. 生成事件源(这里以按钮Button为例)
  2. 生成一个面板(Panel)
  3. 将事件源添加到面板里面
  4. 将面板添加到窗体中
  5. 定义事件监听器的类(必须实现监听器接口ActionListener),类中必须覆盖actionPerformed方法。
  6. 生成事件监听器
  7. 将监听器添加到事件源当中
package button;

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

public class ButtonFrame extends JFrame{
	private JPanel buttonPanel;
	private static final int DEFAULT_WIDTH = 300;
	private static final int DEFAULT_HEIGHT = 200;
	
	public ButtonFrame()
	{
		setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
	//1.生成事件源
		var yellowButton = new JButton("Yellow");
		var blueButton = new JButton("Blue");
		var redButton = new JButton("Red");
	//2.生成一个面板
		buttonPanel = new JPanel();
	//3.将事件源添加至面板当中
		buttonPanel.add(yellowButton);
		buttonPanel.add(blueButton);
		buttonPanel.add(redButton);
	//4.将面板添加到窗体当中
		add(buttonPanel);
	//6.生成一个监听器类 ColorAction(定义在下面)	
		var yellowAction = new ColorAction(Color.YELLOW);
		var blueAction = new ColorAction(Color.BLUE);
		var redAction = new ColorAction(Color.RED);
	//7.将监听器添加到事件源中	
		yellowButton.addActionListener(yellowAction);
		blueButton.addActionListener(blueAction);
		redButton.addActionListener(redAction);
	}
	//5.定义一个监听器类,其中覆盖了actionPerformed方法
	private class ColorAction implements ActionListener
	{
		private Color backgroundColor;
		
		public ColorAction(Color c)
		{
			backgroundColor = c;
		}
		//必须具有参数ActionEvent
		public void actionPerformed(ActionEvent event)
		{
			buttonPanel.setBackground(backgroundColor);
		}
	}
	
	public static void main(String[] args)
	{
		EventQueue.invokeLater(()->
		{
			var buttonFrame = new ButtonFrame();
			buttonFrame.setVisible(true);
		});
	}

}

注意:

  1. JButton的一个对象,即一个事件源,被添加进了一个事件监听器,因此只要事件源被点击一次,就会创建一个ActionEvent的对象(即调用一次actionPerformed方法)。
  2. 一个事件源可以添加多个监听器,这样,事件源被点击一次,所有的actionPerformed方法都会被调用。

Over. Thanks for watching.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值