待更新

概述

在Java初期,包含了一个用于基本GUI程序设计的类库,称为AWT (Abstract Window Toolkit)。基本AWT库采用将处理用户界面元素的任务为派给每个目标平台的本地GUI工具箱的方式,由本地工具箱负责用户界面元素的创建和动作。
问题在于,虽然可以运行与多平台,但是不同平台的观感却很难统一。更坏的是,不同平台上的AWT用户界面库中存在着不同的bug,因而在每个平台上都必须测试应用程序。
后来,创建了IFC库,通过在空白窗口上绘制用户界面元素来统一在不同平台上的外观和动作。后来,经过合作完善,发展成了现在的Swing库。
现在,Swing是指“被绘制的”用户界面类,AWT是指像事件处理这样的窗口工具箱的底层机制。
在编写Swing程序时,可以为程序指定专门的观感。此外,还有Metal这种独立于平台的观感。

创建框架

顶层窗口被称为框架(Frame)。在AWT库中有一个称为Frame的类,用于描述顶层窗口。在Swing中这个库名为JFrame

绝大多数Swing组件都以“J”开头,例如JButtonJFrame等。

代码

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

public class FrameTest {
    public static void main(String[] args){
        EventQueue.invokeLater(new Runnable() { //分派线程
            @Override
            public void run() {
                SimpleFrame frame = new SimpleFrame();
                frame.setDefaultCloseOperation(
                        JFrame.EXIT_ON_CLOSE
                ); //关闭动作
                frame.setVisible(true); //可见
            }
        });
    }
}

class SimpleFrame extends JFrame{
    private static final int DEFAULT_WIDTH = 300;
    private static final int DEFAULT_HEIGHT = 200;

    public SimpleFrame(){ //300*200
        setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
    }
}

讲解

定义了一个子类SimpleFrame,构造器将框架大小设置为300*200像素。
main方法中,构造了一个SimpleFrame对象使它可见。
所有的Swing组件都必须由事件分派线程进行配置。
要定义一个用户关闭这个框架时的响应动作。

框架定位

setLocationsetBounds方法用于设置框架的位置。
setLocationByPlatform(true)可以让窗口系统控制窗口的位置。
setIconImage用于告诉窗口系统在标题栏、任务切换窗口等位置显示哪个图标。
setTitle用于改变标题栏的文字。
setResizalbe利用一个布尔值确定框架的大小是否允许用户改变。

框架属性

组件类的很多方法是通过获取/设置的方法对形式出现的。
这样获取/设置方法对被称为一种属性。属性包含属性名或类型。将getset之后的第一个字母改为小写就可以得到相应的属性名。

确定合适的框架大小

如果没有明确指定大小,所有框架的默认值为0*0像素。对于专业应用程序来说,应该检查屏幕分辨率,并且根据其分辨率编写代码重置框架大小。
为了得到屏幕的大小,需要按照下列步骤操作。调用Toolkit类的静态方法getDefaultToolkit得到一个Toolkit对象。然后调用getScreenSize方法,这个方法以Dimension对象的形式返回屏幕的大小。Dimension对象同时用共有实例变量widthheight保存屏幕的高度和宽度。

Toolkit kit = Toolkit.getDefaultToolkit(); 
// 获取系统工具箱
Dimension screenSize = kit.getScreenSize(); 
// 获取屏幕分辨率
int screenWidth = screenSize.width;
int screenHeight = screenSize.height;
setSize(screenWidth/2, screenHeight/2); 
// 根据屏幕分辨率设置窗口
setLocationByPlatform(true); 
// 由系统设置窗口位置
Image img; 
// 新建Image对象
img = new ImageIcon("1.png").getImage();
setIconImage(img); 
// 设置图标

这里有个小问题,我不知道怎么在macOS中取出图片作为图标。

一些提示

  • 如果框架中只包含标准的组件,如按钮和文本框,那么可以通过调用pack方法设置框架大小。框架将被设置为刚好能够放置所有组件的大小。在通常情况下,将程序的主框架尺寸设置为最大。
    frame.setExtendedState(Frame.MAXIMIZED_BOTH);

  • 记住用户定位应用程序的框架位置、重置框架大小,并且在应用程序再次启动时回复这些内容是个不错的想法。

  • 如果编写一个使用多个显示屏幕的程序,应该利用GraphicsEnvironmentGraphics Device类获得显示屏幕的大小。

  • GraphicsDevice类允许在全屏模式下执行应用程序。

完整示例

package SwingText;

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

/**
 * Create Date: 2017/3/10
 * User: Wzh
 * Project: Java_code
 * Create For: Swing Test Program
 */

public class SizedFrameTest {
    public static void main(String[] args){
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new SizedFrame();
                frame.setTitle("oujitsune");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setVisible(true);
            }
        });
    }
}

