十二、Swing用戶界面組件

一、SWING和MVC設計模式

每個組件都有三個要素:

  • 內容, 如: 按鈕的狀態(是否按下), 或者文本框的文本
  • 外觀(顏色,大小等)。
  • 行爲(對事件的反應)。

在程序員使用 Swing組件時, 通常不需要考慮模型 -視圖-控制器體系結構。每個用戶 界面元素都有一個包裝器類(如 JButton 或 JTextField) 來保存模型和視圖。當需要查詢內容 (如文本域中的文本)時, 包裝器類會向模型詢問並且返回所要的結果。當想改變視圖時(例 如, 在一個文本域中移動光標位置), 包裝器類會把此請求轉發給視圖。然而,有時候包裝器 轉發命令並不得力。在這種情況下,就必須直接地與模型打交道(不必直接操作視圖— —這 是觀感代碼的任務)。

對於大多數組件來說, 模型類將實現一個名字以 Model結尾的接口,例如,按鈕就實現 了 ButtonModel 接口。實現了此接口的類可以定義各種按鈕的狀態。實際上,按鈕並不複雜, 在 Swing 庫中有一個名爲 DefaultButtonModel 的類就實現了這個接口。

 每個 JButton 對象都存儲了一個按鈕模型對象, 可以用下列方式得到它的引用。

Button button = new JButton("Blue"); 
ButtonModel model = button.getModel();

通常, 每 個 Swing組件都有一個相關的後綴爲UI 的視圖對象, 但並不是所有的 Swing組件都有專門 的控制器對象。

在閱讀 JButton底層工作的簡介之後可能會想到: JButton究竟是什麼? 事實上, 它僅僅 是一個繼承了 JComponent 的包裝器類,JComponent 包含了一個 DefauUButtonModel 對象, 一些視圖數據(例如按鈕標籤和圖標)和一個負責按鈕視圖的 BasicButtonUI 對象

 

二、佈局管理概述

  通常,組件放置在容器中, 佈局管理器決定容 器中的組件具體放置的位置和大小。

  這幾個按鈕被放置在一個 JPane͉對象中,且用流佈局管理器(flow layout manager) 管 理, 這是面板的默認佈局管理器。

  通常,組件放置在容器中, 佈局管理器決定容 器中的組件具體放置的位置和大小。 按鈕、文本域和其他的用戶界面元素都繼承於 Component 類, 組件可以放置在面板這樣的容器 中。由於 Container類繼承於 Component類,所以 容器也可以放置在另一個容器中

   可惜的是, 繼承層次有兩點顯得有點混亂。 首先, 像 JFrame 這樣的頂層窗口 是 Container 的子類, 所以也是 Component 的子類, 但卻不能放在其他容器內。 另外, JComponent 是 Container 的子類, 但不直接繼承 Component, 因此, 可以將其他組件添 置到 JButton 中。(但無論如何, 這些組件無法顯示出來)

  每個容器都有一個默認的佈局管理器,但可以重新進行設置

 

 

        JPanel jPanel = new JPanel();
        jPanel.setLayout(new FlowLayout(FlowLayout.CENTER,100,20));
        jPanel.add(new Button("a"));
        jPanel.add(new Button("a"));
        jPanel.add(new Button("a"));
        add(jPanel);

2.1邊框佈局

  邊框佈局管理器ҁborderlayout manager) 是每個 JFrame 的內容窗格的默認佈局管理器。

先放置邊緣組件,剩餘的可用空間由中間組件佔據。當 圖 12-9 邊框佈局 容器被縮放時,邊緣組件的尺寸不會改變,而中部組件的大 小會發生變化。在添加組件時可以指定 BorderLayout類中的 CENTER、NORTH、SOUTH̵EAST 和WEST常量。並非需要佔用所有的位置,如果沒有提供任何值,系統默認爲CENTER。

與流佈局不同,邊框佈局會擴展所有組件的尺寸以便填滿可用空間(流佈局將維持每個 組件的最佳尺寸)。當將一個按鈕添加到容器中時會出現問題:

frame.add(yellowButton, BorderLayout.SOUTH); // don't

解決這個問題的常見方法是使用另外一個面板(panel)。例如,如圖 12-11 所示。屏幕底 部的三個按鈕全部包含在一個面板中。這個面板被放置在內容窗格的南部。

2.2 網格佈局

網格佈局像電子數據表一樣, 按行列排列所有的組件。不過,它 的每個單元大小都是一樣的。當縮放窗口時,計算器按鈕將隨之變大或 變小,但所有的按鈕尺寸始終保持一致。在將組件添加到框架之後,調用了 pack方法。 這個方法使用所有組件的最佳大小來計算框架的高度和寬度

package calculator;

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

/**
 * A panel with calculator buttons and a result display.
 */
public class CalculatorPanel extends JPanel
{
   private JButton display;
   private JPanel panel;
   private double result;
   private String lastCommand;
   private boolean start;

   public CalculatorPanel()
   {
      setLayout(new BorderLayout());

      result = 0;
      lastCommand = "=";
      start = true;

      // add the display

      display = new JButton("0");
      display.setEnabled(false);
      add(display, BorderLayout.NORTH);

      ActionListener insert = new InsertAction();
      ActionListener command = new CommandAction();

      // add the buttons in a 4 x 4 grid

      panel = new JPanel();
      panel.setLayout(new GridLayout(4, 4));

      addButton("7", insert);
      addButton("8", insert);
      addButton("9", insert);
      addButton("/", command);

      addButton("4", insert);
      addButton("5", insert);
      addButton("6", insert);
      addButton("*", command);

      addButton("1", insert);
      addButton("2", insert);
      addButton("3", insert);
      addButton("-", command);

      addButton("0", insert);
      addButton(".", insert);
      addButton("=", command);
      addButton("+", command);

      add(panel, BorderLayout.CENTER);
   }

   /**
    * Adds a button to the center panel.
    * @param label the button label
    * @param listener the button listener
    */
   private void addButton(String label, ActionListener listener)
   {
      JButton button = new JButton(label);
      button.addActionListener(listener);
      panel.add(button);
   }

   /**
    * This action inserts the button action string to the end of the display text.
    */
   private class InsertAction implements ActionListener
   {
      public void actionPerformed(ActionEvent event)
      {
         String input = event.getActionCommand();
         if (start)
         {
            display.setText("");
            start = false;
         }
         display.setText(display.getText() + input);
      }
   }

   /**
    * This action executes the command that the button action string denotes.
    */
   private class CommandAction implements ActionListener
   {
      public void actionPerformed(ActionEvent event)
      {
         String command = event.getActionCommand();

         if (start)
         {
            if (command.equals("-"))
            {
               display.setText(command);
               start = false;
            }
            else lastCommand = command;
         }
         else
         {
            calculate(Double.parseDouble(display.getText()));
            lastCommand = command;
            start = true;
         }
      }
   }

   /**
    * Carries out the pending calculation.
    * @param x the value to be accumulated with the prior result.
    */
   public void calculate(double x)
   {
      if (lastCommand.equals("+")) result += x;
      else if (lastCommand.equals("-")) result -= x;
      else if (lastCommand.equals("*")) result *= x;
      else if (lastCommand.equals("/")) result /= x;
      else if (lastCommand.equals("=")) result = x;
      display.setText("" + result);
   }
}

三、文本輸入

3.1 文本域

]Panel panel = new JPanel(); 
JTextField textField = new JTextField("Default input", 20); 
panel,add(textField);

在這個示例中,寬度值爲 20“ 列”。但是,這裏所說 的列不是一個精確的測量單位。一列就是在當前使用的字體下一個字符的寬度。如果需要在運行時重新設置列數,可以使用 setColumns方法。

使用 setColumns 方法改變了一個文本域的大小之後, 需要調用包含這個文本框的 容器的 revalidate 方法。

revalidate 方法會重新計算容器內所有組件的大小,並且對它們重新進行佈局。調用 revalidate 方法以後, 佈局管理器會重新設置容器的大小, 然後就可以看到改變尺寸後的 文本域了。

revalidate 方法是JComponent 類中的方法。它並不是馬上就改變組件大小, 而是給 這個組件加一個需要改變大小的標記。這樣就避免了多個組件改變大小時帶來的重複計 算。但是, 如果想重新計算一個JFrame 中的所有組件, 就必須調用 validate 方法 JFrame 沒有擴展JComponent。

 如果想要將 getText 方法返回的文本域中的內容的前後空格去掉,就應該 調用 trim方法:String text = textField.getText().trim();

3.2 標籤和標籤組件

標籤是容納文本的組件,它們沒有任何的修飾(例如沒有邊緣), 也不能響應用戶輸入。 可以利用標籤標識組件。例如: 與按鈕不同,文本域沒有標識它們的標籤 要想用標識符標 識這種不帶標籤的組件,應該:

  • 1) 用相應的文本構造一個 JLabel 組件。
  • 2) 將標籤組件放置在距離需要標識的組件足夠近的地方, 以便用戶可以知道標籤所標 識的組件。

Swing Constants 接口中的常量來指定排列方式。在這個接口中定義了幾個很有用的常量, 如 LEFT、 RIGHT、CENTER、NORTH、 EAST 等。JLabel 是實現這個接口的一個 Swing類。因此,可以指定右對齊標籤:

JLabel label = new JLabel("User name: ", SwingConstants.RIGHT);

或者 JLabel label = new JLabel("User name: ", JLabel.RIGHT);

 可以在按鈕、標籤和菜單項上使用無格式文本或 HTML 文本

