十、图形程序设计

一、概述

AWT:基本 AWT 库采用将处理用户界面元素 的任务委派给每个目标平台(Windows、Solaris、 Macintosh 等)的本地 GUI 工具箱的方式, 由本地 GUI 工具箱负责用户界面元素的创建和动作。

 Swing 没有完全替代AWT, 而是基于AWT 架构之上。Swing 仅仅提供了能力更 加强大的用户界面组件。 尤其在采用 Swing 编写的程序中,还需要使用基本的 AWT 处 理事件。从现在开始,Swing 是指 “被绘制的” 用户界面类;AWT 是指像事件处理这样 的窗口工具箱的底层机制。

由于下列几点无法抗拒的原因,人们选择 Swing:

• Swing拥有一个丰富、 便捷的用户界面元素集合。

• Swing 对底层平台依赖的很少,因此与平台相关的 bug 很少。

• Swing 给予不同平台的用户一致的感觉。

不过,上面第三点存在着一个潜在的问题: 如果在所有平台上用户界面元素看起来都一 样,那么它们就有可能与本地控件不一样,而这些平台的用户对此可能并不熟悉。 Swing采用了一种很巧妙的方式来解决这个问题。在程序员编写 Swing 程序时,可以为 程序指定专门的“ 观感”。有些用户希望 Java 应用使用其平台的本地观感, 但另外一些用户可能更喜欢 Metal 或某 种第三方观感。

二、创建框架

JFrame 是极少数几个不绘制在画布上的 Swing组件之一。因此,它的修 饰部件(按钮、标题栏、图标等)由用户的窗口系统绘制, 而不是由 Swing绘制。

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

/**
 * @version 1.33 2015-05-12
 * @author Cay Horstmann
 */
