Java——AWT详解

AWT

在Java 1.0刚刚出现的时候,它包含了一个用于基本GUI程序设计的类库,名为抽象窗口工具包(Abstract Window Toolkit,AWT)。基本AWT库将处理用户界面元素的任务委托给各个目标平台(Windows、Solaris、Macintosh等)上的原生GUI工具包,由原生GUI工具包负责用户界面元素的创建和行为。例如,如果使用最初的AWT在Java窗口中放置一个文本框,就会有一个底层的“对等”文本框具体处理文本输入。从理论上说,所得到的程序可以运行在任何平台上,而且有目标平台的观感。

对于简单的应用,这种基于“对等元素”的方法是可行的。但是,要想编写依赖于原生用户界面元素的高质量、可移植的图形库,显然极其困难。例如,在不同的平台上,菜
单、滚动条和文本域这些用户界面元素的操作行为存在着一些微妙的差别。因此,要想利用这种方法为用户提供一致的、可预见性的体验是相当困难的。而且,有些图形环境(如X11/Motif)并没有像Windows或Macintosh这样丰富的用户界面组件集合。这就进一步限制了基于“最小公分母”方法实现的可移植库。因此,使用AWT构建的GUI应用程序看起来没有原生的Windows或Macintosh应用那么漂亮,也没有提供那些平台用户所期望的功能。更加糟糕的是,不同平台上的AWT用户界面库中存在着不同的bug。研发人员必须在每一个平台上测试应用程序,因此人们嘲弄地把这种做法称为“一次编写,到处调试”。

l996年,Netscape创建了一种称为IFC(Internet Foundation Class)的GUI库,它采用了与AWT完全不同的工作方式。它将按钮、菜单等用户界面元素绘制在空白窗口上。底层窗口系统所需的唯一功能就是能够显示一个窗口,并在这个窗口中绘制。因此,不论程序在哪个平台上运行,Netscape的IFC组件都有着相同的外观和行为。Sun公司与Netscape合作完善了这种方法,创建了一个名为Swing的用户界面库。Swing作为Java 1.1的一个扩展,
现已成为Java1.2标准库的一部分。

Swing现在是不基于对等元素的GUI工具包的官方名字。

1、窗体

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

1.1、创建窗体

public class SimpleFrameTest {
    public static void main(String[] args) {
        //所有的Swing组件必须由 事件分派线程 配置,这是控制线程,它将鼠标点击和按键等事件传递给用户接口组件
        EventQueue.invokeLater(() -> {
            JFrame frame = new SimpleFrame();
            //定义用户关闭这个窗体时的响应动作,简单退出即可
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            //设置窗体可见
            frame.setVisible(true);
        });
    }
}

//继承JFame,自定义宽度、高度
class SimpleFrame extends JFrame {
    private static final int DEFAULT_WIDTH = 300;
    private static final int DEFAULT_HEIGHT = 200;

    public SimpleFrame() throws HeadlessException {
        setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
    }
}

这个窗体的标题栏和外框装饰都是由操作系统绘制的,而不是Swing库。Swing库负责绘制窗体内的所有内容。在这个程序中,它只是用默认的背景色填充了窗体。

1.2、窗体属性

JFrame类本身只包含若干个改变窗体外观的方法,大多数处理窗体大小和位置的方法都来自JFrame的各个超类:

在这里插入图片描述

java.awt.Component

  • boolean isVisible()
  • void setVisible(boolean b):获取或设置visible属性。组件最初是可见的,但类似JFrame的顶层组件例外。
  • void setSize(int width, int height):将组件大小调整为给定的宽度和高度。
  • void setLocation(int x, int y):将组件移到一个新的位置。如果这个组件不是顶层组件,x和y坐标是容器坐标;否则如果组件是顶层组件,x和y坐标就使用屏幕 坐标。
  • void setBounds(int x, int y, int width, int height):移动并调整组件的大小。
  • Dimension getSize()
  • void setSize(Dimension d):获取或设置当前组件的size属性。

java.awt.Window

  • void setLocationByPlatform(boolean b):获取或设置locationByPlatform属性。这个属性在窗口显示之前设置时,由平台选择一个合适的位置。

java.awt.Frame

  • boolean isResizable()
  • void setResizable(boolean b):获取或设置resizable属性。设置了这个属性时(属性值为true),用户可以调整窗体的大小。
  • String getTitle()
  • void setTitle(String s):获取或设置title属性,这个属性确定窗体标题栏中的文字。
  • Image getIconImage()
  • void setIconImage(Image image):获取或设置iconImage属性,这个属性确定窗体的图标。窗口系统可能会将这个图标显示为窗体装饰的一部分或者显示在其它位置。

java.awt.Toolkit

  • static Toolkit getDefaultToolkit():返回默认的工具箱。
  • Dimension getScreenSize():返回用户屏幕的大小。

javax.swing.ImageIcon

  • ImageIcon(String filename):构造一个图标,其图像存储在一个文件中。
  • Image getImage():获得该图标的图像。

2、在组件中显示信息

可以将消息字符串直接绘制在窗体中,但这并不是一种好的编程习惯。在Java中,窗体实际上设计为组件的容器,如菜单栏和其他用户界面元素。在通常情况下,应该在添加到窗体的另一个组件上绘制信息。

JFrame的结构相当复杂。如下图,在JFrame中有四层窗格。其中根窗格、层级窗格和玻璃窗格人们并不太关心;它们要用来组织菜单栏和内容窗格以及实现观感。Swing程序员最关心的是内容窗格(content pane)。

在这里插入图片描述

添加到窗体的所有组件都会自动添加到内容窗格中:

Component c = ...;
frame.add(c);//added to the content pane

要在一个组件上绘制,需要定义一个扩展JComponent的类,并覆盖其中的paintComponent方法。paintComponent方法有一个Graphics类型的参数,Graphics对象保存着用于绘制图像和文本的一组设置,例如,你设置的字体或当前的颜色。在Java中,所有的绘制都必须通过Graphics对象完成,其中包含了绘制图案、图像和文本的方法。

class MyComponent extends JComponent {
	public void paintComponent(Graphics g) {
		//code for drawing
	}
}

无论何种原因,只要窗口需要重新绘制,事件处理器就会通知组件,从而引发执行所有组件的paintComponent方法。绝对不要自己调用该方法。只要应用的某个部分需要重新绘制,就会自动调用这个方法,不要人为地干预这个自动的处理过程。

