第5章 图形程序设计


Java用户界面工具包简史:
Java1.1,包含了一个用于基本GUI程序设计的类库,名为抽象窗口工具包(Abstract Window Toolkit,AWT)基本AWT库将处理用户界面元素的任务委托给各个目标平台上的原生GUI工具包,由原生GUI工具包负责用户界面元素的创建和行为。
但不同平台难以获得一致性体验,且不同平台的AWT用户界面库中存在着不同的bug“一次编写,到处调试”
1996年,Netscape创建了 IFC(Internet Foundation Class)GUI库:采取与AWT完全不同的工作方式,将按钮,菜单等用户界面元素绘制在空白窗口上,底层窗口系统所需唯一功能就是能够显示一个窗口,并在窗口中绘制。
Sun与Netscape合作完善了这种方法,创建了Swing用户界面库(不基于对等元素的GUI工具包,建立在AWT架构之上,使用AWT的基本机制(特别是事件处理),提供更为强大的用户界面组件)

显示窗体

顶层窗口(没有包含在其他窗口中的窗体)称为窗体(frame),AWT库中有一个称为Frame的类,用于描述这个顶层窗口。Swing版本的类名为JFrame,扩展了Frame类,它是极少数几个不绘制在画布上的Swing组件之一。因此它的修饰部件(按钮,标题栏等)由用户的窗口系统绘制,而不由Swing绘制。

基本组件:eg:JButton,JTextField
容器:eg:JFrame,JPanel

创建窗体:

显示空白窗体:
在这里插入图片描述在这里插入图片描述

默认窗体大小0×0像素(无意义)
这里构造器设置窗体大小为300×200像素,main方法中,构造一个SimpleFrame对象并使它可见。
所有Swing组件必须由事件分配线程配置,这是控制线程,它将鼠标点击和按键等事件传递给用户接口组件。

EventQueue.invokeLater(()->
{
	statement
});

定义用户关闭窗体的响应动作。这里只是简单的退出:
(在包含多个窗体的程序中,不能在用户关闭其中一个窗体时就让程序退出。默认情况下,用户关闭窗体只是将窗体隐藏起来)

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

仅仅构造窗体,窗体是不会自动显示的,窗体起初不可见,给程序员在窗体第一次显示之前向其中添加组件的机会,为了显示窗体,main方法中需要调用窗体的setVisible方法

frame.setVisible(true);

初始化语句结束后,main方法退出,退出main方法并没有终止程序,终止的只是主线程,事件分派线程会保持程序处于激活状态,直到关闭窗口或调用System.exit方法终止程序。

窗体属性:

JFrame类中一些处理窗口的方法:

  • setLocation方法和setBounds方法用于设置窗体的位置
  • setIconImage方法用于告诉窗口系统在标题栏,任务切换窗口等位置显示哪个图标。
  • setTitle方法用于改变标题栏的文字
  • setResizable利用一个boolean值确定是否允许用户改变窗体大小
    在这里插入图片描述如图示:
    需要在Component类(所有GUI对象的祖先)和window类(Frame类的父类)中寻找调整窗口大小和位置的方法。
    如:Component类中有setLocation方法:重新定位组件——setLocation(x,y)窗口左上角要位于水平向右x像素,垂直向下y像素位置,坐标(0,0)是屏幕左上方位置。setBound(x,y,width,heigth)调整组件大小和位置。

组件类很多方法以获取/设置方法对形式出现,如:
public String getTitle()
public void setTitle(String title)
这样一对获取/设置方法被称为属性,属性有一个名和一个类型,如Frame类有一个名为title且类型为String的属性。
特别的:对于类型为boolean的属性,获取方法以is开头,如
public boolean isResizable()
public void setResizable(boolean resizable)

要确定适当的窗体大小,首先要得出屏幕的大小,调用Toolkit类的静态方法getDefault-Toolkit得到一个Toolkit对象(Toolkit类相当于一个基地,包含大量与原生窗口系统交互的方法),然后调用getScreenSize方法,这个方法以Dimension对象形式返回屏幕大小,Dimension对象同时用公共实例变量width,heigth保存屏幕的宽度和高度,使用屏幕大小的一个适当百分数指定窗口大小。如图示
在这里插入图片描述

在组件中显示信息

在这里插入图片描述其中根窗口,层级窗口和玻璃窗格人们并不太关心,它们用来组织菜单栏和内容窗格以及实现观感,Swing程序员最关心的是内容窗格,添加到窗口的组件都会自动添加到内容窗格中。

Component c=new ...
frame.add(c);