label = new JLabel("<html><b>Required</b> entry:</html>°);

3.3 密碼域

3.4 文本區

在 JTextArea組件的構造器中,可以指定文本區的行數 和列數。例如:

textArea = new JTextArea(8, 40); // 8 lines of 40 columns each

用戶並不受限於輸人指定的行數和列數。 當輸人過長時,文本會滾動

如果文本區的文本超出顯示的範圍, 那麼剩下的文本就會被剪裁掉。可以通過開啓換行 特性來避免裁剪過長的行:

textArea.setLineWrap(true): // long lines are wrapped

換行只是視覺效果;文檔中的文本沒有改變,在文本中並沒有插入“ \n” 字符

3.5 滾動窗格

在 Swing中, 文本區沒有滾動條。 如果需要滾動條, 可以將文本區插人到滾動窗格 (scroll pane) 中。 要想 爲組件添加滾動條, 只需將它們放人一個滾動窗格中即可。

package text;

import java.awt.BorderLayout;
import java.awt.GridLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingConstants;

/**
 * A frame with sample text components.
 */
public class TextComponentFrame extends JFrame
{
   public static final int TEXTAREA_ROWS = 8;
   public static final int TEXTAREA_COLUMNS = 20;

   public TextComponentFrame()
   {
      JTextField textField = new JTextField();
      JPasswordField passwordField = new JPasswordField();

      JPanel northPanel = new JPanel();
      northPanel.setLayout(new GridLayout(2, 2));
      northPanel.add(new JLabel("User name: ", SwingConstants.RIGHT));
      northPanel.add(textField);
      northPanel.add(new JLabel("Password: ", SwingConstants.RIGHT));
      northPanel.add(passwordField);

      add(northPanel, BorderLayout.NORTH);

      JTextArea textArea = new JTextArea(TEXTAREA_ROWS, TEXTAREA_COLUMNS);
      JScrollPane scrollPane = new JScrollPane(textArea);

      add(scrollPane, BorderLayout.CENTER);

      // add button to append text into the text area

      JPanel southPanel = new JPanel();

      JButton insertButton = new JButton("Insert");
      southPanel.add(insertButton);
      insertButton.addActionListener(event ->
         textArea.append("User name: " + textField.getText() + " Password: "
            + new String(passwordField.getPassword()) + "\n"));

      add(southPanel, BorderLayout.SOUTH);
      pack();
   }
}

四、選擇組件

  4.1 複選框

當複選框獲得焦點時, 用戶也可以通過按空格鍵來切換選擇。

package checkBox;

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

/**
 * A frame with a sample text label and check boxes for selecting font
 * attributes.
 */
public class CheckBoxFrame extends JFrame
{
   private JLabel label;
   private JCheckBox bold;
   private JCheckBox italic;
   private static final int FONTSIZE = 24;

   public CheckBoxFrame()
   {
      // add the sample text label

      label = new JLabel("The quick brown fox jumps over the lazy dog.");
      label.setFont(new Font("Serif", Font.BOLD, FONTSIZE));
      add(label, BorderLayout.CENTER);

      // this listener sets the font attribute of
      // the label to the check box state

      ActionListener listener = event -> {
         int mode = 0;
         if (bold.isSelected()) mode += Font.BOLD;
         if (italic.isSelected()) mode += Font.ITALIC;
         label.setFont(new Font("Serif", mode, FONTSIZE));
      };

      // add the check boxes

      JPanel buttonPanel = new JPanel();

      bold = new JCheckBox("Bold");
      bold.addActionListener(listener);
      bold.setSelected(true);
      buttonPanel.add(bold);

      italic = new JCheckBox("Italic");
      italic.addActionListener(listener);
      buttonPanel.add(italic);

      add(buttonPanel, BorderLayout.SOUTH);
      pack();
   }
}

4.2 單選鈕

構造器的第二個參數爲 true表明這個按鈕初始狀態是被選擇,其他按鈕構造器的這個參 數爲 false。注意,按鈕組僅僅控制按鈕的行爲,如果想把這些按鈕組織在一起佈局, 需要把 它們添加到容器中,如 JPanel。

package radioButton;

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

/**
 * A frame with a sample text label and radio buttons for selecting font sizes.
 */
public class RadioButtonFrame extends JFrame
{
   private JPanel buttonPanel;
   private ButtonGroup group;
   private JLabel label;
   private static final int DEFAULT_SIZE = 36;

   public RadioButtonFrame()
   {      
      // add the sample text label

      label = new JLabel("The quick brown fox jumps over the lazy dog.");
      label.setFont(new Font("Serif", Font.PLAIN, DEFAULT_SIZE));
      add(label, BorderLayout.CENTER);

      // add the radio buttons

      buttonPanel = new JPanel();
      group = new ButtonGroup();

      addRadioButton("Small", 8);
      addRadioButton("Medium", 12);
      addRadioButton("Large", 18);
      addRadioButton("Extra large", 36);

      add(buttonPanel, BorderLayout.SOUTH);
      pack();
   }

   /**
    * Adds a radio button that sets the font size of the sample text.
    * @param name the string to appear on the button
    * @param size the font size that this button sets
    */
   public void addRadioButton(String name, int size)
   {
      boolean selected = size == DEFAULT_SIZE;
      JRadioButton button = new JRadioButton(name, selected);
      group.add(button);
      buttonPanel.add(button);

      // this listener sets the label font size

      ActionListener listener = event -> label.setFont(new Font("Serif", Font.PLAIN, size));

      button.addActionListener(listener);
   }
}

4.3 邊框

如果在一個窗口中有多組單選按鈕,就需要用可視化的形式指明哪些按鈕屬於同一組。 Swing 提供了一組很有用的邊框(borders) 來解決這個問題。可以在任何繼承了 JComponent 的組件上應用邊框。最常用的用途是在一個面板周圍放置一個邊框,然後用其他用戶界面元 素(如單選鈕)填充面板。

1 ) 調用 BorderFactory 的靜態方法創建邊框。下面是幾種可選的風格(如圖 12-16所示):

•凹斜面 •凸斜面 •蝕刻 •直線 •蒙版 •空(只是在組件外圍創建一些空白空間)

 

2) 如果願意的話, 可以給邊框添加標題, 具體的實現方法是將邊框傳遞給 BroderFactory.createTitledBorder。

不同的邊框有不同的用於設置邊框的寬度和顏色的選項。詳情請參看 API 註釋。偏 愛使用邊框的人都很欣賞這一點。SoftBevelBorder 類用於構造具有柔和拐角的斜面邊 框, LineBorder 類也能夠構造圓拐角。這些邊框只能通過類中的某個構造器構造, 而沒有 BorderFactory 方法。

package border;

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

/**
 * A frame with radio buttons to pick a border style.
 */
public class BorderFrame extends JFrame
{
   private JPanel demoPanel;
   private JPanel buttonPanel;
   private ButtonGroup group;

   public BorderFrame()
   {
      demoPanel = new JPanel();
      buttonPanel = new JPanel();
      group = new ButtonGroup();

      addRadioButton("Lowered bevel", BorderFactory.createLoweredBevelBorder());
      addRadioButton("Raised bevel", BorderFactory.createRaisedBevelBorder());
      addRadioButton("Etched", BorderFactory.createEtchedBorder());
      addRadioButton("Line", BorderFactory.createLineBorder(Color.BLUE));
      addRadioButton("Matte", BorderFactory.createMatteBorder(10, 10, 10, 10, Color.BLUE));
      addRadioButton("Empty", BorderFactory.createEmptyBorder());

      Border etched = BorderFactory.createEtchedBorder();
      Border titled = BorderFactory.createTitledBorder(etched, "Border types");
      buttonPanel.setBorder(titled);

      setLayout(new GridLayout(2, 1));
      add(buttonPanel);
      add(demoPanel);
      pack();
   }

   public void addRadioButton(String buttonName, Border b)
   {
      JRadioButton button = new JRadioButton(buttonName);
      button.addActionListener(event -> demoPanel.setBorder(b));
      group.add(button);
      buttonPanel.add(button);
   }
}

4.4 組合框

如果下拉列表框被設置成可編輯(editable), 就可以像編輯文本一樣編輯當前的選項內容。注意, 編輯只會影響當前項, 而不會改變列 表內容。如果你的組合框不是可編輯的, 最好調用

combo.getltemAt(combo.getSelectedlndex() )

可以增加任何類型的選項,組合框可以調用每個選項的 toString方法顯示其內容

如果需要往組合框中添加大量的選項,addltem 方法的性能就顯得很差了 n 取而代 之的是構造一個 DefaultComboBoxModel, 並調用 addElement 方法進行加載, 然後再調 用 JComboBox 中的 setModel 方法。

當用戶從組合框中選擇一個選項時,組合框就將產生一個動作事件。爲了判斷哪個選 項被選擇, 可以通過事件參數調用getSource方法來得到發送事件的組合框引用,接着調用 getSelectedltem方法獲取當前選擇的選項。需要把這個方法的返回值轉化爲相應的類型,通 常是 String 型。

package comboBox;

import java.awt.BorderLayout;
import java.awt.Font;

import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

/**
 * A frame with a sample text label and a combo box for selecting font faces.
 */
public class ComboBoxFrame extends JFrame
{
   private JComboBox<String> faceCombo;
   private JLabel label;
   private static final int DEFAULT_SIZE = 24;

   public ComboBoxFrame()
   {
      // add the sample text label

      label = new JLabel("The quick brown fox jumps over the lazy dog.");
      label.setFont(new Font("Serif", Font.PLAIN, DEFAULT_SIZE));
      add(label, BorderLayout.CENTER);

      // make a combo box and add face names

      faceCombo = new JComboBox<>();
      faceCombo.addItem("Serif");
      faceCombo.addItem("SansSerif");
      faceCombo.addItem("Monospaced");
      faceCombo.addItem("Dialog");
      faceCombo.addItem("DialogInput");

      // the combo box listener changes the label font to the selected face name

      faceCombo.addActionListener(event ->
         label.setFont(
            new Font(faceCombo.getItemAt(faceCombo.getSelectedIndex()), 
               Font.PLAIN, DEFAULT_SIZE)));

      // add combo box to a panel at the frame's southern border

      JPanel comboPanel = new JPanel();
      comboPanel.add(faceCombo);
      add(comboPanel, BorderLayout.SOUTH);
      pack();
   }
}

4.5 滑動條

當值發生變化時,ChangeEvent 就會發送給所有變化的監聽器。