public class NoteHelloWorld {
    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            JFrame frame = new NoteHelloWorldFrame();
            frame.setTitle("NoteHelloWorld");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
        });
    }
}

class NoteHelloWorldFrame extends JFrame {
    public NoteHelloWorldFrame() throws HeadlessException {
        add(new NoteHelloWorldComponent());
        pack();//使组件适应窗体大小
    }
}

class NoteHelloWorldComponent extends JComponent {
    public static final int MWSSAGE_X = 75;
    public static final int MESSAGE_Y = 100;

    private static final int DEFAULT_WIDTH = 300;
    private static final int DEFAULT_HEIGHT = 200;

    @Override
    protected void paintComponent(Graphics g) {
        g.drawString("Note a Hello World program", MWSSAGE_X, MESSAGE_Y);
    }

    public Dimension getPreferredSize() {
        return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
    }
}

在这里插入图片描述

javax.swing.JFrame

  • Component add(Component c):将一个给定的组件添加到该窗体的内容窗格中,并返回这个组件。

java.awt.Component:

  • void repaint():尽可能快地重新绘制组件。
  • Dimension getPreferredSize():覆盖这个方法,以返回这个组件的首选大小。

javax.swing.JComponent

  • void paintComponent(Graphics g):覆盖这个方法来描述需要如何绘制组件。

java.awt.Window

  • void pack():调整窗口大小,要考虑其组件的首选大小。

2.1、处理2D图形

要想使用Java 2D库绘制图形,需要获得Graphics2D类的一个对象。这个类是Graphics类的子类。自从Java1.2以来,paintComponent等方法会自动地接收一个Graphics2D对象,只需要强制转换即可:

public void paintComponent(Graphics g){
	Graphics2D g2 = (Graphics2D)g;
	...
}

Java2D库采用面向对象的方式组织几何图形:

  • Line2D:直线
  • Rectangle2D:矩形
  • Ellipse2D:椭圆

这些类都实现了Shape接口。Java2D库支持更加复杂的图形,例如圆弧、二次曲线、三次曲线和通用路径等。

要想绘制一个图形,首先要创建一个实现了Shape接口的类的对象,然后调用Graphics2D类的draw方法:

Rectangle2D rect = ...;
g2.draw(rect);

Java2D库针对像素采用的是浮点坐标,而不是整数坐标。内部计算都采用单精度float。毕竟,几何计算的最终目的是要在屏幕或打印机上设置像素,所以单精度完全可以满足要求。只要舍入误差限制在一个像素的范围内,视觉效果就不会受到影响。

不过,有时候程序员处理float并不太方便,所以2D库的设计者决定为每个图形类提供两个版本:一个是为那些想节省空间的程序员提供的版本,要使用float类型的坐标;另一个是为那些懒惰的程序员提供的版本,会使用double类型的坐标(本书采用的是第二个版本,即尽可能使用double类型的坐标)。

这个库的设计者采用了一种古怪的机制对这些选择进行打包。例如Rectangle.2D类,这是一个抽象类,有两个具体子类,这两个具体子类也是静态内部类:

  • Rectangle2D.Float
  • Rectangle2D.Double

在这里插入图片描述

public class DrawTest {
    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            JFrame frame = new DrawFrame();
            frame.setTitle("DrawTest");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            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;

    @Override
    protected void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D)g;
        //画矩形
        double leftX = 100;
        double topY = 100;
        double width = 200;
        double height = 150;
        Rectangle2D rect = new Rectangle2D.Double(leftX, topY, width, height);
        g2.draw(rect);

        //画椭圆,位置就是外接矩形
        Ellipse2D ellipse = new Ellipse2D.Double();
        ellipse.setFrame(rect);
        g2.draw(ellipse);

        //画对角线
        g2.draw(new Line2D.Double(leftX, topY, leftX + width, topY + height));

        //画圆
        double centerX = rect.getCenterX();
        double centerY = rect.getCenterY();
        double radius = 150;
        Ellipse2D circle = new Ellipse2D.Double();
        circle.setFrameFromCenter(centerX, centerY, centerX + radius, centerY + radius);
        g2.draw(circle);
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
    }
}

在这里插入图片描述

java.awt.geom.RectangularShape

  • double getCenterX()
  • double getCenterY()
  • double getMinX()
  • double getMinY()
  • double getMaxX()
  • double getMaxY():返回闭合矩形的中心,以及最小、最大x和y坐标值
  • double getWidth()
  • double getHeight():返回闭合矩形的宽和高
  • double getX()
  • double getY():返回闭合矩形左上角的x和y坐标

java.awt.geom.Rectangle2D.Double

  • Rectangle2D.Double(double x, double y, double w, double h):利用给定的左上角、宽和高、构造一个矩形

java.awt.geom.Ellipse2D.Double

  • Ellipse2D.Double(double x, double y, double w, double h):利用有给定左上角、宽和高的外接矩形,构造一个椭圆

java.awt.geom.Point2D.Double

  • Point2D.Double(double x, double y):利用给定坐标构造一个点

java.awt.geom.Line2D.Double

  • Line2D.Double(Point2D start, Point2D end)
  • Line2D.Double(double startX, double startY, double endX, double endY):使用给定的起点和重点,构造一条直线

2.2、使用颜色

使用Graphics2D类的setPaint方法可以为图形上下文上的所有后续的绘制操作选择颜色。

g2.setPaint(Color.RED);
g2.drawString("Warning!", 100, 100);

可以用一种颜色填充一个封闭图形的内部。为此,只需要将调用draw替换为调用fill:

Rectangle2D rect = ...;
g2.setPaint(Color.RED);
g2.fill(rect);

要想用多种颜色绘制,就需要选择一个颜色、绘制图形、再选择另外一个颜色、再绘制图形。

Color类用于定义颜色,在java.awt.Color类中提供了13个预定义的常量,分别表示13种标准颜色:

  • BLACK
  • BLUE
  • CYAN
  • DARK_GRAY
  • GRAY
  • GREEN
  • LIGHT_GRAY
  • MAGENTA
  • ORANGE
  • PINK
  • RED
  • WHITE
  • YELLOW

也可以提供三色分量来创建Color对象,从而指定一个颜色。红、绿和蓝三重颜色取值为0-255之间的整数:

g2.setPaint(new Color(0, 128, 128));
g2.drawString("Welcom!", 75, 125);