class SizedFrame extends JFrame{
    public SizedFrame(){
        Toolkit kit = Toolkit.getDefaultToolkit(); // 获取系统工具箱
        Dimension screenSize = kit.getScreenSize(); // 获取屏幕分辨率
        int screenWidth = screenSize.width;
        int screenHeight = screenSize.height;
        setSize(screenWidth/2, screenHeight/2); // 根据屏幕设置窗口占四分之一
        setLocationByPlatform(true); // 由系统设置窗口位置
        Image img; // 新建Image对象
        img = new ImageIcon("1.png").getImage();
        setIconImage(img); // 设置图标
    }
}

常用方法

java.awt.Component
boolean isVisible()
void setVisible(boolean b)
void setSize(int width, int height)
void setLocation(int x, int y)
void setBounds(int x, int y, int width, int height)
Dimension getSize()
void setSize(Dimension)

java.wat.Window
void toFront() // 置顶
void toBack() // 放在最后,并且重排所有可见窗口
boolean isLocationByPlatform()
void setLocationByPlatform(boolean b)
// 获取或设置locationByPlatform属性,由系统选择一个合适的位置

java.awt.Frame
boolean isResizable()
void setResizable(boolean b)
// 获取或设置resizable属性
String getTitle()
void setTitle(String s)
// 获取或设置Title属性
Image getIconImage()
void setIconImage(Image image)
// 获取或设置iconImage属性
boolean isUndecorated()
void setUndecorated(boolean b)
// 获取或设置undecorated属性,这个属性设置后,框架显示中将没有标题栏或关闭按钮这样的装饰。
int getExtendedState()
void setExtendedState(int state)
// 获取或设置窗口状态,为下列值之一
// Frame.NORMAL
// Frame.ICONIFIED
// Frame.MAXIMIZED_HORIZ
// Frame.MAXIMIZED_VERT
// Frame.MAXIMIZED_BOTH

java.awt.Toolkit
static Toolkit getDefaultToolkit()
// 获取默认工具箱
Dimension getScreenSize()
// 返回用户屏幕尺寸

javax.swing.ImageIcon
ImageIcon(String filename)
// 构造一个图标,将其图像存储在一个文件中
Image getImage()
// 获取该图标的图像

在组件中显示信息

在框架中显示信息可以将消息字符串直接绘制在框架中,但这不是一种好的编程习惯。在Java中,框架被设计为放置组件的一种容器,可以将菜单栏和其他的用户界面元素放置在其中。在通常情况下,应该在另一个组件上绘制信息。
JFrame有四层面板,其中根面板、层级面板和玻璃面板是用来组织菜单栏和内容创客以及实现观感的。内容窗格才是最让人关心的。
在设计框架的时候,要使用下列代码将所有的组件添加到内容窗格中:

Container contentPane = frame.getContentPane();
Component c = ...;
contentPane.add(c);

paintComponent方法有一个Graphics类型的参数,这个参数保存着用于绘制图像和文本的设置,例如设置的字体或当前的颜色。在Java中,所有的绘制都必须使用Graphics对象,其中包含了绘制图案、图像和文本的方法。

创建绘制组件

class Mycomponent extends JComponent{
    puiblic void paintComponent(Graphics g){
        // Code For Drwaing
    }
}

无论何种原因,只要窗口需要重新绘图,事件处理器就会通告组件,从而引发执行所有组件的paintComponent方法。
一定不要自己调用paintComponent方法。在应用程序需要重新绘图的时候,这个方法将会自动地被调用,不要人为地干预这个自动的处理过程。
如果需要强制刷新屏幕,就需要调用repaint方法。
paintComponent方法只有一个Graphics类型的参数。对于屏幕显示来说,Graphics对象的度量单位是像素。
显示文本是一种特殊的绘图。在Graphics类中有一个drawString方法,调用的语法格式为:g.drawString(text, x, y)

完整示例

public class SizedFrameTest {
    public static void main(String[] args){
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new WordsFrame();
                frame.setTitle("oujitsune");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setVisible(true);
            }
        });
    }
}

class WordsFrame extends JFrame{
    public WordsFrame(){
        add(new WordsComponent()); // 添加组件
        pack(); // 考虑组件大小调整窗口大小
    }
}

class WordsComponent 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("Hello world", MESSAGE_X, MESSAGE_Y);
    }

    public Dimension getPreferredSize(){ // 覆盖这个方法,返回首选宽度和高度
        return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
    }
}

常用方法

javax.swing.JFrame
Container getContentPane()
// 返回这个JFrame的内容窗格对象
Component add(Component c)
// 讲一个给定的组件添加到该框架的内容窗格中

java.awt.Component
void repaint()
// 重新绘制组件
Dimension getPreferredSize()
// 返回这个组件的首选大小

javax.swing.JComponent
void paintComponent(Graphics g)
// 覆盖这个方法来描述应该如何绘制自己的程序

javax.awt.Window
void pack()
// 调整窗口大小,要考虑到其组件的首选大小

2D绘图

使用Java 2D库绘制图形,需要获得一个Graphics 2D类对象。这个类是Graphics类的子类。paintComponent方法会自动获得一个Graphics 2D类的对象,我们只需要进行一次类型转换就可以了。

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

