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

    科技2022-08-14  96

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

    目录:

    用户界面工具包历史显示窗体在组件中显示信息——2D图形监听点击事件

    用户界面工具包历史

    抽象窗口工具包 (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() 函数将组件调整为该大小。

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

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

    2.绘制2D图形:

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

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

    绘制图形的步骤:

    创建一个实现了shape接口的类的对象(譬如Rectangle2D,Ellipse2D,Line2D等等)。使用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的操作环境都需要和用户进行互动,换一种说法,操作环境必须要不断地监视按键与鼠标点击的事件。 若有相关事件的发生,操作环境会将其报告给程序,程序在做出相应的动作。 先解释以下几个名词:

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

    具体操作过程如下:

    生成事件源(这里以按钮Button为例)生成一个面板(Panel)将事件源添加到面板里面将面板添加到窗体中定义事件监听器的类(必须实现监听器接口ActionListener),类中必须覆盖actionPerformed方法。生成事件监听器将监听器添加到事件源当中 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); }); } }

    注意:

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

    Over. Thanks for watching.

    Processed: 0.020, SQL: 8