如果想要设置背景颜色,需要使用Component类中的setBackgroud方法。另外,还有一个setForeground方法,用来指定在组件上绘制时使用的默认颜色。

java.awt.Color

  • Color(int r, int g, int b):用给定的红、绿、蓝分量创建一个颜色对象

java.awt.Graphics2D

  • Paint getPaint()
  • void setPaint(Paint p):获取或设置这个图形上下文的绘制属性。Color类实现了Paint接口。因此,可以使用这个方法将绘制属性设置为纯色
  • void fill(Shape s):用当前颜色填充图形

java.awt.Component

  • Color getForeground()
  • Color getBackground()
  • void setForeground(Color c)
  • void setBackground(Color c):获取或设置前景或背景色

2.3、使用字体

可以通过字体名指定一种字体。字体名由字体族名和一个可选的后缀组成。例如,“Helvetica”和“Helvetica Bold”都属于名为“Helvetica”字体族的字体。

要想知道某台特定计算机上可用的字体,可以调用GraphicsEnvironment类的getAvailable. FontFamilyNames方法。这个方法将返回一个字符串数组,其中包含了所有可用的字体名。GraphicsEnvironment类描述了用户系统的图形上下文,为了得到这个类的对象,需要调用静态的getLocalGraphicsEnvironment方法。下面这个程序将打印出你的系统上的所有字体名:

public class ListFonts {
    public static void main(String[] args) {
        GraphicsEnvironment localGraphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
        String[] fontNames = localGraphicsEnvironment.getAvailableFontFamilyNames();
        for (String name : fontNames) {
            System.out.println(name);
        }
    }
}

AWT定义了5个逻辑字体名:

  • SansSerif
  • Serif
  • Monospaced
  • Dialog
  • DialogInput

这些字体名总是被映射到客户机上的某些实际字体。

要想使用某种字体绘制字符,必须首先创建一个Font类的对象。需要指定字体名、字体风格和字体大小。下面是构造一个Font对象的例子:

var sansbold14 new Font("SansSerif",Font.BOLD,14);

第三个参数是以点数目计算的字体大小。排版中普遍使用点数指示字体大小,每英寸包含72个点。

在Font构造器中,提供字体名的位置也可以给出逻辑字体名。可以把Font构造器的第二个参数设置为以下值来指定字体的风格(常规、加粗、斜体或加粗斜体):

  • Font.PLAIN
  • Font.BOLD
  • Font.ITALIC
  • Font.BOLD Font.ITALIC

常规字体的字体大小为1点。可以使用deriveFont方法得到所需大小的字体:

Font f = f1.deriveFont(14.0F);

下面这段代码将使用系统中14点加粗的标准sans serif字体显示字符串"Hello World":

var sansbold14 = new Font("SansSerif", Font.BOLD, 14);
g2.setFont(sansbold14);
var message = "Hello World";
g2.drawString(message, 75, 100);

接下来,将字符串居中,而不是绘制在任意位置。因此,需要知道字符串占据的宽和高的像素数。这两个值取决于下面三个因素:

  • 使用的字体(在这个例子中为sans serif,加粗,14点);
  • 字符串(在这个例子中为“Hello World”);
  • 绘制字体的设备(在这个例子中为用户屏幕)。

要想得到表示屏幕设备字体属性的对象,需要调用Graphics2D类中的getFontRenderContext方法。它将返回一个FontRenderContext类的对象。可以直接将这个对象传递给Font类的getStringBounds方法:

FontRenderContext content = g2.getFontRenderContext();
Rectangle2D bounds = sansbold14.getStringBounds(message, context);

getStringBounds方法将返回包围字符串的矩形。

为了解释这个矩形的大小,需要清楚几个基本的排版术语:

  • 基线(baseline):是一条虚构的线,例如,字母“e”所在的底线。
  • 上坡度(ascent):是从基线到坡顶
    (ascenter)的距离(坡顶是“b”“k”或大写字母的上面部分)。
  • 下坡度(descent):是从基线到
    坡底(descenter)的距离(坡底是“p”或“g”等字母的下面部分)。
  • 行间距(leading):是某一行的坡底与其下一行的坡顶之间的空隙

在这里插入图片描述

字体的高度是连续两个基线之间的距离,它等于下坡度+行间距+上坡度。getStringBounds方法返回的矩形宽度是字符串水平方向的宽度。矩形的高度是上坡度、下
坡度和行间距的总和。这个矩形始于字符串的基线,矩形顶部的y坐标为负值。因此,可以使用下面的方法获得字符串的宽度、高度和上坡度:

double stringWidth = bounds.getWidth();
double stringHeight = bounds.getHeight();
double ascent = -bounds.getY();

如果需要知道下坡度或行间距,可以使用Font类的getLineMetrics方法。这个方法将返回一个LineMetrics类的对象,获得下坡度和行间距的方法是:

LineMetrics metrics = f.getLineMetrics(message,context);
float descent = metrics.getDescent();
float leading = metrics.getLeading();

示例:绘制基线和字符串外框矩形

public class FontTest {
    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            JFrame frame = new FontFrame();
            frame.setTitle("FontTest");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
        });
    }
}

class FontFrame extends JFrame {
    public FontFrame() throws HeadlessException {
        add(new FontComponent());
        pack();
    }
}

class FontComponent extends JComponent {
    private static final int DEFAULT_WIDTH = 400;
    private static final int DEFAULT_HEIGHT = 400;

    @Override
    protected void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D)g;
        String message = "Hello World!";

        Font f = new Font("Serif", Font.BOLD, 36);
        g2.setFont(f);

        //计算文本大小
        FontRenderContext context = g2.getFontRenderContext();
        Rectangle2D bounds = f.getStringBounds(message, context);

        double x = (getWidth() - bounds.getWidth()) / 2;
        double y = (getHeight() - bounds.getHeight()) / 2;

        double ascent = -bounds.getY();
        double baseY = y + ascent;

        //画出文本
        g2.drawString(message, (int)x, (int)baseY);
        g2.setPaint(Color.LIGHT_GRAY);
        //画出基线
        g2.draw(new Line2D.Double(x, baseY, x + bounds.getWidth(), baseY));
        //画出外接矩形
        Rectangle2D rect = new Rectangle2D.Double(x, y, bounds.getWidth(), bounds.getHeight());
        g2.draw(rect);


    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
    }
}

在这里插入图片描述