爲了得到這些 改變的通知,需要調用addChangeListener方法並且安裝一個實現了 ChangeListener接口的對 象。這個接口只有一個方法 StateChanged

可以通過顯示標尺(tick) 對滑動條進行修飾。

如果標尺的標記或者標籤不顯示, 請檢查一下是否調用了 setPaintTicks(true) 和 setPaintLabels(true)。

package slider;

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

/**
 * A frame with many sliders and a text field to show slider values.
 */
public class SliderFrame extends JFrame
{
   private JPanel sliderPanel;
   private JTextField textField;
   private ChangeListener listener;

   public SliderFrame()
   {
      sliderPanel = new JPanel();
      sliderPanel.setLayout(new GridBagLayout());

      // common listener for all sliders
      listener = event -> {
         // update text field when the slider value changes
         JSlider source = (JSlider) event.getSource();
         textField.setText("" + source.getValue());
      };

      // add a plain slider

      JSlider slider = new JSlider();
      addSlider(slider, "Plain");

      // add a slider with major and minor ticks

      slider = new JSlider();
      slider.setPaintTicks(true);
      slider.setMajorTickSpacing(20);
      slider.setMinorTickSpacing(5);
      addSlider(slider, "Ticks");

      // add a slider that snaps to ticks

      slider = new JSlider();
      slider.setPaintTicks(true);
      slider.setSnapToTicks(true);
      slider.setMajorTickSpacing(20);
      slider.setMinorTickSpacing(5);
      addSlider(slider, "Snap to ticks");

      // add a slider with no track

      slider = new JSlider();
      slider.setPaintTicks(true);
      slider.setMajorTickSpacing(20);
      slider.setMinorTickSpacing(5);
      slider.setPaintTrack(false);
      addSlider(slider, "No track");

      // add an inverted slider

      slider = new JSlider();
      slider.setPaintTicks(true);
      slider.setMajorTickSpacing(20);
      slider.setMinorTickSpacing(5);
      slider.setInverted(true);
      addSlider(slider, "Inverted");

      // add a slider with numeric labels

      slider = new JSlider();
      slider.setPaintTicks(true);
      slider.setPaintLabels(true);
      slider.setMajorTickSpacing(20);
      slider.setMinorTickSpacing(5);
      addSlider(slider, "Labels");

      // add a slider with alphabetic labels

      slider = new JSlider();
      slider.setPaintLabels(true);
      slider.setPaintTicks(true);
      slider.setMajorTickSpacing(20);
      slider.setMinorTickSpacing(5);

      Dictionary<Integer, Component> labelTable = new Hashtable<>();
      labelTable.put(0, new JLabel("A"));
      labelTable.put(20, new JLabel("B"));
      labelTable.put(40, new JLabel("C"));
      labelTable.put(60, new JLabel("D"));
      labelTable.put(80, new JLabel("E"));
      labelTable.put(100, new JLabel("F"));

      slider.setLabelTable(labelTable);
      addSlider(slider, "Custom labels");

      // add a slider with icon labels

      slider = new JSlider();
      slider.setPaintTicks(true);
      slider.setPaintLabels(true);
      slider.setSnapToTicks(true);
      slider.setMajorTickSpacing(20);
      slider.setMinorTickSpacing(20);

      labelTable = new Hashtable<Integer, Component>();

      // add card images

      labelTable.put(0, new JLabel(new ImageIcon("nine.gif")));
      labelTable.put(20, new JLabel(new ImageIcon("ten.gif")));
      labelTable.put(40, new JLabel(new ImageIcon("jack.gif")));
      labelTable.put(60, new JLabel(new ImageIcon("queen.gif")));
      labelTable.put(80, new JLabel(new ImageIcon("king.gif")));
      labelTable.put(100, new JLabel(new ImageIcon("ace.gif")));

      slider.setLabelTable(labelTable);
      addSlider(slider, "Icon labels");

      // add the text field that displays the slider value

      textField = new JTextField();
      add(sliderPanel, BorderLayout.CENTER);
      add(textField, BorderLayout.SOUTH);
      pack();
   }

   /**
    * Adds a slider to the slider panel and hooks up the listener
    * @param s the slider
    * @param description the slider description
    */
   public void addSlider(JSlider s, String description)
   {
      s.addChangeListener(listener);
      JPanel panel = new JPanel();
      panel.add(s);
      panel.add(new JLabel(description));
      panel.setAlignmentX(Component.LEFT_ALIGNMENT);
      GridBagConstraints gbc = new GridBagConstraints();
      gbc.gridy = sliderPanel.getComponentCount();
      gbc.anchor = GridBagConstraints.WEST;
      sliderPanel.add(panel, gbc);
   }
}

五、菜單

5,1 創建菜單

當用戶選擇菜單時,將觸發一個動作事件。這裏需要爲每個菜單項安裝一個動作監聽器。

可以使用 JMenu.add(Striiigs)方法將菜單項插入到菜單的尾部, 例如: editMenu.add("Paste");

Add方法返回創建的子菜單項。可以採用下列方式獲取它,並添加監聽器:

JMenuItem pasteltem = editMenu.add("Paste");

pasteltem.addActionListener(listener);

5.2 菜單欄中的圖標

實際上,JMenuItem 類擴展了 AbstractButton類。與按鈕一樣, 菜單可以包含文本標籤、圖標,也可以兩者都包含。

正如所看到的,在默認情況下,菜單項的文本被放置在圖標的右側。如果喜歡將文本放置在左側, 可以調用JMenuItem類中的 setHorizontalTextPosition 方法(繼承自 AbstractButton類)設置。

5.3 複選框和單選鈕菜單項 JCheckBoxMenuItem

5.4 彈出菜單 JPopupMenu

彈出菜單並不像常規菜單欄那樣總是顯示在框架的頂 部,必須調用 show方法菜單才能顯示出來。調用時需要給 出父組件以及相對父組件座標的顯示位置。例如:

popup.show(panel, x, y);

要想在用戶點擊某一個組件 時彈出菜單, 需要按照下列方式調用方法: component.setComponentPopupMenu(popup);

偶爾會遇到在一個含有彈出菜單的組件中放置一個組件的情況。這個子組件可以調用下 列方法繼承父組件的彈出菜單。調用: child.setlnheritsPopupMenu(true);

5.5 快捷鍵和加速器

  JMenuItem aboutltem = new ]HenuItem("About", 'A');

 快捷鍵會自動地顯示在菜單項中, 並帶有一條下劃線 (如圖 12-21 所示)。例如,在上面的例子中, 菜單項中的標 籤爲 “ About”, 字母 A 帶有一個下劃線。

 “ About”, 字母 A 帶有一個下劃線。當顯示菜單時, 用戶只需要按下“ A” 鍵就可以這個選擇菜單項(如果快捷 字母沒有出現在菜單項標籤字符串中, 同樣可以按下快捷 鍵選擇菜單項, 只是快捷鍵沒有顯示出來。很自然,這種 不可見的快捷鍵沒有提示效果)。

有時候不希望在菜單項的第一個快捷鍵字母下面加下劃線。例如, 如果在菜單項“ Save As” 中使用快捷鍵“ A”, 則在第二個“ A” (Save As) 下面加下劃線更爲合理。。可以調用 setDisplayedMnemonicIndex方法指定希望加下劃線的字符。

只能在菜單項的構造器中設定快捷鍵字母, 而不是在菜單構造器中。如果想爲菜單設置 快捷鍵, 需要調用 setMnemonic 方法。

可以同時按下 ALT 鍵和菜單的快捷鍵來實現在菜單欄中選擇一個頂層菜單的操作。例 如:按下 ALT+H 可以從菜單中選擇 Help菜單項。

可以使用快捷鍵從當前打開的菜單中選擇一個子菜單或者菜單項。而加速器是在不打開 菜單的情況下選擇菜單項的快捷鍵。

可以使用 setAccelerator將加速器鍵關聯到一個菜單項 上。這個方法使用 Keystroke類型的對象作爲參數。例如:下面的調用將加速器 CTRL+O 關 聯到 Openltem 菜單項。可以使用 setAccelerator將加速器鍵關聯到一個菜單項 上。這個方法使用 Keystroke類型的對象作爲參數。例如:下面的調用將加速器 CTRL+O 關 聯到 Openltem 菜單項。

openItem.setAccelerator(KeyStroke.getKeyStroke("ctrl O"));

加速器只能關聯到菜單項上, 不能關聯到菜單上。加速器鍵並不實際打開菜單。它將直 接地激活菜單關聯的動作事件。

從概念上講,把加速器添加到菜單項與把加速器添加到 Swing組件上所使用的技術十分 類似(在第 11 章中討論了這個技術)。但是, 當加速器添加 到菜單項時, 對應的組合鍵就會自動地顯示在相應的菜單 上(見圖 12-22)。

5.6 啓用和禁用菜單項

啓用和禁用菜單項有兩種策略。每次環境發生變化就 對相關的菜單項或動作調用setEnabled。例如: 只要當文檔 以只讀方式打開, 就禁用Save和 SaveAs菜單項。另一種 方法是在顯示菜單之前禁用這些菜單項。這裏必須爲“ 菜單選中” 事件註冊監聽器。

MenuListener接口:

void menuSelected(MenuEvent event)

void menuDeselected(MenuEvent event)

void menuCanceled(MenuEvent event)

由於在菜單顯示之前調用menuSelected方法, 所以可以在這個方法中禁用或啓用菜單項。

在顯示菜單之前禁用菜單項是一種明智的選擇, 但這種方式不適用於帶有加速鍵 的菜單項。這是因爲在按下加速鍵時並沒有打開菜單, 因此動作沒有被禁用, 致使加速 鍵還會觸發這個行爲

package menu;

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

/**
 * A frame with a sample menu bar.
 */
public class MenuFrame extends JFrame
{
   private static final int DEFAULT_WIDTH = 300;
   private static final int DEFAULT_HEIGHT = 200;
   private Action saveAction;
   private Action saveAsAction;
   private JCheckBoxMenuItem readonlyItem;
   private JPopupMenu popup;