public class SimpleFrameTest
{
   public static void main(String[] args)
   {
      EventQueue.invokeLater(() ->
         {
            SimpleFrame frame = new SimpleFrame();
            //关闭所有框架装饰
            frame.setUndecorated(true);
            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()
   {
      setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
   }
}

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

三、框架定位

•setLocation 和 setBounds方法用于设置框架的位置。

• setlconlmage 用于告诉窗口系统在标题栏、任务切换窗口等位置显示哪个图标。

• setTitle 用于改变标题栏的文字。

• setResizable 利用一个 boolean值确定框 架的大小是否允许用户改变。

对于框架来说,setLocation 和 setBounds 中的坐标均相对于整个屏幕。在第 12 章 中将会看到, 在容器中包含的组件所指的坐标均相对于容器。

可以让窗口系统控制窗口的位置,如果在显示窗口之前调用

setLocationByPlatform(true):

窗口系统会选用窗口的位置(而不是大小),通常是距最后一个显示窗口很少偏移量的位置。

3.1 框架属性

组件类的很多方法是以获取 / 设置方法对形式出现的。如果类没有匹配的实例域,我们将不清楚(也不关心)如何实现获取和设置方法。或许 只是读、写实例域, 或许还执行了很多其他的操作。例如, 当标题发生变化时,通知给窗口 系统。针对 get/set 约定有一个例外: 对于类型为 boolean 的属性, 获取方法由 is开头。例如, 下面两个方法定义了 locationByPlatform 属性:

public boolean isLocationByPlatform()

public void setLocationByPlatform(boolean b)

3.2 确定合适的框架大小

 如果没有明确地指定框架的大小,所有框架的默认值为0x0像素。为了让示例 程序尽可能地简单,这里将框架的大小重置为大多数情况下都可以接受的显示尺寸。然而,对 于专业应用程序来说,应该检查屏幕的分辨率, 并根据其分辨率编写代码重置框架的大小,如 在膝上型电脑的屏幕上, 正常显示的窗口在高分辨率屏幕上可能会变成一张邮票的大小.

package sizedFrame;

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

/**
 * @version 1.34 2015-06-16
 * @author Cay Horstmann
 */
public class SizedFrameTest
{
   public static void main(String[] args)
   {
      EventQueue.invokeLater(() ->
         {
            JFrame frame = new SizedFrame();
            frame.setTitle("SizedFrame");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
         });
   }
}

class SizedFrame extends JFrame
{
   public SizedFrame()
   {
      // get screen dimensions

      Toolkit kit = Toolkit.getDefaultToolkit();
      Dimension screenSize = kit.getScreenSize();
      int screenHeight = screenSize.height;
      int screenWidth = screenSize.width;

      // set frame width, height and let platform pick screen location

      setSize(screenWidth / 2, screenHeight / 2);
      setLocationByPlatform(true);

      // set frame icon

      Image img = new ImageIcon("icon.gif").getImage();
      setIconImage(img);      
   }
}

四、在组件中显示信息

  其中的根面板,层级面板和玻璃面板人们并不太关心;它们是用来组织菜单栏和内容窗格以及实现观感的。 Swing 程序员最关心的是内容窗格(contentpane)。在设计框架的时候, 要使用下列代码将所 有的组件添加到内容窗格中。

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

  在 Java SE 1.4及以前的版本中,JFrame类中的 add方法抛出了一个异常信息“ Do not use JFrame.add().Use JFrame.getContentPaneQ.add instead”。如今,JFrame.add方法不再显示,这些提示信息,只是简单地调用内容窗格的 add。

  在这里,打算将一个绘制消息的组件添加到框架中。绘制一个组件,需要定义一个扩展 JComponent 的类,并覆盖其中的 paintComponent 方法。paintComponent 方法有一个 Graphics类型的参数, 这个参数保存着用于绘制图像和文本 的设置, 例如,设置的字体或当前的颜色。在 Java中,所有的绘制都必须使用 Graphics对 虡牧其中包含了绘制图案、 图像和文本的方法。

   无论何种原因, 只要窗口需要重新绘图, 事件处理器就会通告组件,从而引发执行所有 组件的 paintComponent 方法。一定不要自己调用 paintComponent 方法。在应用程序需要重新绘图的时候, 这个方法将 被自动地调用,不要人为地干预这个自动的处理过程。何种类别的动作会触发这个自动响应过程呢? 例如,在用户扩大窗口或极小化窗口,然 后又恢复窗口的大小时会引发重新绘图。如果用户弹出了另外一个窗口,并且这个窗口覆盖 了一个已经存在的窗口,使得覆盖的窗口不可见,则此时被覆盖的应用程序窗口被破坏,需 要重新绘制(图形系统不保存下面的像素)。当然,窗口第一次显示时,需要处理一些代码, 主要包含确定绘制最初元素的方式以及位置。如果需要强制刷新屏幕, 就需要调用 repaint 方法, 而不是 paintComponent 方法。 它将引发采用相应配置的 Graphics 对象调用所有组件的 paintComponent 方法 .

  在框架中填入一个或多个组件时, 如果你只想使用它们的首选大小, 可以调用 pack 方法 而不是 setSize 方法.

  有些程序员更喜欢妒展JPanel, 而不是JComponent。JPanel 是一个可以包含其他 组件的容器(container), 但同样也可以在其上面进行绘制。有一点不同之处是, 面板不 透明, 这意味着需要在面板的边界内绘制所有的像素。最容易实现的方法是, 在每个面 板子类的 paintComponent 方法中调用super.paintComponent 来用背景色绘制面板:

class NotHel1oWorldPanel extends JPanel {
    public void paintComponent(Graphics g) {

        super.paintComponent(g);

        //code for drawing
    }
}
package notHelloWorld;

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

/**
 * @version 1.33 2015-05-12
 * @author Cay Horstmann
 */
public class NotHelloWorld
{
   public static void main(String[] args)
   {
      EventQueue.invokeLater(() ->
         {
            JFrame frame = new NotHelloWorldFrame();
            frame.setTitle("NotHelloWorld");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
         });
   }
}

/**
 * A frame that contains a message panel
 */
class NotHelloWorldFrame extends JFrame
{
   public NotHelloWorldFrame()
   {
      add(new NotHelloWorldComponent());
      pack();
   }
}

/**
 * A component that displays a message.
 */
class NotHelloWorldComponent 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("Not a Hello, World program", MESSAGE_X, MESSAGE_Y);
   }
   
   public Dimension getPreferredSize() { return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT); }
}

五、处理2D图形

自从 Java 版本 1.0 以来, Graphics类就包含绘制直线、矩形和楠圆等方法。但是,这些 绘制图形的操作能力非常有限。例如, 不能改变线的粗细,不能旋转这些图形。Java SE 1.2引人了Java 2D 库,这个库实现了一组功能强大的图形操作。要想使用 Java 2D 库绘制图形,需要获得一个 Graphics2D 类对象。自从 Java SE 2 版本以来,paintComponent 方法就会自动地获得一个 Graphics2D 类 对象,我们只需要进行一次类型转换就可以了。

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