java.awt.Font

  • Font(String name, int style, int size):创建一个字体对象
  • String getFontName():获取字体名
  • String getFamily():获得字体族名
  • String getName():获得字体名
  • Rectangle2D getStringBounds(String s, FontRenderContext context):返回保卫这个字符串的矩形
  • LineMetrics getLineMetrics(String s, FontRenderContext context):返回确定字符串宽度的一个度量对象
  • Font deriveFont(int style)
  • Font deriveFont(float size)
  • Font deriveFont(int style, float size):返回一个新字体

java.awt.font.LineMetrics

  • float getAscent():获得字体上坡度
  • float getDescent():获得字体下坡度
  • float getLeading():获得字体行间距
  • float getHeight():获得字体的总高度(下坡度+行间距+上坡度)

java.awt.Graphics2D

  • FontRenderContext getFontRenderContext():获得这个图形上下文中指定字体特征的字体绘制上下文
  • void drawString(String str, float x, float y):采用当前的字体和颜色绘制一个字符串

javax.swing.JComponent

  • FontMetrics getFontMetrics(Font f):获取给定字体的字体度量对象

java.awt.FontMetrics

  • FontRenderContext getFontRenderContext():返回字体的字体绘制上下文

2.4、显示图像

可以使用ImageIcon类从文件读取图像:

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

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

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

可以再进一步,在一个窗口中平铺显示图像。这里采用paintComponent方法来实现平铺显示。首先在左上角显示图像的一个副本,然后使用copyArea调用将其复制到整个窗口:

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

java.awt.Graphics

  • boolean drawImage(Image img, int x, int y, ImageObserver observer)
  • boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer):绘制一个不缩放或缩放的图像。注意:这个调用可能会在图像绘制完毕前就返回。会向imageObserver对象通知绘制的进制。这在很久以前是一个很有用的特性。不过现在只需要传递null作为观察者就可以了。
  • void copyArea(int x, int y, int width, int height, int dx, int dy):复制屏幕的一个区域。dx和dy是原始区域到目标区域的距离。

3、事件处理

3.1、基本事件处理概念

在Java AWT中,事件源(如按钮或滚动条)有一些方法,允许你注册事件监听器,这些对象会对事件做出所需的响应。

通知一个事件监听器发生了某个事件时,这个事件的相关信息会封装在一个事件对象(event object)中。在Java中,所有的事件对象最终都派生于java.util.EventObject类。当然,每个事件类型还有子类,例如,ActionEvent和WindowEvent。

不同的事件源可以产生不同类型的事件。例如,按钮可以发送一个ActionEvent对象,而窗口会发送WindowEvent对象。

综上所述,下面给出AWT事件处理机制的概要说明:

  • 事件监听器是一个实现了监听器接口(listener interface)的类实例。
  • 事件源对象能够注册监听器对象并向其发送事件对象。
  • 当事件发生时,事件源将事件对象发送给所有注册的监听器。
  • 监听器对象再使用事件对象中的信息决定如何对事件做出响应。
ActionListener listener = ...;
JButton button = new JButton("OK");
button.addActionListener(listener);

现在,只要按钮产生了一个『动作事件』,listener对象就会得到通知。

3.2、处理按钮点击事件

三个按钮,点击切换背景色:

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);
        //创建按钮
        JButton yellowButton = new JButton("Yellow");
        JButton blueButton = new JButton("Blue");
        JButton redButton = new JButton("Red");
        //将按钮加入面板
        buttonPanel = new JPanel();
        buttonPanel.add(yellowButton);
        buttonPanel.add(blueButton);
        buttonPanel.add(redButton);
        //将面板加入窗体
        add(buttonPanel);
        //创建事件监听器
        ColorAction yellowAction = new ColorAction(Color.YELLOW);
        ColorAction blueAction = new ColorAction(Color.BLUE);
        ColorAction redAction = new ColorAction(Color.RED);
        //监听
        yellowButton.addActionListener(yellowAction);
        blueButton.addActionListener(blueAction);
        redButton.addActionListener(redAction);

    }

    class ColorAction implements ActionListener {
        private Color backgroundColor;

        public ColorAction(Color backgroundColor) {
            this.backgroundColor = backgroundColor;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            buttonPanel.setBackground(backgroundColor);
        }
    }


    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            JFrame frame = new ButtonFrame();
            frame.setTitle("ButtonTest");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
        });
    }
}

javax.swing.JButton

  • JButton(String label)
  • JButton(Icon icon)
  • JButton(String label, Icon icon):构造一个按钮。标签字符串可以是常规的文本或HTML。

java.awt.Container

  • Component add(Component c):将组件c添加到这个容器中。

3.3、简洁地指定监听器

使用lambda表达式:

redButton.addActionListener(evnet -> System.exit(0));

如果有多个相互关联的动作,可是实现一个辅助方法:

public void makeButton(String name, Color backgroundColor) {
    JButton button = new JButton(name);
    buttonPanel.add(button);
    button.addActionListener(event -> buttonPanel.setBackground(backgroundColor));
}

只需要调用:

makeButton("yellow", Color.YELLOW);
makeButton("blue", Color.BLUE);
makeButton("red", Color.RED);

3.4、适配器类

并不是所有的事件处理都像按钮点击那样简单。假设你想监视用户何时想要关闭主窗体,可能希望弹出一个对话框,只有在用户确认之后才退出程序。

当程序用户试图关闭一个窗口时,JFrame对象就是WindowEvent的事件源。如果希望捕获这个事件,就必须有一个合适的监听器对象,并将它添加到窗体的窗口监听器列表中。

WindowListener listener = ...;
frame.addWindowListener(listener);

窗口监听器必须是实现WindowListener接口的类的一个对象。WindowListener接口中实际上包含7个方法。窗体将调用这些方法响应7个不同的窗口事件。从它们的名字就可以得知这些方法的作用,只有一点需要说明:在Windows下,通常将图标化(iconified)称为最小化(minimized)。

public interface WindowListener extends EventListener {

	//在第一次使窗口可见时调用
    public void windowOpened(WindowEvent e);

	//当用户试图从窗口的系统菜单中关闭窗口时调用
    public void windowClosing(WindowEvent e);

	//当窗口被关闭时调用,作为对窗口调用dispose的结果
    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);
}

