十、圖形程序設計

一、概述

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

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章