Java2D 库采用面向对象的方式将几何图形组织起来。包含描述直线、矩形的椭圆的类,这些类全部实现了 Shape接口。

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

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

在 1.0 的绘制方法中, 采用的是整型像素 坐标, 而Java 2D图形采用的是浮点坐标.在 Java 2D 库中, 内部的很多浮点计算都采用单精度 float.然而,有时候程序员处理 float并不太方便, 这是因为 Java程序设计语言在将 double值 转换成 float 值时必须进行类型转换.

Rectang1e2D r = . . . 
float f = r.getWidth(); // Error 

 这条语句也无法通过编译, 其原因与前面一样。由于 getWidth方法的返回类型是 double, 所以需要进行类型强制转换:

(float) r.getWidth(): // OK

由于后缀和类型转换都有点麻烦, 所以 2D库的设计者决定为每个图形类提供两个版本: 一个是为那些节省空间的程序员提供的 float类型的坐标;另一个是为那些懒惰的程序员提供 的 double类型的坐标.

这个库的设计者选择了一种古怪且在最初看起来还有些混乱的方式进行了打包。看一下 RectangldD类, 这是一个拥有两个具体子类的抽象类,这两个具体子类也是静态内部类:

当创建一个Rectangle2D.Float 对象时, 应该提供float 型数值的坐标 。 而创建 Rectangle2D. Double 对象时,应该提供 double 型数值的坐标。实际上, 由于Rectangle2D.Float 和 Rectangle2D.Double 都扩展于Rectangle2D 类, 并 且子类只覆盖了 RectangldD 超类中的方法, 所以没有必要记住图形类型。可以直接使用 Rectangle2D 变量保存矩形的引用。也就是说, 只有在构造图形对象时,才需要使用烦人的内部类.Rectangle2D方法的参数和返回值均为 double类型。例如, 即使 Rectangle2D.Float 对象 存储 float 类型的宽度,getWidth方法也返回一个 double 值。

   从 Java 1.0遗留下来的两个类也被放置在图形类 的继承层次中。它们是Rectangle 和Point类,分别扩展于Rectangle2D 和Point2D类,并用整型坐标存储矩形和点.

 

package draw;

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

/**
 * @version 1.33 2007-05-12
 * @author Cay Horstmann
 */
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);
         });
   }
}

/**
 * A frame that contains a panel with drawings
 */
class DrawFrame extends JFrame
{
   public DrawFrame()
   {      
      add(new DrawComponent());
      pack();
   }
}

/**
 * A component that displays rectangles and ellipses.
 */
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;

      // 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);
   }
   
   public Dimension getPreferredSize() { return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT); }
}

六、使用颜色

只需要将调用 draw 替换为调用 fill 就可以用一种颜色填充一个封闭图形(例如: 矩形或 椭圆)的内部: fill 方法会在右侧和下方少绘制一个像素

Rectangle2D rect = ...; 
g2.setPaint(Color.RED);
g2.fi11(rect); // fills rect with red

Color 类用于定义颜色。在java.awt.Color 类中提供了 13 个预定义的常量。

如果使用 Graphics 对象, 而不是 Graphics2D 对象, 就需要使用 setColor 方法设置颜色。

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

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

七、文本使用特殊字体

字 体名由“ Helvetica” 这样的字体家族名(font familyname) 和一个可选的“ Bold” 后缀组成。 例如,Helvetica和Helvetica Bold属于Helvetica家族的字体 。

 要想知道某台特定计算机上允许使用的字体, 就需要调用 GraphicsEnvironment 类中的 getAvailableFontFamilyNames方法。

    public static void main(String[] args) {
        String [] strs = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
        for(String s:strs){
            System.out.println(s);
        }
    }

为了创建一个公共基准, AWT 定义了五个逻辑(logical) 字体名: SansSerif Serif Monospaced Dialog Dialoglnput

这些字体将被映射到客户机上的实际字体。例如,在 Windows 系统中,SansSerif将被映 射到 Arial。

  要想使用某种字体绘制字符, 必须首先利用指定的字体名、字体风格和字体大小来创建— 个 Font 类对象。