当然,我们可以定义一个实现这个接口的类,在windowClosing方法中添加一个System.exit(0)调用,而其他6个方法不做任何事情。不过,为6个没有任何操作的方法写代码显然是一个乏味的工作,没有人喜欢这样做。为了简化这个任务,每个含有多个方法的AWT监听器接口都配有一个适配器(adapter)类,这个类实现了接口中的所有方法,但每个方法并不做任何事情。

例如,WindowAdapter有7个什么也不做的方法。可以扩展适配器类来指定对某些事件的响应动作,而不必实现接口中的每一个方法(类似ActionListener的接口只有一个
方法,因此不需要适配器类)。

public class Terminator extends WindowAdapter {
    @Override
    public void windowClosing(WindowEvent e) {
        System.exit(0);
    }
}

现在,可以注册一个Terminator类型作为事件监听器:

Terminator listener = new Terminator();
frame.addWindowListener(listener);

3.5、动作

通常,启动同一个命令可以有多种方式。用户可以通过菜单、按键或工具栏上的按钮选择特定的功能。在AWT事件模型中这非常容易实现:将所有事件连接到同一个监听器。

例如,假设blueAction是一个动作监听器,它的actionPerformed方法可以将背景颜色改变成蓝色。可以关联一个监听器对象作为多个事件源的监听器:

  • 标记“Blue”的工具栏按钮
  • 标记“Blue”的菜单项
  • 按键CTRL+B

然后,无论是通过点击按钮、选择菜单还是按键,都会采用统一的方式处理改变背景颜色的命令。

Swing包提供了一种非常实用的机制来封装命令,并将它们关联到多个事件源,这就是Action接口。动作(action)是封装以下内容的一个对象:

  • 命令的说明(一个文本字符串和一个可选的图标)
  • 执行命令所需要的参数(例如,示例中所请求的颜色)
public interface Action extends ActionListener {
	
	//继承自ActionListener的方法
	public void actionPerformed(ActionEvent event);
	//获取动作对象中的任意键值对
    public Object getValue(String key);
	//设置动作对象中的任意键值对
    public void putValue(String key, Object value);
	//设置这个动作当前是否启用
    public void setEnabled(boolean b);
	//检查这个动作是否启用
    public boolean isEnabled();
	//在动作对象的属性发生变化时得到通知
    public void addPropertyChangeListener(PropertyChangeListener listener);
    public void removePropertyChangeListener(PropertyChangeListener listener);

}

Action预定义了很多动作,可以使用putValue和getValue设置/获取:

action.putValue(Action.NAME, "Blue");
action.putValue(Action.SMALL_ICON, new ImageIcon("blue-ball.gif"));
名称
NAME动作名,显示在按钮和菜单项上
SMALL_ICON存储小图标的地方,显示在按钮、菜单项或工具栏中
SHORT_DESCRIPTION图标的一个简短说明,显示在工具提示中
LONG_DESCRIPTION图标的详细说明;可能用在联机帮助中。没有Swing组件使用这个值
MEMONIC_KEY快捷键缩写;显示在菜单项中
ACCELERATOR_KEY存储加速键的地方;没有Swing组件使用这个值
ACTION_COMMAND_KEY原先在registerKeyboardAction方法中使用,但这个方法已经过时
DEFAULT可能很有用的全包型属性;没有Swing组件使用这个值

Java提供了一个AbstractAction给我们用于扩展,该类实现了除了actionPerformed方法之外的所有其它方法。

下面构造一个可以执行改变颜色命令的动作对象。首先存储这个命令的名称、图标和需要的颜色。将颜色存储在AsbstractAction类提供的名/值对表中。下面是ColorAction类的代码。构造器设置名/值对,而actionPerformed方法执行改变颜色的动作。

class ColorAction extends AbstractAction {

    public ColorAction(String name, Icon icon, Color c) {
        putValue(Action.NAME, name);
        putValue(Action.SMALL_ICON, icon);
        putValue("color", c);
        putValue(Action.SHORT_DESCRIPTION, "Set panel color to " + name.toLowerCase());
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        Color color = (Color) getValue("color");
        buttonPanel.setBackground(color);
    }
}

在测试类中,创建这个类的三个对象:

ColorAction yellowAction = new ColorAction("Yellow", new ImageIcon("icon.png"), Color.YELLOW);
ColorAction blueAction = new ColorAction("Blue", new ImageIcon("icon.png"), Color.BLUE);
ColorAction redAction = new ColorAction("Red", new ImageIcon("icon.png"), Color.RED);

接下来,将这个动作与一个按钮关联起来:

JButton yellowButton = new JButton(yellowAction);
JButton blueButton = new JButton(blueAction);
JButton redButton = new JButton(redAction);

在这里插入图片描述

最后,想要将这个动作对象添加到按键,使得用户从键盘键入命令时会执行这个动作。为了将动作与按键关联,首先需要生成KeyStroke类对象。它封装了对按键的描述:

KeyStroke ctrlBKey = KeyStore.getKeyStroke("ctrl B");

为了能够理解下一个步骤,需要知道键盘焦点(keyboard focus)的概念。用户界面中可能有许多按钮、菜单、滚动条以及其他的组件。当用户按键时,这个动作会被发送给拥有焦点的组件。通常可以从外观上看出拥有焦点的组件(但并不总是这样),例如,在Java观感中,有焦点的按钮在按钮文本周围有一个很细的矩形边框。可以使用Tb键在组件之间移动焦点。当按下空格键时,就会点击拥有焦点的按钮。还有一些按键会执行其他的动作,例如,箭头键可以移动滚动条。

不过,在这里的示例中,我们并不希望将按键发送给拥有焦点的组件。否则,每个按钮都需要知道如何处理CTRL+Y、CTRL+B和CTRL+R。

这是一个常见的问题,Swing设计者给出了一种很便捷的解决方案。每个JComponent有三个输入映射(imput map),分别将KeyStroke对象映射到关联的动作。这三个输入映射对应着三个不同的条件:

  • WHEN_FOCUSED:当这个组件拥有键盘焦点时
  • WHEN_ANCESTOR_FOCUSED_COMPONENT:当这个组件包含拥有键盘焦点的组件时
  • WHEN_IN_FOCUSED_WINDOW:当这个组件包含拥有键盘焦点的组件所在的同一个窗口中时