   /**
    * A sample action that prints the action name to System.out
    */
   class TestAction extends AbstractAction
   {
      public TestAction(String name)
      {
         super(name);
      }

      public void actionPerformed(ActionEvent event)
      {
         System.out.println(getValue(Action.NAME) + " selected.");
      }
   }

   public MenuFrame()
   {
      setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

      JMenu fileMenu = new JMenu("File");
      fileMenu.add(new TestAction("New"));

      // demonstrate accelerators

      JMenuItem openItem = fileMenu.add(new TestAction("Open"));
      openItem.setAccelerator(KeyStroke.getKeyStroke("ctrl O"));

      fileMenu.addSeparator();

      saveAction = new TestAction("Save");
      JMenuItem saveItem = fileMenu.add(saveAction);
      saveItem.setAccelerator(KeyStroke.getKeyStroke("ctrl S"));

      saveAsAction = new TestAction("Save As");
      fileMenu.add(saveAsAction);
      fileMenu.addSeparator();

      fileMenu.add(new AbstractAction("Exit")
         {
            public void actionPerformed(ActionEvent event)
            {
               System.exit(0);
            }
         });

      // demonstrate checkbox and radio button menus

      readonlyItem = new JCheckBoxMenuItem("Read-only");
      readonlyItem.addActionListener(new ActionListener()
         {
            public void actionPerformed(ActionEvent event)
            {
               boolean saveOk = !readonlyItem.isSelected();
               saveAction.setEnabled(saveOk);
               saveAsAction.setEnabled(saveOk);
            }
         });

      ButtonGroup group = new ButtonGroup();

      JRadioButtonMenuItem insertItem = new JRadioButtonMenuItem("Insert");
      insertItem.setSelected(true);
      JRadioButtonMenuItem overtypeItem = new JRadioButtonMenuItem("Overtype");

      group.add(insertItem);
      group.add(overtypeItem);

      // demonstrate icons

      Action cutAction = new TestAction("Cut");
      cutAction.putValue(Action.SMALL_ICON, new ImageIcon("cut.gif"));
      Action copyAction = new TestAction("Copy");
      copyAction.putValue(Action.SMALL_ICON, new ImageIcon("copy.gif"));
      Action pasteAction = new TestAction("Paste");
      pasteAction.putValue(Action.SMALL_ICON, new ImageIcon("paste.gif"));

      JMenu editMenu = new JMenu("Edit");
      editMenu.add(cutAction);
      editMenu.add(copyAction);
      editMenu.add(pasteAction);

      // demonstrate nested menus

      JMenu optionMenu = new JMenu("Options");

      optionMenu.add(readonlyItem);
      optionMenu.addSeparator();
      optionMenu.add(insertItem);
      optionMenu.add(overtypeItem);

      editMenu.addSeparator();
      editMenu.add(optionMenu);

      // demonstrate mnemonics

      JMenu helpMenu = new JMenu("Help");
      helpMenu.setMnemonic('H');

      JMenuItem indexItem = new JMenuItem("Index");
      indexItem.setMnemonic('I');
      helpMenu.add(indexItem);

      // you can also add the mnemonic key to an action
      Action aboutAction = new TestAction("About");
      aboutAction.putValue(Action.MNEMONIC_KEY, new Integer('A'));
      helpMenu.add(aboutAction);
      
      // add all top-level menus to menu bar

      JMenuBar menuBar = new JMenuBar();
      setJMenuBar(menuBar);

      menuBar.add(fileMenu);
      menuBar.add(editMenu);
      menuBar.add(helpMenu);

      // demonstrate pop-ups

      popup = new JPopupMenu();
      popup.add(cutAction);
      popup.add(copyAction);
      popup.add(pasteAction);

      JPanel panel = new JPanel();
      panel.setComponentPopupMenu(popup);
      add(panel);
   }
}

5.7 工具欄

  工具欄是在程序中提供的快速訪問常用命令的按鈕欄,工具欄的特殊之處在於可以將它隨處移動。 可以將它拖拽到框架的四個邊框上, 。釋放鼠標按鈕後, 工具欄將會停靠在新的位置上。

   工具欄只有位於採用邊框佈局或者任何支持North、East、South 和 West 約束佈局 管理器的容器內才能夠被拖拽。

  工具欄可以完全脫離框架。 這樣的工具欄將包含在自己的框架中,

當 關閉包含工具欄的框架時, 它會冋到原始的框架中。

JToolBar類還有一個用來添加 Action 對象的方法,可以用 Action對象填充工具欄

5.8 工具提示

工具欄有一個缺點,這就是用戶常常需要猜測按鈕上小圖標按鈕的含義。爲了解決這個 問題,用戶界面設計者發明了工具提示(tooltips)。當光標停留在某個按鈕上片刻時,工具提 示就會被激活。

/**
 * A frame with a toolbar and menu for color changes.
 */
public class ToolBarFrame extends JFrame
{
   private static final int DEFAULT_WIDTH = 300;
   private static final int DEFAULT_HEIGHT = 200;
   private JPanel panel;

   public ToolBarFrame()
   {
      setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

      // add a panel for color change

      panel = new JPanel();
      add(panel, BorderLayout.CENTER);

      // set up actions

      Action blueAction = new ColorAction("Blue", new ImageIcon("blue-ball.gif"), Color.BLUE);
      Action yellowAction = new ColorAction("Yellow", new ImageIcon("yellow-ball.gif"),
            Color.YELLOW);
      Action redAction = new ColorAction("Red", new ImageIcon("red-ball.gif"), Color.RED);

      Action exitAction = new AbstractAction("Exit", new ImageIcon("exit.gif"))
         {
            public void actionPerformed(ActionEvent event)
            {
               System.exit(0);
            }
         };
      exitAction.putValue(Action.SHORT_DESCRIPTION, "Exit");

      // populate toolbar

      JToolBar bar = new JToolBar();
      bar.add(blueAction);
      bar.add(yellowAction);
      bar.add(redAction);
      bar.addSeparator();
      bar.add(exitAction);
      add(bar, BorderLayout.NORTH);

      // populate menu

      JMenu menu = new JMenu("Color");
      menu.add(yellowAction);
      menu.add(blueAction);
      menu.add(redAction);
      menu.add(exitAction);
      JMenuBar menuBar = new JMenuBar();
      menuBar.add(menu);
      setJMenuBar(menuBar);
   }

   /**
    * The color action sets the background of the frame to a given color.
    */
   class ColorAction extends AbstractAction
   {
      public ColorAction(String name, Icon icon, Color c)
      {
         putValue(Action.NAME, name);
         putValue(Action.SMALL_ICON, icon);
         putValue(Action.SHORT_DESCRIPTION, name + " background");
         putValue("Color", c);
      }

      public void actionPerformed(ActionEvent event)
      {
         Color c = (Color) getValue("Color");
         panel.setBackground(c);
      }
   }
}

六、複雜的佈局管理

6.1 網格組佈局

要想使用網格組管理器進行佈局, 必須經過下列過程:

  • 1 ) 建立一個 GridBagLayout 的對象。不需要指定網格的行數和列數。佈局管理器會根據 後面所給的信息猜測出來。
  • 2) 將 GridBagLayout 對象設置成組件的佈局管理器。
  • 3) 爲每個組件建立一個 GridBagConstraints 對象。設置 GridBagConstraints 對象的域以 便指出組件在網格組中的佈局方案。
  • 4) 最後,通過下面的調用添加組件的約束: add(component, constraints);

下面將詳細地介紹幾個最重要的約束:

  •  gridx、gridy.gridwidth 和 gridheight 參數.gridx 和 gridy 指定了被添加組件左上角的行、列 位置。gridwidth 和 gridheight 指定了組件佔據的行數和列數。
  •   增量域(weightx 和 weighty)。.另一方面, 如果將增量設置爲 0, 則這個區域將永遠爲初始尺寸。如果將所有區域的增量 都設置爲 0, 容器就會集聚在爲它分配的區域中間, 而不是通過拉伸來填充它。從概念上講,增量參數屬於行和列的屬性, 而不屬於某個單獨的單元格。但卻需要在單 元格上指定它們,這是因爲網格組佈局並不暴露行和列。或每列單元 格的增量最大值。因此, 如果想讓一行或一列的大小保持不變,就需要將這行、這列的所有 組件的增量都設置爲 0。當容器超過首選大小時, 增量表示分配給每個 區域的擴展比例值.這麼說並不太直觀。這裏建議將所有的增量設置爲 100, 運行程序,查看一下佈局情況。縮放對話框,查看一下行和列是如何調整的。如果發現某行或某列不應該 擴大,就將那行或那列中的所有組件的增量設置爲 0。也可以使用其他的增量值進行修補, 但是這麼做的意義並不大。
  •  fill 和anchor參數,如果不希望組件拉伸至整個區域, 就需要設置 fill 約束。它有四個有效值ғGridBag Constraints.NONE、GridBagConstraints.HORIZONTAL、GridBagConstraints. VERTICAL 和 GridBagConstraints.BOTH.0 如果組件沒有填充整個區域, 可以通過設置anchor域指定其位置。 有效值爲 GridBag Constraints.CENTER ( 默認值) 、GridBagConstraints.NORTH、GridBagConstraints. NORTHEAST 和 GridBagConstraints.EAST 等。
  • 填充,可以通過設置 GridBagLayout 的 insets域在組件周圍增加附加的空白區域。通過設置 Insets 對象的 left、top、right 和 bottom指定組件周圍的空間量。這被稱作外部填充(或外邊距) (externalpadding)。 通過設置 ipadx 和 ipady 指定內部填充(或內外距)(internalpadding)。這兩個值被加到組 件的最小寬度和最小高度上。這樣可以保證組件不會收縮至最小尺寸之下。
  • 指定 gridx,gridy,gridwidth 和 gridheight 參數的另一種方法。AWT 文檔建議不要將 gridx 和 gridy設置爲絕對位置,應該將它們設置爲常量GridBag Constraints.RELATIVEo 然後,按照標準的順序,將組件添加到網格組佈局中。即第一行從 左向右,然後再開始新的一行,以此類推。 還需要通過爲gridheight 和 gridwidth 域指定一個適當的值來設置組件橫跨的行數和列 數。除此之外,如果組件擴展至最後一行或最後一列,則不要給出一個實際的數值, 而是用 常量GridBagConstraints.REMAINDER 替代,這樣會告訴佈局管理器這個組件是本行上的最 後一個組件。