在 Font 构造器中,提供字体名的位置也可以给出逻辑字体名称。另外,利用 Font 构造器的 第二个参数可以指定字体的风格(常规、加粗、斜体或加粗斜体),下面是几个字体风格的值:

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

可以读取 TrueType 或 PostScriot Type 1 格式的字体文件。这需要一个字体输人流 通 常从磁盘文件或者 URL 读取(有关流的更详细信息请参看卷n第 1 章)。然后调用静态方法 Font.createFont

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

上面定义的字体为常规字体,大小为 1。可以使用 deriveFont方法得到希望大小的字体:

Font f = fl.deriveFont(14.0F);

 deriveFont 方法有两个重栽版本。一个(有一个 float 参数)设置字体的大小;另一 个(有一个 int 参数)设置字体风格。所以 f.deriveFont(14) 设置的是字体风格, 而不是大 小(其结果为斜体, 因为 14 的二进制表示的是 ITALIC, 而不是BOLD)。

Java 字体包含了通用的ASCII 字符和符号。 例如, 如果用Dialog字体打印字符 ‘ W2297’,那么就会看到 ® 字符。只有在 Unicode 字符集中定义的符号才能够使用。

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

  • 使用的字体(在前面列举的例子中为 sansserif,加粗,14 号);
  • 字符串(在前面列举的例子中为“ Hello,World”) ;
  • 绘制字体的设备(在前面列举的例子中为用户屏幕)。

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

Font sansbo1dl4 = new Font(wSansSerif", Font.BOLD, 14);

g2.setFont(sansbo1dl4);

String message = "Hello, World!";

g2.drawString(message, 75, 100);

FontRenderContext context = g2.getFontRenderContext();

Rectangle2D bounds = sansboldl4..getStringBounds(message, context);

如果需要在 paintComponent 方法外部计算布局图 的尺度, 不能从Graphics2D 对象得到字体绘制环境。换 作调用 JComponent 类的 getFontMetrics 方法, 而后紧接 着调用 getFontRenderContext:

FontRenderContext context = getFontMetncs(f).getFontRenderContext();
package font;

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

/**
 * @version 1.34 2015-05-12
 * @author Cay Horstmann
 */
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);
         });
   }
}

/**
 * A frame with a text message component
 */
class FontFrame extends JFrame
{
   public FontFrame()
   {      
      add(new FontComponent());
      pack();
   }
}

/**
 * A component that shows a centered message in a box.
 */
class FontComponent extends JComponent
{
   private static final int DEFAULT_WIDTH = 300;
   private static final int DEFAULT_HEIGHT = 200;

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

      String message = "Hello, World!";

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

      // measure the size of the message

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

      // set (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;

      // draw the message

      g2.drawString(message, (int) x, (int) baseY);

      g2.setPaint(Color.LIGHT_GRAY);

      // draw the baseline

      g2.draw(new Line2D.Double(x, baseY, x + bounds.getWidth(), baseY));

      // draw the enclosing rectangle

      Rectangle2D rect = new Rectangle2D.Double(x, y, bounds.getWidth(), bounds.getHeight());
      g2.draw(rect);
   }
   
   public Dimension getPreferredSize() { return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT); }
}

八、显示图像

package image;

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

/**
 * @version 1.34 2015-05-12
 * @author Cay Horstmann
 */
public class ImageTest
{
   public static void main(String[] args)
   {
      EventQueue.invokeLater(() ->
         {
            JFrame frame = new ImageFrame();
            frame.setTitle("ImageTest");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
         });
   }
}

/**
 * A frame with an image component
 */
class ImageFrame extends JFrame
{
   public ImageFrame()
   {
      add(new ImageComponent());
      pack();
   }
}

/**
 * A component that displays a tiled image
 */
class ImageComponent extends JComponent
{
   private static final int DEFAULT_WIDTH = 300;
   private static final int DEFAULT_HEIGHT = 200;

   private Image image;

   public ImageComponent()
   {
      image = new ImageIcon("blue-ball.gif").getImage();
   }

   public void paintComponent(Graphics g)
   {
      if (image == null) return;

      int imageWidth = image.getWidth(null);
      int imageHeight = image.getHeight(null);

      // draw the image in the upper-left corner

      g.drawImage(image, 0, 0, null);
      // tile the image across the component

      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);
   }
   
   public Dimension getPreferredSize() { return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT); }
}

发布了141 篇原创文章 · 获赞 65 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_39326472/article/details/103160127