按键处理将按照以下顺序检查这些映射:

  1. 检查有输入焦点的组件的WHEN_FOCUSED映射。如果这个按键存在,而且启用了相应的动作,则执行这个动作,并停止处理。
  2. 从有输入焦点的组件开始,检查其父组件的WHEN ANCESTOR_OF_FOCUSED_COMPONENT映射。一
    旦找到这个按键的映射,而且相应的动作已经启用,就执行这个动作,并停止处理。
  3. 查看有输入焦点的窗口中的所有可见和已启用的组件,看是否在一个WHEN_IN_FOCUSED_WINDOW映射中注册了这个按键。给这些组件一个执行对应动作的机会(按照按键注册的顺序)。一旦执行第一个启用的动作,就停止处理。

可以使用getInputMap方法从组件中得到输入映射。例如:

InputMap imap = panel.getInputMap(JComponent.WHEN_FOCUSED);

WHEN_FOCUSED条件意味着在当前组件拥有键盘焦点时会查看这个映射。在这里,这不是我们想要的映射。某个按钮拥有输入焦点,而不是面板。其他的两个映射都能够很好地增加颜色改变按键。在示例程序中使用的是WHEN ANCESTOR_OF_FOCUSED_COMPONENT。

InputMap不是直接将KeyStroke对象映射到Action对象,而是先映射到任意对象上,然后由ActionMap类实现的第2个映射将对象映射到动作。这样可以更容易地在来自不同输入映射的按键中共享一个动作。

因而,每个组件都可以有三个输入映射和一个动作映射。为了将它们关联起来,需要为动作命名。可以如下将键关联到一个动作:

InputMap imap = buttonPanel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
imap.put(KeyStroke.getKeyStroke("ctrl Y"), "panel.yellow");
imap.put(KeyStroke.getKeyStroke("ctrl B"), "panel.blue");
imap.put(KeyStroke.getKeyStroke("ctrl R"), "panel.red");
ActionMap amap = buttonPanel.getActionMap();
amap.put("panel.yellow", yellowAction);
amap.put("panel.blue", blueAction);
amap.put("panel.red", redAction);

习惯上,会使用字符串"none"表示空动作,这样可以轻松取消一个按键动作:

imap.put(KeyStroke.getKeyStroke("ctrl C"), "none");

如何完成相同动作来响应按钮、菜单项或按键:

  1. 实现一个扩展AbstractAction类的类。可以使用同一个类表示多个相关的动作。
  2. 构造动作类的一个对象。
  3. 从动作对象构造一个按钮或菜单项。构造器将从动作对象读取标签文本和图标。
  4. 对于能够由按键触发的动作,必须额外多执行几步。首先找到窗口的顶层组件,例如,包含所有其他组件的面板。
  5. 然后,得到顶层组件的WHEN ANCESTOR OF FOCUS COMPONENT输入映射。为需要的按键创建一个KeyStroke对象。创建一个动作键对象,如描述动作的一个字符串。将(按键,动作键)对添加到输入映射中。
  6. 最后,得到顶层组件的动作映射。将(动作键,动作对象)对添加到映射中。

3.6、鼠标事件

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

  • 鼠标第一次被按下时调用mousePressed;
  • 鼠标被释放时调用mouseReleased;
  • 最后调用mouseClicked;

如果只对最终的,点击事件感兴趣,就可以忽略前两个方法。以MouseEvent类对象作为参数,调用getX和getY方法可以获得鼠标被按下时鼠标指针所在的x和y坐标。

要想区分单击、双击和三击,需要使用getClickCount方法。

在我们的示例程序中,提供了mousePressedmouseclicked方法。当鼠标,点击的像素在所有已绘制的小方块之外时,就会增加一个新的小方块。这个操作是在mousePressed方法中实现的,这样可以让用户的操作立即得到响应,而不必等到释放鼠标按键。如果用户在某个小方块中双击鼠标,就会将它擦除。由于需要知道点击次数,所以这个操作在mouseclicked方法中实现。

当鼠标在窗口上移动时,窗口将会收到一连串的鼠标移动事件。请注意:有两个独立的接口MouseListenerMouseMotionListener。这样做有利于提高效率。当用户移动鼠标时,会有大量鼠标事件,只关心鼠标,点击(click)的监听器就不会被多余的鼠标移动事件所干扰。

这里给出的测试程序将捕获鼠标动作事件,光标位于一个小方块之上时变成另外一种形状(十字)。这是使用Cursor类中的getPredefinedCursor方法完成的。

在这里插入图片描述

public class MouseComponent extends JComponent {

    private static final int DEFAULT_WIDTH = 300;
    private static final int DEFAULT_HEIGHT = 300;

    private static final int SIDELENGTH = 10;
    private ArrayList<Rectangle2D> squares;
    private Rectangle2D current;

    public MouseComponent() {
        squares = new ArrayList<>();
        current = null;

        addMouseListener(new MouseHandler());
        addMouseMotionListener(new MouseMotionHandler());
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
    }

    @Override
    protected void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D)g;
        //画矩形
        for (Rectangle2D r : squares) {
            g2.draw(r);
        }
    }

    //查找第一个包含指定点的矩形
    public Rectangle2D find(Point2D p) {
        for (Rectangle2D r : squares) {
            if (r.contains(p)) {
                return r;
            }
        }
        return null;
    }

    //添加一个矩形到集合
    public void add(Point2D p) {
        double x = p.getX();
        double y = p.getY();

        current = new Rectangle2D.Double(x - SIDELENGTH / 2, y -SIDELENGTH / 2, SIDELENGTH, SIDELENGTH);
        squares.add(current);
        repaint();
    }

    //从集合移除一个矩形
    public void remove(Rectangle2D s) {
        if (s == null) {
            return;
        }
        if (s == current) {
            current = null;
        }
        squares.remove(s);
        repaint();
    }

    private class MouseHandler extends MouseAdapter {
        @Override
        public void mousePressed(MouseEvent e) {
            //鼠标按下时,如果矩形之外,则新增一个矩形
            current = find(e.getPoint());
            if (current == null) {
                add(e.getPoint());
            }
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            //如果鼠标双击时位于矩形之内,则擦出当前这个矩形
            current = find(e.getPoint());
            if (current != null && e.getClickCount() >= 2) {
                remove(current);
            }
        }
    }


    private class MouseMotionHandler implements MouseMotionListener {

        @Override
        public void mouseDragged(MouseEvent e) {
            //鼠标在移动的同时按下鼠标,即拖动
            //移动矩形位置
            if (current != null) {
                int x = e.getX();
                int y = e.getY();
                current.setFrame(x - SIDELENGTH / 2, y - SIDELENGTH / 2, SIDELENGTH, SIDELENGTH);
                repaint();
            }
        }

        @Override
        public void mouseMoved(MouseEvent e) {
            //鼠标移动时,将光标变为十字形状
            if (find(e.getPoint()) == null) {
                setCursor(Cursor.getDefaultCursor());
            } else {
                setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
            }
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            JFrame frame = new MouseFrame();
            frame.setTitle("MouseTest");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
        });
    }
}