package gridbag;

import java.awt.*;

/**
 * This class simplifies the use of the GridBagConstraints class.
 * @version 1.01 2004-05-06
 * @author Cay Horstmann
 */
public class GBC extends GridBagConstraints
{
   /**
    * Constructs a GBC with a given gridx and gridy position and all other grid
    * bag constraint values set to the default.
    * @param gridx the gridx position
    * @param gridy the gridy position
    */
   public GBC(int gridx, int gridy)
   {
      this.gridx = gridx;
      this.gridy = gridy;
   }

   /**
    * Constructs a GBC with given gridx, gridy, gridwidth, gridheight and all
    * other grid bag constraint values set to the default.
    * @param gridx the gridx position
    * @param gridy the gridy position
    * @param gridwidth the cell span in x-direction
    * @param gridheight the cell span in y-direction
    */
   public GBC(int gridx, int gridy, int gridwidth, int gridheight)
   {
      this.gridx = gridx;
      this.gridy = gridy;
      this.gridwidth = gridwidth;
      this.gridheight = gridheight;
   }

   /**
    * Sets the anchor.
    * @param anchor the anchor value
    * @return this object for further modification
    */
   public GBC setAnchor(int anchor)
   {
      this.anchor = anchor;
      return this;
   }

   /**
    * Sets the fill direction.
    * @param fill the fill direction
    * @return this object for further modification
    */
   public GBC setFill(int fill)
   {
      this.fill = fill;
      return this;
   }

   /**
    * Sets the cell weights.
    * @param weightx the cell weight in x-direction
    * @param weighty the cell weight in y-direction
    * @return this object for further modification
    */
   public GBC setWeight(double weightx, double weighty)
   {
      this.weightx = weightx;
      this.weighty = weighty;
      return this;
   }

   /**
    * Sets the insets of this cell.
    * @param distance the spacing to use in all directions
    * @return this object for further modification
    */
   public GBC setInsets(int distance)
   {
      this.insets = new Insets(distance, distance, distance, distance);
      return this;
   }

   /**
    * Sets the insets of this cell.
    * @param top the spacing to use on top
    * @param left the spacing to use to the left
    * @param bottom the spacing to use on the bottom
    * @param right the spacing to use to the right
    * @return this object for further modification
    */
   public GBC setInsets(int top, int left, int bottom, int right)
   {
      this.insets = new Insets(top, left, bottom, right);
      return this;
   }

   /**
    * Sets the internal padding
    * @param ipadx the internal padding in x-direction
    * @param ipady the internal padding in y-direction
    * @return this object for further modification
    */
   public GBC setIpad(int ipadx, int ipady)
   {
      this.ipadx = ipadx;
      this.ipady = ipady;
      return this;
   }
}

package gridbag;

import java.awt.Font;
import java.awt.GridBagLayout;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextArea;

/**
 * A frame that uses a grid bag layout to arrange font selection components.
 */
public class FontFrame extends JFrame
{
   public static final int TEXT_ROWS = 10;
   public static final int TEXT_COLUMNS = 20;

   private JComboBox<String> face;
   private JComboBox<Integer> size;
   private JCheckBox bold;
   private JCheckBox italic;
   private JTextArea sample;

   public FontFrame()
   {
      GridBagLayout layout = new GridBagLayout();
      setLayout(layout);

      ActionListener listener = event -> updateSample();

      // construct components

      JLabel faceLabel = new JLabel("Face: ");

      face = new JComboBox<>(new String[] { "Serif", "SansSerif", "Monospaced",
            "Dialog", "DialogInput" });

      face.addActionListener(listener);

      JLabel sizeLabel = new JLabel("Size: ");

      size = new JComboBox<>(new Integer[] { 8, 10, 12, 15, 18, 24, 36, 48 });

      size.addActionListener(listener);

      bold = new JCheckBox("Bold");
      bold.addActionListener(listener);

      italic = new JCheckBox("Italic");
      italic.addActionListener(listener);

      sample = new JTextArea(TEXT_ROWS, TEXT_COLUMNS);
      sample.setText("The quick brown fox jumps over the lazy dog");
      sample.setEditable(false);
      sample.setLineWrap(true);
      sample.setBorder(BorderFactory.createEtchedBorder());

      // add components to grid, using GBC convenience class

      add(faceLabel, new GBC(0, 0).setAnchor(GBC.EAST));
      add(face, new GBC(1, 0).setFill(GBC.HORIZONTAL).setWeight(100, 0)
            .setInsets(1));
      add(sizeLabel, new GBC(0, 1).setAnchor(GBC.EAST));
      add(size, new GBC(1, 1).setFill(GBC.HORIZONTAL).setWeight(100, 0)
            .setInsets(1));
      add(bold, new GBC(0, 2, 2, 1).setAnchor(GBC.CENTER).setWeight(100, 100));
      add(italic, new GBC(0, 3, 2, 1).setAnchor(GBC.CENTER).setWeight(100, 100));
      add(sample, new GBC(2, 0, 1, 4).setFill(GBC.BOTH).setWeight(100, 100));
      pack();
      updateSample();
   }

   public void updateSample()
   {
      String fontFace = (String) face.getSelectedItem();
      int fontStyle = (bold.isSelected() ? Font.BOLD : 0)
            + (italic.isSelected() ? Font.ITALIC : 0);
      int fontSize = size.getItemAt(size.getSelectedIndex());
      Font font = new Font(fontFace, fontStyle, fontSize);
      sample.setFont(font);
      sample.repaint();
   }
}

6.2 組佈局

6.3 不使用佈局管理器

  frame.setLayout(null);

6.4 定製佈局管理器

定製佈局管理器必須實現LayoutManager 接口,並且需要覆蓋下面5個方法:

在添加或刪除一個組件時會調用前面兩個方法。如果不需要保存組件的任何附加信息, 那麼可以讓這兩個方法什麼都不做。接下來的兩個方法計算組件的最小布局和首選佈局所需 要的空間。兩者通常相等。第 5 個方法真正地實施操作,它調用所有組件的 setBounds方法。

 

package circleLayout;

import java.awt.*;

/**
 * A layout manager that lays out components along a circle.
 */
public class CircleLayout implements LayoutManager
{
   private int minWidth = 0;
   private int minHeight = 0;
   private int preferredWidth = 0;
   private int preferredHeight = 0;
   private boolean sizesSet = false;
   private int maxComponentWidth = 0;
   private int maxComponentHeight = 0;

   public void addLayoutComponent(String name, Component comp)
   {
   }

   public void removeLayoutComponent(Component comp)
   {
   }

   public void setSizes(Container parent)
   {
      if (sizesSet) return;
      int n = parent.getComponentCount();

      preferredWidth = 0;
      preferredHeight = 0;
      minWidth = 0;
      minHeight = 0;
      maxComponentWidth = 0;
      maxComponentHeight = 0;

      // compute the maximum component widths and heights
      // and set the preferred size to the sum of the component sizes.
      for (int i = 0; i < n; i++)
      {
         Component c = parent.getComponent(i);
         if (c.isVisible())
         {
            Dimension d = c.getPreferredSize();
            maxComponentWidth = Math.max(maxComponentWidth, d.width);
            maxComponentHeight = Math.max(maxComponentHeight, d.height);
            preferredWidth += d.width;
            preferredHeight += d.height;
         }
      }
      minWidth = preferredWidth / 2;
      minHeight = preferredHeight / 2;
      sizesSet = true;
   }

   public Dimension preferredLayoutSize(Container parent)
   {
      setSizes(parent);
      Insets insets = parent.getInsets();
      int width = preferredWidth + insets.left + insets.right;
      int height = preferredHeight + insets.top + insets.bottom;
      return new Dimension(width, height);
   }

   public Dimension minimumLayoutSize(Container parent)
   {
      setSizes(parent);
      Insets insets = parent.getInsets();
      int width = minWidth + insets.left + insets.right;
      int height = minHeight + insets.top + insets.bottom;
      return new Dimension(width, height);
   }

   public void layoutContainer(Container parent)
   {
      setSizes(parent);

      // compute center of the circle

      Insets insets = parent.getInsets();
      int containerWidth = parent.getSize().width - insets.left - insets.right;
      int containerHeight = parent.getSize().height - insets.top - insets.bottom;

      int xcenter = insets.left + containerWidth / 2;
      int ycenter = insets.top + containerHeight / 2;

      // compute radius of the circle

      int xradius = (containerWidth - maxComponentWidth) / 2;
      int yradius = (containerHeight - maxComponentHeight) / 2;
      int radius = Math.min(xradius, yradius);

      // lay out components along the circle

      int n = parent.getComponentCount();
      for (int i = 0; i < n; i++)
      {
         Component c = parent.getComponent(i);
         if (c.isVisible())
         {
            double angle = 2 * Math.PI * i / n;

            // center point of component
            int x = xcenter + (int) (Math.cos(angle) * radius);
            int y = ycenter + (int) (Math.sin(angle) * radius);

            // move component so that its center is (x, y)
            // and its size is its preferred size
            Dimension d = c.getPreferredSize();
            c.setBounds(x - d.width / 2, y - d.height / 2, d.width, d.height);
         }
      }
   }
}

6.5 遍歷順序

當把很多組件添加到窗口中時, 需要考慮遍歷順序(traversal order) 的問題。窗口被初 次顯示時,遍歷序列的第一個組件會有鍵盤焦點。每次用戶按下 TAB 鍵, 下一個組件就會獲得焦點。遍歷順序很直觀, 它的順序是從左至右,從上至下。示例文本區(按下 CTRL+TAB 鍵移動到下一個文本 域, TAB字符被認爲是文本輸入)。

