Java读书笔记10 图形程序设计基础
《JAVA 2核心技术 卷Ⅰ》第七章:图形程序设计,读书笔记,第一部分(7.1-7.5)。
主要内容包括了:Swing概述、创建框架、框架定位等基本方法列表、在面板中显示文字信息的方法及程序、Java 2D库的基础部分介绍。
一.Swing概述
在Java 1.0刚出现的时候,包含了一个用于基本GUI程序设计的类库,Sun将它称为抽象窗口工具箱(Abstract Window Toolkit, AWT)。基本AWT库采用将处理用户界面元素的任务委派给每个目标平台的本地GUI工具箱的方式,由本地GUI工具箱负责用户界面元素的创建和动作。例如,如果使用最初的AWT在Java窗口中放置一个文本框,就会有一个底层的“对等体”文本框,用它来实际地处理文本输入。
1996年,Netscape创建了一种称为IFC(Internet Foundation Class)的GUI库,它采用了与AWT完全不同的工作方式。它将按钮、菜单这样的用户界面元素绘制在空白窗口上,而对等体只需要创建和绘制窗口。Sun与Netscape合作完善了这种方式,创建了一个名为Swing的用户界面库。
现在,Swing是不对等基于GUI工具箱的正式名字,它已是Java基础类库(Java Foundation Class, JFC)的一部分。
Swing没有完全替代AWT,而是基于AWT架构之上。Swing仅仅提供了能力更加强大的用户界面组件。尤其在采用Swing编写的程序中,还需要使用基本的AWT处理事件。
从现在开始,“Swing”是指“被绘制的”非对等体用户界面类;“AWT”是指像事件处理这样的窗口工具箱的底层机制。
在程序员编写Swing程序时,可以为程序指定专门的“观感”,如“Metal”观感,它支持多种主题,默认的主题叫“Ocean”。
绝大多数Java用户界面程序设计都采用Swing,但有一个特别的例外,Eclipse集成开发环境使用了一种与AWT类似,且被称为SWT的图形工具箱,它可以映射到不同平台的本地组件上。
二.创建框架
在Java中,顶层窗口(没有包含在其他窗口中的窗口)被称为框架(frame)。在AWT中有一个称为Frame的类,用于描述顶层窗口。这个类的Swing版本名为JFrame,它扩展于Frame类。JFrame是极少数几个不绘制在画布上的Swing组件之一。因此,它的修饰部件(按钮、标题、图标等)由用户的窗口系统绘制,而不是由Swing绘制。
下面是一个在屏幕中显示一个空框架的简单程序:
/** @version 1.31 2004-05-03 @author Cay Horstmann */ import javax.swing.*; public class SimpleFrameTest { public static void main(String[] args) { SimpleFrame frame = new SimpleFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } } class SimpleFrame extends JFrame { public SimpleFrame() { setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; }
在默认情况下,框架的大小为0*0像素,定义了一个子类SimpleFrame,将框架的大小设置为300*200像素。
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);定义了用户关闭这个框架时的响应动作,对于这个程序而言,是让程序退出。在默认的情况下,用户关闭窗口时只是将框架隐藏了起来,而程序没有终止。
简单地构造一个框架并不自动显示出来,框架起初是不可见的,为了显示框架,需要调用setVisible方法。
然后,main方法退出,退出main并没有终止程序,终止的只是主线程。目前显示的框架激活了用户界面线程,以保持程序处于激活状态。
程序运行结果,是显示一个小窗口。
三.框架定位
JFrame类本身只包含若干个改变框架外观的方法,然而,它通过继承从各个超类中继承了许多用于处理框架大小和位置的方法。
最重要的有下面几个:
dispose方法用于关闭窗口,并回收创建窗口所使用的全部系统资源。
setIconImage方法用于将窗口极小化时的图标设置为Image对象(图标化)。
setTitle方法用于改变标题栏中的文本。
setResizable方法利用一个boolean值确定框架的大小是否允许用户改变。
JFrame类 (1.7)(http://docs.oracle.com/javase/7/docs/api/index.html)
可以看到继承关系。下面介绍各个超类中比较常用的功能:
java.awt.Component
方法 |
功能及说明 |
boolean isVisible() |
检查组件是否设置为可见。组件最初是可见的,但是JFrame这样的顶层组件除外。 |
void setVisible(boolean b) |
显示或隐藏组件 |
boolean isShowing() |
检查该组件是否显示在屏幕中,如果是,它必须是可见的,且包含在一个正在显示的容器中 |
boolean isEnabled() |
检查组件是否被激活。被激活的组件可以接受键盘输入。组件最初是被激活的。 |
void setEnabled(boolean b) |
激活或禁用组件 |
Point getLocation() |
返回该组件左上角的位置,这是相对于外围容器左上角的位置 |
Point getLocationOnScreen() |
返回该组件左上角的位置,这个位置用屏幕坐标表示。 |
void setBounds(int x, int y, int width, int height) |
移动并重置组件的大小。 |
void setLocation(int x, int y) void setLocation(Point p) |
将组件移到一个新的位置上。如果该组件不是顶层组件,x,y是容器坐标,若是顶层组件,则为屏幕坐标。 |
Dimension getSize() |
返回该组件当前的尺寸 |
void setSize(int width, int height) void setSize(Dimension d) |
重置组件的大小 |
java.awt.Window
方法 |
说明 |
void toFront() |
将该窗口显示在其他窗口前面 |
void toBack() |
将该窗口移到桌面窗口栈的后面,并重新排列所有的可见窗口 |
java.awt.Frame
方法 |
说明 |
void setResizable(boolean b) |
决定用户是否可以重置框架的大小 |
void setTitle(String s) |
将框架标题栏中的文字设置为字符串s |
void setIconImage(Image image) |
框架显示图标的图像 |
void setUndecorated(boolean b) |
为true时取消框架装饰 |
boolean isUndecorated() |
如果框架无装饰,返回true |
int getExtendedState() void setExtendedState(int state) |
获得或设置窗口状态,状态是下列值之一: Frame.NORMAL Frame.ICONIFIED Frame.MAXIMIZED_HORIZ Frame.MAXIMIZED_VERT Frame.MAXIMIZED_BOTH |
另外,比较常用的一个类(不是JFrame的超类):
java.awt.Toolkit
方法 |
说明 |
static Toolkit getDefaultToolkit() |
返回默认的工具箱 |
Dimension getScreenSize() |
返回用户屏幕的尺寸 |
Image getImage(String filename) |
加载文件名为filename的图像 |
如果没有明确指定框架的大小,所有的框架默认为0x0像素。对于专业应用程序来说,应该检查用户屏幕的分辨率,并根据其分辨率编写代码重置框架的大小。
为了得到屏幕的大小,可以如下操作:
// get screen dimensions Toolkit kit = Toolkit.getDefaultToolkit(); Dimension screenSize = kit.getScreenSize(); int screenHeight = screenSize.height; int screenWidth = screenSize.width;
四.在面板中显示信息
可以将消息字符串直接绘制在框架中,但是这并不是一种良好的编程习惯。在通常情况下,应该在一个称为面板(panel)的组件上绘制信息,并将这个面板添加到框架中。
JFrame中有四层面板:根面板、层级面板、玻璃面板、内容面板(或称内容窗格)(content pane)。
在设计框架的时候,要使用add()方法将所有的组件添加到内容窗格中。
面板用JPanel类实现,这种用户界面元素有两个很有用的特性:
1.包含一个可以用于绘图的表面。
2.本身也是一个容器。
为了使面板更加具有实际使用价值,应该通过继承创建一个新类,然后覆盖或添加一些方法,来获得所需要的附加功能。
尤其是,为了能够在面板上进行绘图,需要:
1.定义一个扩展于JPanel的类;
2.在这个类中,覆盖paintComponnet方法。
paintComponnet方法定义在JComponent类中,这个类是所有非窗口Swing组件的超类。该方法有一个Graphics类型的参数。Graphics对象保存着用于绘制图像和文本的设置,例如,设置当前的字体或当前的颜色。在Java中,所有的绘图都必须通过Graphics对象,其中包括绘制图案、图像和文本的方法。
不管何种原因,只要窗口需要重新绘图,事件处理器就会通告组件,从而引发执行所有组件的paintComponent方法。一定不要自己调用paintComponnet方法。
如果需要强制刷新屏幕,就需要调用repaint方法。
显示文本
显示文本是一种特殊的绘图,Graphics类中有一个drawString方法,调用格式为:
g.drawString(text,x,y);
完整的程序代码如下:
/** @version 1.31 2004-05-03 @author Cay Horstmann */ import javax.swing.*; import java.awt.*; public class NotHelloWorld { public static void main(String[] args) { NotHelloWorldFrame frame = new NotHelloWorldFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } } /** A frame that contains a message panel */ class NotHelloWorldFrame extends JFrame { public NotHelloWorldFrame() { setTitle("NotHelloWorld"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // add panel to frame NotHelloWorldPanel panel = new NotHelloWorldPanel(); add(panel); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; } /** A panel that displays a message. */ class NotHelloWorldPanel extends JPanel { public void paintComponent(Graphics g) { super.paintComponent(g); g.drawString("Not a Hello, World program", MESSAGE_X, MESSAGE_Y); } public static final int MESSAGE_X = 75; public static final int MESSAGE_Y = 100; }
NotHelloWorldPanel类扩展了JPanel类,在填充面板的背景颜色时,JPanel类有自己的处理方式,为了确保超类完成自己那部分工作,必须在绘制自己的内容之前调用super.paintComponent(g);
五.2D图形
Java 2D库实现了一组功能强大的图形操作。
要想使用Java 2D库绘制图形,需要获得一个Graphics2D类对象。这个类是Graphics类的子类。如果使用的是一个支持Java 2D的JDK版本,paintComponent方法就会自动地获得一个Graphics2D类对象,只需进行一次类型转换就可以了,如下:
public void paintComponent(Graphics g) { Graphics2D g2=(Graphics2D) g; … }
Java 2D库采用面向对象的方式将几何图形组织起来。包含描述直线、矩形和椭圆的类:Line2D、 Rectangle2D、 Ellipse2D,这些类全部实现了Shape接口。
要想绘制图形,首先创建一个实现了Shape接口的类的对象,然后调用Graphics2D类中的draw方法。例如:
Rectangle2D rect=…
g2.draw(rect);
float和double
Java 2D库为每个图形类提供float类型坐标和double类型坐标两个版本。
如Rectangle2D类,这是一个拥有两个具体子类的抽象类,这两个具体子类也是静态内部类:
Rectangle2D.Float
Rectangle2D.Double
实际上,由于Rectangle2D.Float和Rectangle2D.Double都扩展于Rectangle2D类,并且子类只覆盖了Rectangle2D超类中的方法,所以没有必要记住图形类型,可以直接使用Rectangle2D变量保存矩形的引用。
Rectangle2D floatRect = new Rectangle2D.Float(10.0F, 25.0F, 22.5F, 20.0F); Rectangle2D doubleRect = new Rectangle2D.Double(10.0, 25.0, 22.5, 20.0);
只有在构造图形对象时,才需要使用内部类。
Rectangle2D方法的参数和返回值类型均为double,例如,即使Rectangle2D.Float对象存储着float类型的宽度,getWidth方法也返回一个double值。
对Rectangle2D的论述也适用于其他图形类。
图形构造方法
Rectangle2D和Ellipse2D类都是由公共超类RectangularShape继承来的。RectangularShape定义了多个有关图形操作的通用方法,比较常用的有getWidth、getHeight、getCenterX、getCenterY等。
Rectangle2D和Ellipse2D对象很容易构造,需要给出左上角的xy坐标和宽度和高度。例如:
Ellipse2D ellipse = new Ellipse2D.Double(150,200,100,50);
用左上角位于(150,200),宽100,高50的外接矩形构造一个椭圆。
在构造椭圆(这种情况还出现在构造其他图形时)时,通常能够知道椭圆的中心、宽和高,而不是外接矩形的顶点,setFrameFromCenter方法使用中心点,但仍然要给出四个顶点中的一个。
经常得到的是矩形的两个对角点,而这两个对角点并不一定是左上角和右下角,不能简单地这样构造:
Rectangle2D rect = new Rectangle2D.Double(px, py, qx-px, qy-py);
如果两个坐标之差为负值,矩形就为空。
在这种情况下,首先创建一个空矩形,然后调用setFrameFromDiagonal方法。
Rectangle2D rect = new Rectangle2D.Double(); rect. setFrameFromDiagonal(px, py, qx, qy);
要想构造一条直线,就提供起点和终点,这两个点可以用Point2D对象表示,也可以使用一对数值表示。
示例程序,功能:绘制一个矩形,这个矩形的内接椭圆,矩形的一条对角线,以矩形中心为原点的一个圆。
/** @version 1.31 2004-05-03 @author Cay Horstmann */ import java.awt.*; import java.awt.geom.*; import javax.swing.*; public class DrawTest { public static void main(String[] args) { DrawFrame frame = new DrawFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } } /** A frame that contains a panel with drawings */ class DrawFrame extends JFrame { public DrawFrame() { setTitle("DrawTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // add panel to frame DrawPanel panel = new DrawPanel(); add(panel); } public static final int DEFAULT_WIDTH = 400; public static final int DEFAULT_HEIGHT = 400; } /** A panel that displays rectangles and ellipses. */ class DrawPanel extends JPanel { public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; // draw a rectangle double leftX = 100; double topY = 100; double width = 200; double height = 150; Rectangle2D rect = new Rectangle2D.Double(leftX, topY, width, height); g2.draw(rect); // draw the enclosed ellipse Ellipse2D ellipse = new Ellipse2D.Double(); ellipse.setFrame(rect); g2.draw(ellipse); // draw a diagonal line g2.draw(new Line2D.Double(leftX, topY, leftX + width, topY + height)); // draw a circle with the same center 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); } }