class MouseFrame extends JFrame {
    public MouseFrame() {
        add(new MouseComponent());
        pack();
    }
}

在这里插入图片描述

还有另外两个鼠标事件方法:mouseEntered和mouseExited.。这两个方法会在鼠标进入或移
出组件时被调用。

最后来解释如何监听鼠标事件。鼠标点击由mouseclicked方法报告,它是MouseListener接口的一个方法。很多应用只对鼠标点击感兴趣,而对鼠标移动并不感兴趣,但鼠标移动事件发生的频率又很高,因此将鼠标移动事件与拖动事件定义在一个名为MouseMotionListener的单独的接口中。

在示例程序中,我们对两种鼠标事件类型都感兴趣。这里定义了两个内部类:MouseHandler和MouseMotionHandler。MouseHandler类扩展了MouseAdapter类,这是因为它只定义了
5个MouseListener方法中的两个。MouseMotionHandler实现了MouseMotionListener接口,并定义了
这个接口中的两个方法。

3.7、AWT事件继承层次

EventObject类有一个子类AWTEvent,它是所有AWT事件类的父类。

在这里插入图片描述

有些Swing组件会生成更多其他事件类型的事件对象;它们都直接扩展自EventObject,.而不是AWTEvent。

事件对象封装了事件源与监听器通信的有关信息。在必要的时候,可以对传递给监听器对象的事件对象进行分析,我们在按钮例子中就利用getSource和getActionCommand方法分析了事件对象。

有些AWT事件类对Java程序员来说并不实用。例如,AWT会把PaintEvent对象插入事件队列中,但这些对象并没有传递给监听器。Java程序员并不监听绘制事件,实际上,需要覆盖paintComponent方法来控制重新绘制。另外,AWT还会生成很多只对系统程序员有用的事件,用于提供表义语言的输入系统、自动检测机器人等。

AWT将事件分为底层(low-level)事件语义(semantic)事件。语义事件是表示用户动作的事件,例如,点击按钮;因此,ActionEvent是一种语义事件。底层事件是使语义事件得以发生的事件。点击按钮时,这包括按下鼠标、一系列移动鼠标和松开鼠标(仅当鼠标在按钮区内松开)。或者如果用户用Tb键选择按钮,再用空格键激活按钮,就会发生击键事件。类似地,调节滚动条是一种语义事件,但拖动鼠标是底层事件。

下面是java.awt.event包中最常用的语义事件类:

  • ActionEvent(对应按钮点击、菜单选择、选择列表项或在文本域中按回车);
  • AdjustmentEvent(用户调节滚动条);
  • ItemEvent((用户从复选框或列表框中选择一项)。

常用的5个底层事件类是:

  • KeyEvent(一个键按下或释放);
  • MouseEvent(鼠标键按下、释放、移动或拖动);
  • MouseWheelEvent(鼠标滚轮转动);
  • FocusEvent(某个组件获得焦点或失去焦点);
  • WindowEvent(窗口状态改变);

4、监听器接口、事件和事件源总结

接口方法参数/访问方法时间源
ActionListeneractionPerformedActionEvent
.getActionCommand()
.getModifiers()
AbstractButton
JComboBox
JTextField
Timer
AdjustmentListenerasjustmentValueChangedAdjustmentEvent
.getAdjustable()
.getAdjustmentType()
.getValue()
JScrollbar
ItemListeneritemStateChangedItemEvent
.getItem()
.getItemSelectable()
.getStateChange()
AbstracButton
JComboBox
FocusListenerfocusGained
focusLost
FocusEvent
.isTemporary()
Component
KeyListenerkeypressed
keyReleased
keyTyped
KeyEvent
.getKeyChar()
.getKeyCode()
.getKeyModifiersText()
.getKeyText()
.isActionKey()
Component
MouseListenermousePressed
mouseReleased
mouseEntered
mouseExited
mouseClicked
MouseEvent
.getClickCount()
.getX()
.getY()
.getPoint()
.translatePoint()
Component
MouseMotionListenermouseDragged
mouseMoved
MouseEventComponent
MouseWheelListenermouseWheelMovedMouseWheelEvent
.getWheelRotation()
.getScrollAmount()
Component
WindowListenerwindowClosing
windowOpened
windowIconified
windowDeiconified
windowClosed
windowActivated
windowDeactivated
WindowEvent
.getWindow()
Window
WindowFocusListenerwindowGainedFocus
windowLostFocus
WindowEvent
.getOppositeWindow()
Window
WindowStateListenerwindowStateChangedWindowEvent
.getOldState()
.getNewState()
Window

5、首选项API

java.util.preferences API。在桌面程序中,你通常都会存储用户首选项,如用户最后处理的文件、窗口的最后位置,等等。

有些操作系统有一个存储配置信息的中心存储库。最著名的例子就是Microsoft Windows中的注册表。

Preferences类以一种平台无关的方式提供了这样一个中心存储库:

  • 在Windows中,Preferences类使用注册表来存储信息;
  • 在Linux上,信息存储在本地文件系统中;

当然,存储库实现对使用Preferences类的程序员是透明的。Preferences存储库有一个树状结构,节点路径名类似于/com/mycompany./myapp。类似于包名,只要程序员用逆置的域名作为路径的开头,就可以避免命名冲突。实际上,API的设计者就建议配置节点路径要与程序中的包名一致。

存储库的各个节点分别有一个单独的键/值对表,可以用来存储数值、字符串或字节数组,但不能存储可串行化的对象。API设计者认为对长期存储来说,串行化格式过于脆弱,并不合适。当然,如果你不同意这种看法,也可以用字节数组保存串行化对象。为了增加灵活性,可以有多个并行的树。每个程序用户分别有一棵树;另外还有一棵系
统树,可以用于存放所有用户的公共信息。Preferences类使用操作系统的“当前用户”概念来访问相应的用户树。

若要访问树中的一个节点,需要从用户或系统根开始:

Preferences root = Preferences.userRoot();

Preferences root = Preferences.systemRoot();

然后访问节点。可以直接提供一个节点路径名:

Preferences node = root.node("/com/mycompany/myapp");

如果节点的路径名等于类的包名,还有一种便捷方式可以获得这个节点。只需要得到这个类的一个对象,然后调用

Preferences node = Preferences.userNodeForPackage(obj.getClass());

Preferences node = Preferences.systemNodeForPackage(obj.getclass());

一般来说,obj往往是this引用。一旦得到了节点,可以用以下方法访问键/值表:

String get(String key,String defval)
int getInt(String key,int defval)
long getLong(String key,long defval)
float getFloat(String key,float defval)
double getDouble(String key,double defval)
boolean getBoolean(String key,boolean defval)
byte[]getByteArray(String key,byte[]defval)

需要说明的是,读取信息时必须指定一个默认值,以防止没有可用的存储库数据。之所以必须有默认值,有很多原因。可能由于用户从未指定过首选项,所以没有相应的数据。某些资源受限的平台可能没有存储库,移动设备有可能与存储库暂时断开了连接。

相对应地,可以用如下的put方法向存储库写数据:

put(String key,String value)
putInt(String key,int value)

可以用以下方法枚举一个节点中存储的所有键:

String[] keys()

目前没有办法找出一个特定键值的类型。

注意:节点名和键都最多只能有80个字符,字符串值最多可以有8192个字符。

类似Windows注册表的中心存储库通常都存在两个问题:

  • 它们会变成充斥着过期信息的“垃圾场”。
  • 配置数据与存储库纠缠在一起,所以很难把首选项迁移到新平台。

Preferences类为第二个问题提供了解决方案。可以通过调用以下方法导出一个子树(或者比较少见的,也可以是一个节点)的首选项:

void exportSubtree(OutputStream out)
void exportNode(OutputStream out)

数据用XML格式保存。可以通过调用以下方法将数据导入到另一个存储库:

void importPreferences(InputStream in)

下面是一个示例文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
<preferences EXTERNAL_XML_VERSION="1.0">
    <root type="user">
        <map/>
        <node name="pers">
            <map/>
            <node name="zhang">
                <map>
                    <entry key="height" value="200.0"/>
                    <entry key="left" value="1027.0"/>
                    <entry key="filename" value="/Users/acton_zhang/J2EE/MavenWorkSpace/json_demo/icon.png"/>
                    <entry key="top" value="380.0"/>
                    <entry key="width" value="300.0"/>
                </map>
            </node>
        </node>
    </root>
</preferences>

如果你的程序使用首选项,要让用户有机会导出和导入首选项,从而可以很容易地将设置从一台计算机迁移到另一台计算机。这个程序只保存了窗口的位置和最后加载的文件名。试着调整窗口的大小,然后导出你的首选项,移动
窗口,退出并重启应用。窗口的状态应该与之前退出时是一样的。导入你的首选项,窗口会恢复到之前的位置。

public class ImageViewer {
    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            JFrame frame = new ImageViewerFrame();
            frame.setTitle("ImageViewer");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
        });
    }
}