如果容器還包含其他的容器,情況就更加複雜了。當焦點給予另外一個容器時,那個容 器左上角的組件就會自動地獲得焦點,然後再遍歷那個容器中的所有組件。最後,將焦點移 交給緊跟着那個容器的組件。 利用這一點,可以將相關元素組織在一起並放置在一個容器中。例如’ 放置在一個面板中

調用 component.setFocusable(false) ; 可以從焦點遍歷中刪除一個組件。這對於不接受鍵盤輸入、 自行繪製的組件很有用

七、對話框

 與大多數的窗口系統一樣, AWT 也分爲模式對話框和無模式對話框。所謂模式對話框是 指在結束對它的處理之前, 不允許用戶與應用程序的其餘窗口進行交互。模式對話框主要用 於在程序繼續運行之前獲取用戶提供的信息。所謂無模式對話框是指允許用戶同時在對話框和應用程序的其他窗口中輸入信息。Swing 有一個很容易使用 的類JOptionPane, 它可以彈出一個簡單的對話框,而不必編寫任何對話框的相關代碼。

7.1 選項對話框

Swing 有一套簡單的對話框, 用於獲取用戶的一些簡單信息。JOptionPane 有 4個用於顯 示這些對話框的靜態方法:

showMessageDialog:                                showConfirmDialog:
顯示一條消息並等待用戶點擊 OK             顯示一條消息並等待用戶確認(與 OK/Cancel類似)

showOptionDialog:                                                          showInputDialog:

顯示一條消息並獲得用戶在一組選項中的選擇                顯示一條消息並獲得用戶輸人的一行文本

左側的圖標將由下面 5 種消息類型決定:

  • ERROR_MESSACE
  • INFORMATION_MESSACE
  • WARNING_MESSACE
  • QUESTION_MESSACE
  • PLAIN_MESSACE

PLAIN_MESSAGE類型沒有圖標。每個對話框類型都有一個方法,可以用來提供自己的 圖標, 以替代原來的圖標。

可以爲每個對話框類型指定一條消息。這裏的消息既可以是字符串、圖標、用戶界面組件, 也可以是其他類型的對象。下面是顯示消息對象的基本方式:

String:          Icon: Component           Object[]:
繪製字符串 顯示圖標 顯示組件             顯示數組中的所有對象, 依次疊加

任何其他對象: 調用 toString方法來顯示結果字符串

位於底部的按鈕取決於對話框類型和選項類型。 當調用showMessageDialog 和 showInputDialog時,只能看到一組標準按鈕(分別是 OK/Cancel)。當調用showConfirmDialog 時, 可以選擇下面四種選項類型之一:

  • DEFAULTOPTION
  • YES_NO_OPTION
  • YES_N0_CANCEL_OPTION
  • OK_CANCEL_OPTION

使用showOptionDialog 可以指定任意的選項。這裏需要爲選項提供一個對象數組。每個 數組元素可以是下列類型之一:

  • String: 使用字符串標籤創建一個按鈕
  • Icon: 使用圖標創建一個按鈕
  • Component: 顯示這個組件
  • 其他類型的對象:使用toString方法,然後用結果字符串作爲標籤創建按鈕

下面是這些方法的返回值:

  • showMessageDialog 無
  • showConfirmDialog 表示被選項的一個整數
  • showOptionDialog 表示被選項的一個整數
  • showInputDialog 用戶選擇或輸入的字符串

showConfirmDialog 和 showOptionDialog返回一個整數用來表示用戶選擇了哪個按鈕。 對於選項對話框來說, 這個值就是被選的選項的索引值或者是CLOSED_OPTION (此時用戶 沒有選擇可選項, 而是關閉了對話框)。對於確認對話框,返回值可以是下列值之一:

  • OK_OPTI0N
  • CANCEL_OPTION
  • YES_OPTION
  • NO_OPTION
  • CLOSED_OPTION

其他類型的對象:使用toString方法,然後用結果字符串作爲標籤創建按鈕

這些選項似乎令人感到迷惑不解, 實際上非常簡單步驟如下:

  • 選擇對話框的類型(消息、確認、選項或者輸人)。
  • 選擇圖標(錯誤、信息、警告、問題、無或者自定義)。
  • 選擇消息(字符串、圖表、 自定義組件或者它們的集合)。
  • 對於確認對話框, 選擇選項類型 (默認、Yes/No、Yes/No/Cancel 或者 Ok/Cancel)。
  •  對於選項對話框, 選擇選項(字符串、 圖表或者自定義組件)和默認選項
  • 對於輸人對話框, 選擇文本框或者組合框。
  •  調用 JOptionPane API 中的相應方法
package optionDialog;

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

/**
 * A frame that contains settings for selecting various option dialogs.
 */
public class OptionDialogFrame extends JFrame
{
   private ButtonPanel typePanel;
   private ButtonPanel messagePanel;
   private ButtonPanel messageTypePanel;
   private ButtonPanel optionTypePanel;
   private ButtonPanel optionsPanel;
   private ButtonPanel inputPanel;
   private String messageString = "Message";
   private Icon messageIcon = new ImageIcon("blue-ball.gif");
   private Object messageObject = new Date();
   private Component messageComponent = new SampleComponent();

   public OptionDialogFrame()
   {
      JPanel gridPanel = new JPanel();
      gridPanel.setLayout(new GridLayout(2, 3));

      typePanel = new ButtonPanel("Type", "Message", "Confirm", "Option", "Input");
      messageTypePanel = new ButtonPanel("Message Type", "ERROR_MESSAGE", "INFORMATION_MESSAGE",
            "WARNING_MESSAGE", "QUESTION_MESSAGE", "PLAIN_MESSAGE");
      messagePanel = new ButtonPanel("Message", "String", "Icon", "Component", "Other", 
            "Object[]");
      optionTypePanel = new ButtonPanel("Confirm", "DEFAULT_OPTION", "YES_NO_OPTION",
            "YES_NO_CANCEL_OPTION", "OK_CANCEL_OPTION");
      optionsPanel = new ButtonPanel("Option", "String[]", "Icon[]", "Object[]");
      inputPanel = new ButtonPanel("Input", "Text field", "Combo box");

      gridPanel.add(typePanel);
      gridPanel.add(messageTypePanel);
      gridPanel.add(messagePanel);
      gridPanel.add(optionTypePanel);
      gridPanel.add(optionsPanel);
      gridPanel.add(inputPanel);

      // add a panel with a Show button

      JPanel showPanel = new JPanel();
      JButton showButton = new JButton("Show");
      showButton.addActionListener(new ShowAction());
      showPanel.add(showButton);

      add(gridPanel, BorderLayout.CENTER);
      add(showPanel, BorderLayout.SOUTH);
      pack();
   }

   /**
    * Gets the currently selected message.
    * @return a string, icon, component, or object array, depending on the Message panel selection
    */
   public Object getMessage()
   {
      String s = messagePanel.getSelection();
      if (s.equals("String")) return messageString;
      else if (s.equals("Icon")) return messageIcon;
      else if (s.equals("Component")) return messageComponent;
      else if (s.equals("Object[]")) return new Object[] { messageString, messageIcon,
            messageComponent, messageObject };
      else if (s.equals("Other")) return messageObject;
      else return null;
   }

   /**
    * Gets the currently selected options.
    * @return an array of strings, icons, or objects, depending on the Option panel selection
    */
   public Object[] getOptions()
   {
      String s = optionsPanel.getSelection();
      if (s.equals("String[]")) return new String[] { "Yellow", "Blue", "Red" };
      else if (s.equals("Icon[]")) return new Icon[] { new ImageIcon("yellow-ball.gif"),
            new ImageIcon("blue-ball.gif"), new ImageIcon("red-ball.gif") };
      else if (s.equals("Object[]")) return new Object[] { messageString, messageIcon,
            messageComponent, messageObject };
      else return null;
   }

   /**
    * Gets the selected message or option type
    * @param panel the Message Type or Confirm panel
    * @return the selected XXX_MESSAGE or XXX_OPTION constant from the JOptionPane class
    */
   public int getType(ButtonPanel panel)
   {
      String s = panel.getSelection();
      try
      {
         return JOptionPane.class.getField(s).getInt(null);
      }
      catch (Exception e)
      {
         return -1;
      }
   }

   /**
    * The action listener for the Show button shows a Confirm, Input, Message, or Option dialog
    * depending on the Type panel selection.
    */
   private class ShowAction implements ActionListener
   {
      public void actionPerformed(ActionEvent event)
      {
         if (typePanel.getSelection().equals("Confirm")) JOptionPane.showConfirmDialog(
               OptionDialogFrame.this, getMessage(), "Title", getType(optionTypePanel),
               getType(messageTypePanel));
         else if (typePanel.getSelection().equals("Input"))
         {
            if (inputPanel.getSelection().equals("Text field")) JOptionPane.showInputDialog(
                  OptionDialogFrame.this, getMessage(), "Title", getType(messageTypePanel));
            else JOptionPane.showInputDialog(OptionDialogFrame.this, getMessage(), "Title",
                  getType(messageTypePanel), null, new String[] { "Yellow", "Blue", "Red" },
                  "Blue");
         }
         else if (typePanel.getSelection().equals("Message")) JOptionPane.showMessageDialog(
               OptionDialogFrame.this, getMessage(), "Title", getType(messageTypePanel));
         else if (typePanel.getSelection().equals("Option")) JOptionPane.showOptionDialog(
               OptionDialogFrame.this, getMessage(), "Title", getType(optionTypePanel),
               getType(messageTypePanel), null, getOptions(), getOptions()[0]);
      }
   }
}

/**
 * A component with a painted surface
 */

class SampleComponent extends JComponent
{
   public void paintComponent(Graphics g)
   {
      Graphics2D g2 = (Graphics2D) g;
      Rectangle2D rect = new Rectangle2D.Double(0, 0, getWidth() - 1, getHeight() - 1);
      g2.setPaint(Color.YELLOW);
      g2.fill(rect);
      g2.setPaint(Color.BLUE);
      g2.draw(rect);
   }

