AWT(Abstract Window Toolkit):抽象窗口工具箱,一个用于基本GUI程序设计的类库。
Swing:一个用户界面库,基于AWT架构之上。
创建框架
图像
Swing:一个用户界面库,基于AWT架构之上。
创建框架
顶层窗口(就是没有包含在其他窗口中的窗口)被称为框架。AWT中有一个Frame类,用于描述顶层窗口。这个类的Swing版本名为JFrame,它扩展于Frame类。
JFrame示例:
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,这种框架没有意义。需要定义一个子类,使用构造器修改框架大小。
在每个Swing程序中,有两点技术需要强调:
首先,所有的Swing组件必须由事件调度进程(event dispatch thread)进行配置,线程将鼠标点击和键盘敲击控制转移到用户接口组件。
下面的代码片段是事件调度线程中的执行代码:
EventQueue.invokeLater(new Runnable() { public void run() { statements } });接下来,定义一个用户关闭这个框架时的响应动作。对于这个程序而言,只让程序简单地退出即可。选择这个响应动作的语句是:
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);在包含多个框架的程序中,不能在用户关闭其中的一个框架时就让程序退出。默认情况下,用户关闭窗口时只是将框架隐藏起来,而程序并没有终止。
框架起初是不可见的,程序员可以在框架第一次显示之前往其中添加组件。main方法调用框架的setVisible方法以显示框架。
在初始化语句结束后,main方法退出。此时并没有终止程序,终止的只是主线程。事件调度线程保持程序处于激活状态。
框架定位
- setLocation和setBounds方法用于设置框架的位置。
- setIconImage用于告诉窗口系统在标题栏、任务切换窗口等位置显示哪个图标。
- setTitle用于改变标题栏的文字。
- setResizable利用一个boolean值确定框架的大小是否允许用户改变。
框架属性
组件类的很多方法是以获取/设置这一对操作形式出现的,例如:
public String getTitle() public void setTitle(String title)针对get/set约定有一个例外:对于类型为boolean属性,获取类方法由is开头,例如:
public boolean isLocationByPlatform() public void setLocationByPlatform(boolean b)
决定框架大小
对于专业应用程序,应检查屏幕分辨率,并根据其分辨率编写代码重置框架大小。
为得到屏幕大小,首先需要调用Toolkit类的静态方法getDefaultToolkit得到一个Toolkit对象。然后,调用getScreenSize方法,返回一个保存着屏幕高宽的Dimension对象:
Toolkit kit = Toolkit.getDefaultToolkit(); Dimension screenSize = kit.getScreenSize(); int screenWidth = screenSize.width; int screenHeight = screenSize.height;下面,将框架大小设定为上面取值的50%,然后告知系统定位框架:
setSize(screenWidth / 2, screenHeight / 2); setLocationByPlatform(true);另外,为框架设置一个图标:
Image img = kit.getImage("icon.gif"); setIconImage(img);提示:
1. 可以通过调用下列方法将框架设置为最大:
frame.setExtendedState(Frame.MAXIMIZED_BOTH);2. 牢记用户定位应用程序的框架位置、重置框架大小,并且在应用程序再次启动时恢复这些内容是不错的想法。
3. 如果编写一个使用多个屏幕的应用程序,应该利用GraphicsEnviroment和GraphicsDevice类获得显示屏幕的大小。
4. GraphicsDevice类允许在全屏模式下执行应用程序。
在组件中显示信息
在JFrame中有根面板、层级面板、内容面板和玻璃面板四个面板。
其中,根面板、层级面板和玻璃面板人们并不关心。Swing程序员最关心的是内容窗格(content pane)。
在设计框架时,使用下列代码将所有的组件添加到内容窗格中:
Container contentPane = frame.getContentPane(); Component c = ...; contentPane.add(c);绘制一个组件,需要定义一个扩展JComponent的类,并覆盖其中的paintComponent方法。
paintComponent方法有一个Grpahics类型的参数,保存着用于绘制图像和文本的设置。在Java中,所有的绘制都必须使用Graphics对象。
下面代码给出了如何创建一个能够进行绘制的组件:
class MyComponent extends JComponent { public void paintComponent(Graphics g) { code for drawing } }只要窗口需要重新绘图,事件处理器就会通告组件,从而引发执行所有组建的paintComponent方法。
如果需要强制刷新屏幕,可以调用repaint方法。它将引发采用相应配置的Graphics对象调用所有组件的paintComponent方法。
显示文本是一种特殊的绘图。在Graphics类中有一个drawString方法,调用语法格式为:
g.drawString(x, y)
2D图形
要想使用Java 2D库绘制图形,需要获得一个Graphics2D类对象。这个类是Graphics类的子类。
paintComponent方法会自动获得一个Graphics2D类对象,只需进行一次类型转换即可:
public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D)g; }Java2D库包含描述直线、矩形、椭圆的类(全部实现了Shape接口):
- Line2D
- Rectangle2D
- Ellipse2D
绘制图形时,首先创建一个实现了Shape接口的类的对象,然后调用Graphics2D类中的draw方法:
Rectangle2D rect = ...; draw(rect);为了避免后缀和类型转换带来的麻烦,2D库设计者为每个图形类提供两个版本(float和double):
Rectangle2D.Float
Rectangle2D.Double
当创建Rectangle2D.Float对象时,应提供float型数值坐标,而创建Rectangle2D.Double对象时,应提供double型数值的坐标:
Rectangle2D.Float floatRect = new Rectangle2D.Float(10.0F, 25.0F, 22.5F, 20.0F); Rectangle2D.Double doubleRect = new Rectangle2D.Double(10.0, 25.0, 22.5, 20.0);Rectangle2D方法的参数和返回值均为double类型。例如,即使Rectangle2D.Float对象存储float类型的宽度,getWidth方法也返回一个double值。
Rectangle类和Point类分别扩展于Rectangle2D类和Point2D类,并用整型存储矩形和点。
Rectangle2D和Ellipse2D对象很容易构造,需要给出:
- 左上角的x和y坐标
- 宽和高
有时候不知道左上角位置。经常得到的是矩形两个对焦点,但这两点不一定是左上角和右下角,不能直接这样构造矩形:
Rectangle2D rect = new Rectangle2D.Double(px, py, qx - px, qy - py); // ERROR如果p不是左上角,矩形就为空。
处理方法:首先创建一个空矩形,然后调用setFrameFromDiagonal方法:
Rectangle2D rect = new Rectangle2D.Double(); rect.setFrameFromDiagonal(px, py, qx, qy);或者,如果已知顶点分别用Point2D类型的两个对象p和q表示,就应该这样调用:
rect.setFrameFromDiagonal(p, q);构造椭圆时,通常可以知道椭圆的中心,宽和高。setFrameFromCenter方法使用中心点,但仍要给出四个顶点中的一个。
常用做法是:
颜色
Ellipse2D ellipse = new Ellipse2D.Double(centerX - width / 2, centerY - height / 2, width, height);构造直线,需提供起点和终点,这两个点既可以使用Point2D对象表示,也可以用一对数值表示:
Line2D line = new Line2D.Double(start, end); Line2D line = new Line2D.Double(startX, startY, endX, endY);
颜色
使用Graphics2D类的setPaint方法可以为图形环境上所有后续的绘制操作选择颜色,例如:
g2.setPaint(Color.RED); g2.drawString("Warning!", 100, 100);将draw替换为fill就可以用一种颜色填充一个封闭图形,例如:
Rectangle2D rect = ...; g2.setPaint(Color.RED); g2.fill(rect); // fills rect with red color在java.awt.Color类中提供了13个预定义的常量,表示13种标准颜色:
BLACK, BLUE, CYAN, DARK_GRAY, GRAY, GREEN, LIGHT_GRAY, MAGENTA, ORANGE, PINK, RED, WHITE, YELLOW
可以通过红、绿和蓝三色成分来创建一个Color对象,范围0~255,调用格式为:
Color(int redness, int greenness, int blueness)例:
g2.setPaint(new Color(0, 128, 128)); // a dull blue-green g2.drawString("Welcome!", 75, 125);注:如果使用Graphics对象,而不是Graphics2D对象,就需要使用setColor方法设置颜色。
要想设置背景颜色,就需要使用Component类中的setBackground方法:
MyComponent p = new MyComponent(); p.setBackground(Color.PINK);另外,还有一个setForeground方法,用来设定在组件上进行绘制时使用的默认颜色。
Color类中的brighter()方法和darker()方法分别加亮或变暗当前颜色。使用brighter方法也是加亮条目的好办法。希望达到耀眼的效果,可以使用三次:c.brighter().brighter().brighter()。
SystemColor类中预定义了很多类的名字,这个类中的常量,封装了用户系统的各个元素的颜色,例如:
为文本设定特殊字体
可以通过调用GraphicsEnviroment类中的getAvailableFontFamilyName方法返回计算机上允许使用的字体。这个方法返回一个字符型数组,包含了所有可用的字体名。为了得到GraphicsEnviroment类的对象,需要调用静态的getLocalGraphicsEnviroment方法,下面程序将打印出系统上所有字体名:
p.setBackground(SystemColor.window)它将把面板的背景颜色设定为用户界面上所有窗口使用的默认颜色。
desktop | 桌面背景颜色 | textText | 文本前景颜色 |
activeCaption | 标题背景颜色 | textInactiveText | 非活动组件文本颜色 |
activeCaptionText | 标题文本颜色 | textHighlight | 高亮度文本背景颜色 |
activeCaptionBorder | 标题文本边框颜色 | textHighlightText | 高亮度文本文本颜色 |
inactiveCaption | 非活动标题背景颜色 | control | 组件背景颜色 |
inactiveCaptionText | 非活动标题文本颜色 | controlText | 组件文本颜色 |
inactiveCaptionBorder | 非活动标题文本边框颜色 | controlLtHighlight | 组件浅高亮度颜色 |
Window | 窗口背景 | controlHighlight | 组件高亮度颜色 |
windowBorder | 窗口边框颜色 | controlShadow | 组件阴影颜色 |
windowText | 窗口内文本颜色 | controlDkShadow | 组件暗阴影颜色 |
menu | 菜单背景颜色 | scrollbar | 滚动条背景颜色 |
menuText | 菜单文本颜色 | info | 帮助区文本的颜色 |
text | 文字背景颜色 | infoText | 帮助区的文本颜色 |
为文本设定特殊字体
可以通过调用GraphicsEnviroment类中的getAvailableFontFamilyName方法返回计算机上允许使用的字体。这个方法返回一个字符型数组,包含了所有可用的字体名。为了得到GraphicsEnviroment类的对象,需要调用静态的getLocalGraphicsEnviroment方法,下面程序将打印出系统上所有字体名:
import java.awt.*; public class ListFonts { public static void main(String[] args) { String[] fontNames = GraphicsEnvironment .getLocalGraphicsEnvironment() .getAvailableFontFamilyNames(); for (String fontName : fontNames) System.out.println(fontName); } }为了创建一个公共基准,AWT定义了五个逻辑(logical)字体名:
SansSerif
Serif
Monospaced
Dialog
DialogInput
这些字体将被映射到客户机上的实际字体。
另外,Sun JDK包含3种字体:Lucida Sans,Lucida Bright,Lucida Sans Typewriter。
要想使用某种字体绘制字符,必须首先利用指定的字体名、字体风格和字体大小来创建一个Font对象,例如:
Font sansbold14 = new Font("SansSerif", Font.BOLD, 14);第三个参数是以点数目计算字体大小,每英寸包含72个点。
第二个参数可以给出字体风格(常规/加粗/斜体/加粗斜体):Font.PLAN,Font.BOLD,Font.ITALIC,Font.BOLD + Font.ITALIC。
可以通过从磁盘文件或URL读取TrueType或PostScript Type 1格式的字体文件,然后调用静态方法Font.createFont:
URL url = new URL("http://www.fonts.com/Wingbats.ttf"); InputStream in = url.openStream(); Font f1 = Font.createFont(font, TRUETYPE_FONT, in);上面定义的字体为常规字体,大小为1.可以使用deriveFont方法得到希望大小的字体:
Font f = f1.deriveFont(14.0F);注:deriveFont有两个重载版本,除了上述版本,还有一个版本接受一个int参数,用来设置字体风格,所有deriveFont(14)设置的是字体风格,不是大小。
下面代码使用系统中14号加粗的标准sans serif字体显示字符串“Hello, world”:
Font sansbold14 = new Font("SansSerif", Font.BOLD, 14); g2.setFont(sansbold14); String message = "Hello, world!"; g2.drawString(message, 75, 100);接下来,将字符串绘制在面板中央,而不是任意位置。因此,需要知道字符串占据的宽和高的像素数量。这两个值取决于三个因素:
- 使用的字体
- 字符串
- 绘制字体的设备
要想得到屏幕设备字体属性的描述对象,需要调用Graphics2D类中的getFontRenderContext方法。它将返回一个FontRenderContext类对象。可以直接将这个对象传递给Font类的getStringBounds方法:
getStringBounds方法返回包围字符串的矩形。这个矩形始于字符串的基线,矩形顶部的y坐标为负值。可以采用下面的方法获得字符串的宽度、高度和上坡度:
FontRenderContext context = g2.getFontRenderContext(); Rectangle2D bounds = f.getStringBounds(message, context);
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();下面代码使用了所有这些信息,将字符串显示在包围它的组件中央:
FontRenderContext context = g2.getFontRenderContext(); Rectangle2D bounds = f.getStringBounds(message, context); // (x, y) = top left corner of text double x = (getWidth() - bounds.getWidth()) / 2; double y = (getHeight() - bounds.getHeight()) / 2; // add ascent to y to reach the baseline double ascent = -bounds.getY(); double baseY = y + ascent; g2.drawString(message, (int)x, (int)baseY);
注:若要在paintComponent方法外部计算布局图的尺度,应使用JComponent类的getFontMetrics方法,而后紧接着调用getFontRenderContext:
FontRenderContext context = getFontMetrics(f).getFontRenderContext();
图像
如果图像存储在本地文件中,就应该调用:
String filename = "..."; Image image = ImageIO.read(new File(filename));否则,应提供URL:
String urlname = "..."; Image image = ImageIO.read(new URL(urlname));如果图像不可用,read方法将抛出一个IOException。
可以使用Graphics类的的drawImage方法将图像显示出来:
public void paintComponent(Graphics g) { ... g.drawImage(image, x, y, null); }