要在一个组件上绘制,需要定义一个扩展JComponent的类,并覆盖其中的paintComponent方法。
paintComponent方法中有一个Graphics类型的参数,Graphics对象保存着用于绘制图像和文本的一组设置。Java中所有的设置必须通过Graphics对象完成,其中包含了绘制图案,图像和文本的方法。
在这里插入图片描述无论何种原因,只要窗口需要重新绘制,事件处理器就会通知组件,从而引发执行所有组件的paintComponent方法,绝对不要自己调用paintComponent方法。只要应用的某个部分需要重新绘制,就会自动调用这个方法,不要人为干预这个自动处理过程。
触发这个自动响应的动作如:扩大窗口,极小化窗口又恢复窗口大小会引发绘制,其他窗口弹出并覆盖了已有窗口,让上层窗口消失时被覆盖的窗口已被破坏,需要重新绘制(图形系统不保存下层像素)等。
强制重新绘制屏幕调用repaint方法,此方法将引发采用适当配置的Graphics对象调用所有组件的paintComponent方法。

Graphics对象的度量单位是像素,坐标(0,0)指示所绘制组件的左上角。

处理2D图形

在这里插入图片描述Java1.2版本以来,paintComponent等方法会自动地接收一个Graphics2D类对象,只需要使用一个强制类型转换即可得到2D类对象

Java2D库采用面向对象方式组织几何图形,具体来说,提供了直线,矩形,椭圆类:Line2D,Rectangle2D,Ellipse2D,等多种复杂图形,且都实现了Shape接口。

绘制图形:首先创建一个实现了Shape接口的类的对象,然后调用Graphics2D类的draw方法
在这里插入图片描述
Java2D库针对像素采用浮点坐标,而非整数坐标。内部计算都采用单精度float。但考虑到操作float有时不方便(一些方法返回值可能为double,以及常量小数在Java默认为double,而double转换为float必须强制类型转换),因而为每个图形类提供两个版本。
使用double类可以避免处理float类型值,但float类型更节省存储空间
在这里插入图片描述一些图形的绘制
在这里插入图片描述
在这里插入图片描述

使用颜色

使用graphics2D类的setPaint方法可以为图形上下文上的所有后续的绘制操作选择颜色。
在这里插入图片描述可以用一种颜色填充一个封闭图形的内部,只需要将调用draw替换为调用fill

(fill方法会在右侧和下方少绘制一个像素,如绘制矩形:new Rectangle2D.Double(0,0,10,20),绘制的矩形将包括x=10和y=20的像素,若填充这个矩形则不会绘制x=10和y=20的像素)

在这里插入图片描述设置背景颜色,需要使用Component类中的setBackground方法。

var component =new MyComponent();
component.setBackground(Color.PINK);

还有一个setForeground方法,用来指定在组件上进行绘制时使用的默认颜色。

使用字体

通过字体名指定字体,字体名由字体族名(如:”Helvetica“)和一个可选后缀(如”Bold“)组成。

获得计算机内可用字体:

import java.awt.*;
public class ListFonts
{
	public static void main(String[] args)
	{
		String[] fontNames=GraphicsEnvironment.//该类描述了用户系统的图形上下文
		getLocalGraphicsEnvironment().//为了得到上述类的对象,需要调用静态的getLocalGraphicsEnvironment方法
		getAvailableFontFamilyNames();//GraphicsEnvironment类的方法:返回一个字符串数组,其中包含了所有可用字体名
		for(String fontName:fontNames)
		{
			System.out.println(fontName);
		}
	}
}

使用某种字体绘制字符:

var sansbold14=new Font("SansSerif",Font.Bold,14);
//指定字体名,字体风格,字体大小
//字体大小即第三个参数,是以点数目计算的字体大小。(排版中普遍使用点数指示字体大小,每英寸包含72个点)
//字体风格第二个参数,允许:常规(Font.PLAIN),加粗(Font.BOLD),斜体(Font.ITALIC),加粗斜体(Font.BOLD+Font.ITALIC)

常规字体大小为1点,可以使用deriveFont方法得到所需大小字体
deriveFont方法有两个重载版本:
float参数:设置字体大小(fa.deriveFont(14.0F))
int参数:设置字体风格( fa.deriveFont(14)结果为斜体,14的二进制表示的是ITALIC位为1,BOLD位为0)

在这里插入图片描述若想让字符串居中显示:需要得到一个表示屏幕设备字体属性的对象,需要调用Graphics2D中的getFontRenderContext方法,将返回一个FontRenderContext类的对象,将这个对象传递给Font类的getStringBounds方法,此方法将返回包围字符串的矩形。在这里插入图片描述在这里插入图片描述

显示图像

使用ImageIcon类从文件中读取图像

Image image=new ImageIcon(filename).getImage();

变量image包含了一个封装了图像数据的对象的引用,可以使用Graphics类的drawImage方法显示这个图像

public void paintComponent(Graphics g)
{
	g.drawImage(image,x,y.null);
}

在这里插入图片描述

平铺显示:

for(int i=0;i*imageWidth<=getWidth();++i)
	for(int j=0;j*imageHeight<=getHeight();++j)
	{
		if(i+j>0) g.copyArea(0,0,imageWidth,imageHeight,i*imageWidth,j*imageHeight);
	}

事件处理

任何支持GUI的操作环境都要不断地监视按键或点击鼠标这样的事件,事件再报告给正在运行的程序,每个程序将决定如何对这些事件做出响应。

基本概念

在这里插入图片描述在JavaAWT中,事件源(如按钮或滚动条等)有一些方法,允许注册事件监听器,这些对象会对事件做出所需的响应。通知一个事件监听器发生了某个事件时,这个事件的相关信息会封装在一个事件对象中。Java中,所有事件对象最终派生于java.util.EventObject类,不同的事件源可以产生不同类型的事件。
在这里插入图片描述在这里插入图片描述在这里插入图片描述只要按钮产生了一个动作事件”按钮点击“,listener对象就会得到通知,要实现ActionListener接口,监听器类必须有一个名为actionPerformed方法,该方法接收一个ActionEvent对象作为参数。
点击按钮->JButton对象创建一个ActionEvent对象->调用所有监听器的listener.actionPerformed(event),并传入事件对象

一个示例:点击按钮修改面板的背景颜色


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

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_WIDTH);

        //create buttons
        var yellowButton =new JButton("Yellow");
        var blueBuuton=new JButton("Blue");
        var redButton=new JButton("Red");

        buttonPanel=new JPanel();
        buttonPanel.add(yellowButton);
        buttonPanel.add(blueBuuton);
        buttonPanel.add(redButton);

        add(buttonPanel);//add panel to frame

        //create button actions
        var yellowAction=new ColorAction(Color.YELLOW);
        var blueAction=new ColorAction(Color.BLUE);
        var redAction=new ColorAction(Color.RED);

        //associate actions with buttons
        yellowButton.addActionListener(yellowAction);
        blueBuuton.addActionListener(blueAction);
        redButton.addActionListener(redAction);
    }

    private class ColorAction implements ActionListener
    {
        private Color  backgroundColor;
        public ColorAction(Color c)
        {
            backgroundColor=c;
        }

        public void actionPerformed(ActionEvent event)
        {
            buttonPanel.setBackground(backgroundColor);
        }

    }
}

AWT事件继承层次

  • Java中所有事件的父类都是java.util.EventObject
  • AWT事件类的父类为java.util.AWTEvent,这是EventObject的直接子类
  • 如果某事件监听器接口有多个方法,而实际只对其中某个或几个方法感兴趣,可使用相对应的适配器类

AWT将事件分为底层事件和语义事件,语义事件是指用户动作的事件:如点击按钮,因此ActionEvent是一种语义事件。底层事件是使语义事件得以发生的事件:点击按钮时包括了按下鼠标,移动鼠标,和松开鼠标。调节滚动条是一种于语义事件,但拖动鼠标是底层事件。

常见语义事件类型

  • ActionEvent(按钮点击,菜单选择,选择列表项,在文本域中键入enter)
  • AdjustmentEvent(用户调节滚动条)
  • MouseEvent(鼠标键被按下,释放或拖动)
  • FocusEvent(某个组件获得焦点或失去焦点)
  • ItemEvent(用户从复选框或列表项中选择一项)
  • KeyEvent(一个键被按下或释放)
  • MouseWheelEvent(鼠标滚轮被转动)
  • WindowEvent(窗口状态被改变)
  • AWT监听器接口,事件和事件源列表P471

对应监听器:~Listener eg:ActionListener
在这里插入图片描述适配器类:
~Adapter eg:MouseAdapter;FocusAdapter
每个含有多个方法的AWT监听器接口都配有一个适配器类,这个类实现了接口中的所有方法,但每个方法并不做任何事情

鼠标事件:

用户点击鼠标按钮时,将会调用三个监听器方法:

  • 鼠标第一次被按下时调用mousePressed
  • 鼠标被释放时调用mouseReleased
  • 最后调用mouseClicked
    如果只对最终的点击事件感兴趣,可以忽略前两个方法。
    以MouseEvent类对象为参数,调用getX(),getY()方法可以获得鼠标被按下时鼠标指针所在的x,y坐标。想要区分单击,双击和三击(!),需要使用getClicked方法。

当鼠标在窗口上移动时,窗口会收到一连串的鼠标移动事件。PS:有两个独立的接口MouseLIstener和MouseMotionListener,这样做有利于提高效率,当用户移动鼠标时,会有大量鼠标事件,只关心鼠标点击的监听器就不会被多余的鼠标移动事件所干扰。
用户在移动鼠标的同时按下鼠标,就会生成mouseDragged调用而不是mouseMoved调用。只有鼠标在一个组件内部停留才会调用mouseMoved方法。
mouseEntered,mouseExited在鼠标进入或移出组件时被调用。

键盘事件

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值