   public Dimension getPreferredSize()
   {
      return new Dimension(10, 10);
   }
}

7.2 創建對話框

  要想實現一個對話框,需要從 JDialog 派生一個類。這與應用 程序窗口派生於 JFrame 的過程完全一樣。具體過程如下:

  •   在對話框構造器中,調用超類JDialog 的構造器
  •   添加對話框的用戶界面組件
  •   添加事件處理器。
  •   設置對話框的大小。

 調用超類構造器時,需要提供擁有者框架(ownerframe)、對話框標題及模式特徵。擁有者框架控制對話框的顯示位置,如果將擁有者標識爲null, 那麼對話框將由一個隱 藏框架所擁有。模式特徵將指定對話框處於顯示狀態時,應用程序中其他窗口是否被鎖住。無模式對話 框不會鎖住其他窗口,而有模式對話框將鎖住應用程序中的所有其他窗口(除對話框的子窗 口外)。用戶經常使用的工具欄就是無模式對話框, 另一方面, 如果想強迫用戶在繼續操作 之前提供一些必要的信息就應該使用模式對話框。

package dialog;

import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;

/**
 * A frame with a menu whose File->About action shows a dialog.
 */
public class DialogFrame extends JFrame
{
   private static final int DEFAULT_WIDTH = 300;
   private static final int DEFAULT_HEIGHT = 200;
   private AboutDialog dialog;

   public DialogFrame()
   {
      setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

      // Construct a File menu.

      JMenuBar menuBar = new JMenuBar();
      setJMenuBar(menuBar);
      JMenu fileMenu = new JMenu("File");
      menuBar.add(fileMenu);

      // Add About and Exit menu items.

      // The About item shows the About dialog.

      JMenuItem aboutItem = new JMenuItem("About");
      aboutItem.addActionListener(event -> {
         if (dialog == null) // first time
            dialog = new AboutDialog(DialogFrame.this);
         dialog.setVisible(true); // pop up dialog
      });
      fileMenu.add(aboutItem);

      // The Exit item exits the program.

      JMenuItem exitItem = new JMenuItem("Exit");
      exitItem.addActionListener(event -> System.exit(0));
      fileMenu.add(exitItem);
   }
}

package dialog;

import java.awt.BorderLayout;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

/**
 * A sample modal dialog that displays a message and waits for the user to click the OK button.
 */
public class AboutDialog extends JDialog
{
   public AboutDialog(JFrame owner)
   {
      super(owner, "About DialogTest", true);

      // add HTML label to center

      add(
            new JLabel(
                  "<html><h1><i>Core Java</i></h1><hr>By Cay Horstmann</html>"),
            BorderLayout.CENTER);

      // OK button closes the dialog

      JButton ok = new JButton("OK");
      ok.addActionListener(event -> setVisible(false));

      // add OK button to southern border

      JPanel panel = new JPanel();
      panel.add(ok);
      add(panel, BorderLayout.SOUTH);

      pack();
   }
}

 

7.3 數據交換

package dataExchange;

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

/**
 * A frame with a menu whose File->Connect action shows a password dialog.
 */
public class DataExchangeFrame extends JFrame
{
   public static final int TEXT_ROWS = 20;
   public static final int TEXT_COLUMNS = 40;
   private PasswordChooser dialog = null;
   private JTextArea textArea;

   public DataExchangeFrame()
   {
      // construct a File menu

      JMenuBar mbar = new JMenuBar();
      setJMenuBar(mbar);
      JMenu fileMenu = new JMenu("File");
      mbar.add(fileMenu);

      // add Connect and Exit menu items

      JMenuItem connectItem = new JMenuItem("Connect");
      connectItem.addActionListener(new ConnectAction());
      fileMenu.add(connectItem);

      // The Exit item exits the program

      JMenuItem exitItem = new JMenuItem("Exit");
      exitItem.addActionListener(event -> System.exit(0));
      fileMenu.add(exitItem);

      textArea = new JTextArea(TEXT_ROWS, TEXT_COLUMNS);
      add(new JScrollPane(textArea), BorderLayout.CENTER);
      pack();
   }

   /**
    * The Connect action pops up the password dialog.
    */
   private class ConnectAction implements ActionListener
   {
      public void actionPerformed(ActionEvent event)
      {
         // if first time, construct dialog

         if (dialog == null) dialog = new PasswordChooser();

         // set default values
         dialog.setUser(new User("yourname", null));

         // pop up dialog
         if (dialog.showDialog(DataExchangeFrame.this, "Connect"))
         {
            // if accepted, retrieve user input
            User u = dialog.getUser();
            textArea.append("user name = " + u.getName() + ", password = "
                  + (new String(u.getPassword())) + "\n");
         }
      }
   }
}

package dataExchange;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Frame;
import java.awt.GridLayout;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

/**
 * A password chooser that is shown inside a dialog
 */
public class PasswordChooser extends JPanel
{
   private JTextField username;
   private JPasswordField password;
   private JButton okButton;
   private boolean ok;
   private JDialog dialog;

   public PasswordChooser()
   {
      setLayout(new BorderLayout());

      // construct a panel with user name and password fields

      JPanel panel = new JPanel();
      panel.setLayout(new GridLayout(2, 2));
      panel.add(new JLabel("User name:"));
      panel.add(username = new JTextField(""));
      panel.add(new JLabel("Password:"));
      panel.add(password = new JPasswordField(""));
      add(panel, BorderLayout.CENTER);

      // create Ok and Cancel buttons that terminate the dialog

      okButton = new JButton("Ok");
      okButton.addActionListener(event -> {
         ok = true;
         dialog.setVisible(false);
      });

      JButton cancelButton = new JButton("Cancel");
      cancelButton.addActionListener(event -> dialog.setVisible(false));

      // add buttons to southern border

      JPanel buttonPanel = new JPanel();
      buttonPanel.add(okButton);
      buttonPanel.add(cancelButton);
      add(buttonPanel, BorderLayout.SOUTH);
   }

   /**
    * Sets the dialog defaults.
    * @param u the default user information
    */
   public void setUser(User u)
   {
      username.setText(u.getName());
   }

   /**
    * Gets the dialog entries.
    * @return a User object whose state represents the dialog entries
    */
   public User getUser()
   {
      return new User(username.getText(), password.getPassword());
   }

   /**
    * Show the chooser panel in a dialog
    * @param parent a component in the owner frame or null
    * @param title the dialog window title
    */
   public boolean showDialog(Component parent, String title)
   {
      ok = false;

      // locate the owner frame

      Frame owner = null;
      if (parent instanceof Frame)
         owner = (Frame) parent;
      else
         owner = (Frame) SwingUtilities.getAncestorOfClass(Frame.class, parent);

      // if first time, or if owner has changed, make new dialog

      if (dialog == null || dialog.getOwner() != owner)
      {
         dialog = new JDialog(owner, true);
         dialog.add(this);
         dialog.getRootPane().setDefaultButton(okButton);
         dialog.pack();
      }

      // set title and show dialog

      dialog.setTitle(title);
      dialog.setVisible(true);
      return ok;
   }
}

 

7.4 文件對話框

  注意,JFileChooser類並不 是 JDialog類的子類。需要調用 showOpenDialog, 而不是調用 SetVisible(true)顯示打開文件 的對話框,或者調用 showSaveDialog顯示保存文件的對話框。接收文件的按鈕被自動地標籤 爲 Open 或者 Save。也可以調用 showDialog方法爲按鈕設定標籤

重用一個文件選擇器對象是一個很好的想法,其原因是JFileChooser 的構造器相 當耗費時間。特別是在Windows 上, 用戶映射了很多網絡驅動器的情況下。

下面是建立文件對話框並且獲取用戶選擇信息的步驟:

1 ) 建立一個 JFileChooser對象。與 JDialog 類的構造器不同,它不需要指定父組件。允 許在多個框架中重用一個文件選擇器。

2) 調用setCurrentDirectory方法設置當前目錄。例如, 使用當前的工作目錄:

chooser.setCurrentDirectory(new File("."));

3) 如果有一個想要作爲用戶選擇的默認文件名,可以使用setSelectedFile方法進行指定: chooser.setSelectedFi1e(new File(filename));

4) 如果允許用戶在對話框中選擇多個文件,需要調用setMultiSelectionEnabled方法。 當然,這是可選的。 chooser.setMultiSelectionEnabled(true);

5 ) 如果想讓對話框僅顯示某一種類型的文件(如,所有擴展名爲 .gif的文件), 需要設 置文件過濾器, 稍後將會進行討論。

6) 在默認情況下,用戶在文件選擇器中只能選擇文件。如果希望選擇目錄,需要調用 setFileSelectionMode方法。 參數值爲:JFileChooser.FILES_ONLY (默認值) ,JFileChooser. DIRECTORIES_ONLY 或者 JFileChooser.FILES_AND_DIRECTORIES.

7 ) 調用 showOpenDialog 或者 showSaveDialog方法顯示對話框。必須爲這些調用提供父組件:

僅當用戶確認、 取消或者離開對話框時才返回調用。返回值可以是 JFileChooser. APPROVE_OPTION、JFileChooser.CANCE_LOPTION 或者 JFileChooser.ERROR_OPTION。

8) 調用getSelectedFile() 或者 getSelectedFiles()方法獲取用戶選擇的一個或多個文件。 這些方法將返回一個文件對象或者一組文件對象。 如果需要知道文件對象名時,可以調用 getPath方法。

其實,設計專用文件過濾器非常簡單,只要實現 FileFilter 超類 中的兩個方法即可:

可以通過爲文件選擇器顯示的每個文件提供特定的圖標和文件描述來定製文件選 擇器。這需要應用一個擴展於javax.swing.filechooser 包中的 FileView 類的對象。這是一個高 級技巧。在通常情況下,不需要提供文件視圖— —可插觀感會提供。然而, 如果想讓某種特 定的文件類型顯示不同的圖標, 就需要安裝自己的文件視圖。這要擴展 FileView 並實現下面 5個方法:

文件選擇器調用 isTraversable 方法來決定是否在用戶點擊一個目錄的時候打開這個目錄。 請注意,這個方法返回一個 Boolean對象, 而不是 boolean值。看起來似乎有點怪,但實際 上很方便— —如果需要使用默認的視圖, 則返回 null。文件選擇器將會使用默認的文件視圖 。換句話說,這個方法返回的 Boolean對象能給出下面三種選擇: 真(Boolean.TRUE), 假 (Boolean.FALSE) 和不關心(null)。

最後, 可以通過添加一個附件組件來定製文件對話框。例如, 圖 12*41 在文件列表旁邊 顯示了一個預覽附件。這個附件顯示了當前選擇文件的縮略視圖。

package fileChooser;

import java.io.*;
import javax.swing.*;
import javax.swing.filechooser.*;
import javax.swing.filechooser.FileFilter;

/**
 * A file view that displays an icon for all files that match a file filter.
 */
public class FileIconView extends FileView
{
   private FileFilter filter;
   private Icon icon;

   /**
    * Constructs a FileIconView.
    * @param aFilter a file filter--all files that this filter accepts will be shown 
    * with the icon.
    * @param anIcon--the icon shown with all accepted files.
    */
   public FileIconView(FileFilter aFilter, Icon anIcon)
   {
      filter = aFilter;
      icon = anIcon;
   }

   public Icon getIcon(File f)
   {
      if (!f.isDirectory() && filter.accept(f)) return icon;
      else return null;
   }
}

public class ImagePreviewer extends JLabel
{
   /**
    * Constructs an ImagePreviewer.
    * @param chooser the file chooser whose property changes trigger an image
    *        change in this previewer
    */
   public ImagePreviewer(JFileChooser chooser)
   {
      setPreferredSize(new Dimension(100, 100));
      setBorder(BorderFactory.createEtchedBorder());

      chooser.addPropertyChangeListener(event -> {
         if (event.getPropertyName() == JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)
         {
            // the user has selected a new file
            File f = (File) event.getNewValue();
            if (f == null)
            {
               setIcon(null);
               return;
            }

            // read the image into an icon
            ImageIcon icon = new ImageIcon(f.getPath());

            // if the icon is too large to fit, scale it
            if (icon.getIconWidth() > getWidth())
               icon = new ImageIcon(icon.getImage().getScaledInstance(
                     getWidth(), -1, Image.SCALE_DEFAULT));

            setIcon(icon);
         }
      });
   }
}

 

7.5 顏色選擇器

  除了文件選擇器外,Swing還提供了一種選擇 器— —JColorChooser (如圖 12M2 ~圖 12 44 )。可 以利用這個選擇器選取顏色。與 JFileChooser 樣, 顏色選擇器也是一個組件,而不是一個對話 框, 但是它包含了用於創建包含顏色選擇器組件的對話框方法。

 

下面這段代碼說明了如何利用顏色選擇器顯示模式對話框:

Color selectedColor = JColorChooser.showDialog(parent,title, initialColor);

另外, 也可以顯示無模式顏色選擇器對話框, 需要提供:

  • 一個父組件。
  • 對話框的標題。
  • 選擇模式 / 無模式對話框的標誌。
  • 顏色選擇器。
  • OK 和 Cancel 按鈕的監聽器(如果不需要監聽器可以設置爲 null)。
package colorChooser;

import java.awt.Color;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JDialog;
import javax.swing.JPanel;

/**
 * A panel with buttons to pop up three types of color choosers
 */
public class ColorChooserPanel extends JPanel
{
   public ColorChooserPanel()
   {
      JButton modalButton = new JButton("Modal");
      modalButton.addActionListener(new ModalListener());
      add(modalButton);

      JButton modelessButton = new JButton("Modeless");
      modelessButton.addActionListener(new ModelessListener());
      add(modelessButton);

      JButton immediateButton = new JButton("Immediate");
      immediateButton.addActionListener(new ImmediateListener());
      add(immediateButton);
   }

   /**
    * This listener pops up a modal color chooser
    */
   private class ModalListener implements ActionListener
   {
      public void actionPerformed(ActionEvent event)
      {
         Color defaultColor = getBackground();
         Color selected = JColorChooser.showDialog(ColorChooserPanel.this, "Set background",
               defaultColor);
         if (selected != null) setBackground(selected);
      }
   }

   /**
    * This listener pops up a modeless color chooser. The panel color is changed when the user
    * clicks the OK button.
    */
   private class ModelessListener implements ActionListener
   {
      private JDialog dialog;
      private JColorChooser chooser;

      public ModelessListener()
      {
         chooser = new JColorChooser();
         dialog = JColorChooser.createDialog(ColorChooserPanel.this, "Background Color",
               false /* not modal */, chooser, 
               event -> setBackground(chooser.getColor()), 
               null /* no Cancel button listener */);
      }

      public void actionPerformed(ActionEvent event)
      {
         chooser.setColor(getBackground());
         dialog.setVisible(true);
      }
   }

   /**
    * This listener pops up a modeless color chooser. The panel color is changed immediately when
    * the user picks a new color.
    */
   private class ImmediateListener implements ActionListener
   {
      private JDialog dialog;
      private JColorChooser chooser;

      public ImmediateListener()
      {
         chooser = new JColorChooser();
         chooser.getSelectionModel().addChangeListener(
               event -> setBackground(chooser.getColor()));

         dialog = new JDialog((Frame) null, false /* not modal */);
         dialog.add(chooser);
         dialog.pack();
      }

      public void actionPerformed(ActionEvent event)
      {
         chooser.setColor(getBackground());
         dialog.setVisible(true);
      }
   }
}

八、GUI程序排錯 

8.1 調試技巧

 

只需要把這些代碼行放在 feme 窗口構造器的末尾。程序運行時, 你將看到會用慢動作填充 內容窗格。

package eventTracer;

import java.awt.*;
import java.beans.*;
import java.lang.reflect.*;

/**
 * @version 1.31 2004-05-10
 * @author Cay Horstmann
 */
public class EventTracer
{
   private InvocationHandler handler;

   public EventTracer()
   {
      // the handler for all event proxies
      handler = new InvocationHandler()
         {
            public Object invoke(Object proxy, Method method, Object[] args)
            {
               System.out.println(method + ":" + args[0]);
               return null;
            }
         };
   }

   /**
    * Adds event tracers for all events to which this component and its children can listen
    * @param c a component
    */
   public void add(Component c)
   {
      try
      {
         // get all events to which this component can listen
         BeanInfo info = Introspector.getBeanInfo(c.getClass());

         EventSetDescriptor[] eventSets = info.getEventSetDescriptors();
         for (EventSetDescriptor eventSet : eventSets)
            addListener(c, eventSet);
      }
      catch (IntrospectionException e)
      {
      }
      // ok not to add listeners if exception is thrown

      if (c instanceof Container)
      {
         // get all children and call add recursively
         for (Component comp : ((Container) c).getComponents())
            add(comp);
      }
   }

   /**
    * Add a listener to the given event set
    * @param c a component
    * @param eventSet a descriptor of a listener interface
    */
   public void addListener(Component c, EventSetDescriptor eventSet)
   {
      // make proxy object for this listener type and route all calls to the handler
      Object proxy = Proxy.newProxyInstance(null, new Class[] { eventSet.getListenerType() },
            handler);

      // add the proxy as a listener to the component
      Method addListenerMethod = eventSet.getAddListenerMethod();
      try
      {
         addListenerMethod.invoke(c, proxy);
      }
      catch (ReflectiveOperationException e)
      {
      }
      // ok not to add listener if exception is thrown
   }
}

8.2 AWT 機器人完成工作

package robot;

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

/**
 * @version 1.05 2015-08-20
 * @author Cay Horstmann
 */
public class RobotTest
{
   public static void main(String[] args)
   {
      EventQueue.invokeLater(() ->
            {
               // make frame with a button panel

               ButtonFrame frame = new ButtonFrame();
               frame.setTitle("ButtonTest");
               frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
               frame.setVisible(true);
            });
      
      // attach a robot to the screen device

      GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
      GraphicsDevice screen = environment.getDefaultScreenDevice();

      try
      {
         final Robot robot = new Robot(screen);
         robot.waitForIdle();
         new Thread()
         {
            public void run() 
            {
               runTest(robot);                        
            };
         }.start();
      }
      catch (AWTException e)
      {
         e.printStackTrace();
      }     
   }

   /**
    * Runs a sample test procedure
    * @param robot the robot attached to the screen device
    */
   public static void runTest(Robot robot)
   {
      // simulate a space bar press
      robot.keyPress(' ');
      robot.keyRelease(' ');

      // simulate a tab key followed by a space
      robot.delay(2000);
      robot.keyPress(KeyEvent.VK_TAB);
      robot.keyRelease(KeyEvent.VK_TAB);
      robot.keyPress(' ');
      robot.keyRelease(' ');

      // simulate a mouse click over the rightmost button
      robot.delay(2000);
      robot.mouseMove(220, 40);
      robot.mousePress(InputEvent.BUTTON1_MASK);
      robot.mouseRelease(InputEvent.BUTTON1_MASK);

      // capture the screen and show the resulting image
      robot.delay(2000);
      BufferedImage image = robot.createScreenCapture(new Rectangle(0, 0, 400, 300));

      ImageFrame frame = new ImageFrame(image);      
      frame.setVisible(true);
   }
}

/**
 * A frame to display a captured image
 */
class ImageFrame extends JFrame
{
   private static final int DEFAULT_WIDTH = 450;
   private static final int DEFAULT_HEIGHT = 350;

   /**
    * @param image the image to display
    */
   public ImageFrame(Image image)
   {
      setTitle("Capture");
      setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

      JLabel label = new JLabel(new ImageIcon(image));
      add(label);
   }
}

 

 

 

 

 

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