class ImageViewerFrame extends JFrame {
    private static final int DEFAULT_WIDTH = 300;
    private static final int DEFAULT_HEIGHT = 200;
    private String image;

    public ImageViewerFrame() throws HeadlessException {
        Preferences root = Preferences.userRoot();
        Preferences node = root.node("pers.zhang.ImageViewer");

        int left = node.getInt("left", 0);
        int top = node.getInt("top", 0);
        int width = node.getInt("width", DEFAULT_WIDTH);
        int height = node.getInt("height", DEFAULT_HEIGHT);

        setBounds(left, top, width, height);
        image = node.get("image", null);
        JLabel label = new JLabel();
        if (image != null) {
            label.setIcon(new ImageIcon(image));
        }

        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                node.putInt("left", getX());
                node.putInt("top", getY());
                node.putInt("width", getWidth());
                node.putInt("height", getHeight());
                node.put("image", image);
            }
        });

        add(label);

        JFileChooser chooser = new JFileChooser();
        chooser.setCurrentDirectory(new File("."));

        JMenuBar menuBar = new JMenuBar();
        setJMenuBar(menuBar);

        JMenu menu = new JMenu("File");
        menuBar.add(menu);

        JMenuItem openItem = new JMenuItem("Open");
        menu.add(openItem);
        openItem.addActionListener(event -> {
            int result = chooser.showOpenDialog(null);
            if (result == JFileChooser.APPROVE_OPTION) {
                image = chooser.getSelectedFile().getPath();
                label.setIcon(new ImageIcon(image));
            }
        });

        JMenuItem exitItem = new JMenuItem("Exit");
        menu.add(exitItem);
        exitItem.addActionListener(event -> System.exit(0));
    }
}

java.util.prefs.Preferences

  • Preferences userRoot():返回调用程序的用户的首选项根节点
  • Preferences systemRoot():返回系统范围的首选项根节点
  • Preferences node(String path):返回从当前节点由给定路径可以到达的节点。如果path是绝对路径(也就是说,以一个/开头),则从包含这个首选项节点的树的根节点开始查找。如果给定路径不存在相应的节点,则创建这样一个节点
  • Preferences userNodeForPackage(Class cl):返回当前用户树或系统树中的一个节点,其绝对节点路径对应类cl的包名
  • String[] keys():返回属于这个节点的所有键
  • String get(String key, String defval)
  • int getInt(String key, int defval)
  • long getLong(String key, long defval)
  • float getFloat(String key, float defval)
  • double getDouble(String key, boolean defval)
  • boolean getBoolean(String key, boolean defval)
  • byte[] getByteArray(String key, byte[] defval):返回与给定键关联的值,或者如果没有值与这个键关联、关联的值类型不正确或首选项存储库不可用,则返回所提供的默认值
  • void put(String key, String value)
  • void putInt(String key, int value)
  • void putLong(String key, long value)
  • void putFloat(String key, float value)
  • void putDouble(String key, double value)
  • void putBoolean(String key, boolean value)
  • void putByteArray(String key, byte[] value):在这个节点存储一个键/值对
  • void exportSubtree(OutputStream out):将这个节点以及其子节点的首选项写至指定流
  • void exportNode(OutputStream out):将这个节点(但不包括其子节点)的首选项写至指定的流
  • void importPreferences(InputStream in):导入指定流中包含的首选项
  • 24
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值