要想绘制图形,首先要创建一个实现了Shape接口的类的对象,然后调用Graphics2D类中的draw方法。
在Java 2D库中,内部的很多浮点计算都采用单精度float。因为集合计算的最终目的是要设置屏幕或打印机的像素,所以单精度完全可以满足要求了。然而有时候程序员处理float不太方便,所以每个图形类都有两个版本,一个是节省空间的float类型的坐标,另一个是double类型的坐标。
除了构造图像对象时,一般不需要使用烦人的内部类。

矩形和椭圆

Rectangle2DEllipse2D类都是由公共超类RecktangularShape继承来的。
构造时需要左上角位置和矩形的宽和高。
有时候并不知道左上角的位置,经常得到的是矩形的两个对角点,二这两个对角不一定是左上角和右下角,所以不能直接构造。
更好的方法是先创建一个恐惧小,然后调用setFrameFromDiagonal方法。
在构造椭圆时通常可以知道椭圆的中心、宽和高,而不是外接矩形的顶点。setFrameFrontCenter方法使用中心点,但仍然要给出四个顶点中的一个。因此,通常采用下列方式构造椭圆:
Line2D line = new Line2D.Double(start, end);
Line2D line = new Line2D.Double(startX, startY, ednX, endY);

完整实例

public class SizedFrameTest {
    public static void main(String[] args){
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new DrawFrame();
                frame.setTitle("oujitsune");
                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;

    public void paintComponent(Graphics g){
        Graphics2D g2 = (Graphics2D) g; // 重置Graphics对象

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

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

使用颜色

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

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

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

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

要设置背景颜色需要使用Component类中的setBackground方法。Component类是JComponent类的祖先。

// 不知为何无效
JFrame frame = new DrawFrame();                frame.setBackground(Color.BLUE);

文本使用字体

有时希望选用不同的字体显示文本。人们可以通过字体名指定一种字体,字体名由字体家族名和一个可选的后缀组成。
需要知道某台特点计算机上允许使用的字体,就需要调用GraphicsEnvironment类中的getAvailableFontFamilyNames方法。这个方法返回一个字符型数组,包含所有可用的字体名。GraphicisEnvironment类描述了用户系统的图形环境,为了得到这个类的对象,需要调用静态的getLocalGraphicsEnvironment方法。

创建字体对象

AWT中定义了五个逻辑字体名,这些字体将会被映射到客户机上的时机字体。要想使用某种字体绘制字符,必须首先利用指定的字体名、字体风格和字体大小来创建一个Font对象。
Font sansbold14 = new Font(“SansSerif”, Font.BOLD, 14);
构造器第二个参数可以指定字体的风格(常规、加粗、斜体或加粗体)。

Font.PLAIN
Font.BOLD
Font.ITALIC
Font.BOLD + Font.ITALIC

读取字体文件

可以读取TrueTypePostScriot Type 1格式的字体文件。这需要一个字体输入流,通常从磁盘文件或者URL读取,然后调用静态方法Font.createFont

URL url = new URL("http://www.font.com/Wingbats.ttf");
InputStream in = url.openStream();
Font f1 = Font.createFont(Font.TRUETYPE_FONT, in);

初始添加的字体为常规字体,大小为1。使用deriveFont方法得到希望大小的字体:Font f = f1.deriveFont(14.0f);

deriveFont方法有两个重载版本。一个(float版本)设置大小,一个(int版本)设置风格。所以f.deriveFont(14)设置的是字体风格,而不是大小。

字体使用

使用字体只需调用它Graphics实例的setFont方法。
要想得到屏幕设备字体属性的描述对象,需要调用Graphics2D类的getFontRenderContext方法。它将会返回一个FontRenderContext对象。可以直接将这个对象传递给Font类的getStringBounds方法:

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

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

常用方法

java.atw.Font
Font(String name, int style, int size)
// 创建一个新字体对象
String getFontName()
// 返回字体名
String getFamily()
// 返回字体家族名
String getName()
// 如果采用逻辑字体名创建字体,将返回逻辑字体
Rectangle2D getStringBounds(String s, FontRenderContext context)
// 返回包围这个字符串的矩形
// 这个矩形起点为极限,顶端y坐标等于上坡度的负值
// 矩形的高度等于上坡度、下坡度和行间距之和。宽度等于字符串的宽度
LineMetrics getLineMetrics(String s, FontRenderContext context)
// 返回测定字符串宽度的一个线性metrics对象
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.Graphics
Font getFont()
void setFont(Font font)
void drawString(String str, int x, int y)
// 采用当前字体和颜色绘制一个字符串

java.awt.Graphics2D
FontRenderContext getFontRenderContext()
// 返回这个图形文本中,指定字体特征的字体绘制环境
void drawString(String str, int x, int y)

javax.swing.JComponent
FontMetrics getFontMetrics(Font f)
// 获取给定字体的度量,LineMetrics类的前身

java.awt.FontMetrics
FontRenderContext getFontRenderContext()
// 返回字体的字体绘制环境

显示图像

可以读取本地图像。读取有很多种方法,比如之前使用过的ImageIcon类。