最終更新日:

Chapter11
ボタンをユーザーの動作に反応させる

ボタンを配置すればクリックを何となく受け付けますが、それ以上のことはしてくれません。そこで、クリックして何かを処理させるようなプログラムを今週は作ってみましょう。クリックをしたときなどを含めて、「何か起こった」という場面でプログラムのある特定の部分を実行させるようなことができます。こうした一連のメカニズムは「イベント」と呼ばれますが、この機能を使うことで、ボタンをクリックすると、何かの処理を行うようなプログラムが作成されます。イベント処理をアプレットに組み込んでみましょう。

11-1 ウインドウでイベントを受け付ける

イベントというメカニズムが、Javaのライブラリ機能に含まれています。ボタンのクリックの受け付けだとか、あるいはTextFieldが修正されたときに何かをするというようなことをさせるには、このイベント処理を組み込む必要があります。イベント処理の利用方法と、1つの組み込み例をまず示しましょう。

プロジェクトを用意する

ボタンをクリックして何かを行うプログラムを作りながら、Javaでのイベント処理について説明するのが今週の主旨です。まずは、ウインドウの外観を作ってしまいましょう。とは言っても非常に簡単なものです。単にボタンがあるだけです。ただし、1つだけのボタンだと対照しづらいかもしれないので、意図的に何もしないボタンも1つ付けておくことにしましょう。

まずは、今週のプログラムをまとめておくプロジェクトを用意します。イベント処理とは言ってもいくつも作り方があります。それらは違うプログラム構成だけれども同じ動作をするということで、そのバリエーションを示したいと思います。JBuilderを操作して「EventDriven」というプロジェクトを作っておきましょう。そして、「新規」ボタンをクリックしてオブジェクトギャラリを表示し、アプリケーションを作ります。

プロジェクトは「EventDriven」という名前にする
アプリケーションは「EventTestApp」という名前にする
ウインドウクラスは「ButtonAction」とした

プロジェクトが作られて、EventTestApp.javaとButtonAction.javaの2つのソースファイルが見えています。アプリケーションとしてはEventTestAppクラスがあります。今回はこちらではなく、ウインドウを表示するためのクラスButtonAction.javaを修正することにします。修正する前に、一度実行をして正しく稼働することを確かめます。

そして、ButtonAction.javaのプログラムを以下のように変更します。もともとBorderLayoutが設定されていますが、簡単にするために、FlowLayoutを使うように変更します。プログラムの変更はビジュアルデザイナを使わないでソース上で行ってみます。実行させるとボタンが2つだけ出てきますが、クリックしても何も起こりません。ウインドウの背景はsetBackgroundメソッドで白にしています。そして、JTextAreaのインスタンスを生成してウインドウに組み込んでいます。後から、ボタンをクリックしたときにここに文字が流れるようにしたいのですが、ここでは単に黄色いテキストエリアが表示されるだけです。

package eventdriven;

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

public class ButtonAction extends JFrame {
  private JPanel contentPane;
  JButton activeButton, noActionButton;
  JTextArea messages;

  //フレームのビルド
  public ButtonAction() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
  //コンポーネントの初期化
  private void jbInit() throws Exception  {
    contentPane = (JPanel) this.getContentPane();
    this.setSize(new Dimension(400, 300));
    this.setTitle("ボタンをクリックすると");

    contentPane.setLayout(new FlowLayout());
    contentPane.setBackground(Color.white);

    activeButton = new JButton("記入する");
    contentPane.add(activeButton);

    noActionButton = new JButton("何もしない");
    contentPane.add(noActionButton);

    messages = new JTextArea("メッセージ開始¥n", 12, 60);
    messages.setLineWrap(true);
    messages.setBackground(Color.orange);
    contentPane.add(messages);
  }

  //ウィンドウが閉じられたときに終了するようにオーバーライド
  protected void processWindowEvent(WindowEvent e) {
    super.processWindowEvent(e);
    if (e.getID() == WindowEvent.WINDOW_CLOSING) {
      System.exit(0);
    }
  }
}
実行した結果表示されるウインドウ

11-2 イベントを受け付けるプログラム

続いて、入力したプログラムを少し改造して、クリックを受け付けるようにします。そのクリックを受け付けるメカニズムは「イベント」としてJavaの実行システムでは利用できるようになっています。この「イベント」は概念的に理解してください。一般には何か変化があったとき、あるいはユーザーが何かのアクションを起こしたとき、Javaの実行システムで「イベント」が発生します。具体的にどうなるのかということも知りたいかもしれませんが、ここはまずは抽象的に理解してください。

たとえばマウスの操作なら、マウスの操作を追い掛けるという処理が、コンピュータのどこかで行われているはずです。一般には、常に監視するというよりも、割り込みといって、たとえばマウスが動いたりクリックしたりという変化が発生すると、プログラムを中断してあらかじめ登録しておいたプログラムを実行するようなことが行われます。こうした複雑な処理が背後にはあるのですが、それはOSがやってくれていることですし、Javaのプログラムから見れば、Javaの実行システム側でこうした作業が行われています。マウスやキーボードなどの周辺機器だとイベントという意味合いがわかりやすいかもしれませんが、たとえばウィンドウというものも、ウィンドウのサイズが変わったという場合にイベントが発生するなど、いろいろな場面でイベントは発生します。いずれも、何かの変化が起こったとき、Javaの実行システム内で何かが起こるのだということを抽象的に理解してください。

イベントの考え方

キーボードの画像は、こちらからいただきました。

先ほど作ったプログラムでも、たとえばボタンをクリックすると、イベントが起こっているはずですが、別に何も表示されません。これはJavaの実行システムでは内部的にはイベント処理が行われているのですが、作ったプログラムの側でそれを一切活用していないので、イベントも泡と消えているという状態なのです。

そこで、どうすれば、システム側で発生しているイベントという機会をとらえることができるのでしょうか。まず基本は、イベントが発生すると、ある決められたメソッドが呼び出されるということです。イベントの発生は抽象的な概念ですが、実際にイベントの発生で何が起こるかと言えばメソッドの呼び出しなのです。こうしたイベント処理メカニズムに対応するためのポイントは、次の点に集約できます。

こうした機能の組み込みに、インタフェースという通常とはちょっと違ったクラスを利用します。イベントとひとくくりに説明してきましたが、マウスのクリックや、あるいはウィンドウのサイズ変更など、実際にはいろいろな種類のイベントがあります。詳細はJavaのリファレンスなどを見ていただくとして、今週のプログラムではその中の「アクションイベント」というイベントを利用することになります。言い換えれば、Javaの実行システムでは、Buttonをクリックするとアクションイベントが発生するのです。そのアクションイベントに対応するためのクラスとして、ActionListenerというインタフェースクラスが用意されています。インタフェースクラスは単独では使えず、既存のクラスに組み込むような使い方をします。

このクラス名からわかるように、ActionListenerですので、「アクションイベントを受信できる人」のような意味でとらえてください。つまり、イベントはシステム側で発生しますが、その受け手のクラスをプログラムで作ろうというわけです。そのためのインタフェースとしてActionListenerが用意されているというわけです。

イベント処理を組み込んだプログラム

では、実際にButtonAction.javaにイベント処理を組み込んでみましょう。次のようなプログラムになります。前に入力した、ButtonAction.javaに追加をしますが、このファイルに記述するプログラムをすべて記載しましょう。実行結果を確かめてから、プログラムの詳細を説明します。

package eventdriven;

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

public class ButtonAction extends JFrame implements ActionListener {
  private JPanel contentPane;
  JButton activeButton, noActionButton;
  JTextArea messages;

  //フレームのビルド
  public ButtonAction() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
  //コンポーネントの初期化
  private void jbInit() throws Exception  {
    contentPane = (JPanel) this.getContentPane();
    this.setSize(new Dimension(400, 300));
    this.setTitle("ボタンをクリックすると");

    contentPane.setLayout(new FlowLayout());
    contentPane.setBackground(Color.white);

    activeButton = new JButton("記入する");
    activeButton.addActionListener(this);
    contentPane.add(activeButton);

    noActionButton = new JButton("何もしない");
    contentPane.add(noActionButton);

    messages = new JTextArea("メッセージ開始¥n", 12, 60);
    messages.setLineWrap(true);
    messages.setBackground(Color.orange);
    contentPane.add(messages);
  }

  private String alfabets[] = {"A","B","C","D","E","F","G","H","I","J","K","L",
    "M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d",
    "e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v",
    "w","x","y","z","0","1","2","3","4","5","6","7","8","9"};

  public void actionPerformed( ActionEvent e ) {
    System.out.println("Actionイベントが発生しました。");

    StringBuffer currentText = new StringBuffer( messages.getText() );
    for ( int i = 0; i < 10; i++ ){
      int index = (int)(Math.random() * alfabets.length);
      currentText.append( alfabets[index] );
    }
    currentText.append( " " );
    messages.setText( currentText.toString() );
  }

  //ウィンドウが閉じられたときに終了するようにオーバーライド
  protected void processWindowEvent(WindowEvent e) {
    super.processWindowEvent(e);
    if (e.getID() == WindowEvent.WINDOW_CLOSING) {
      System.exit(0);
    }
  }
}
実行しボタンを何度かクリックしたところ

大きな違いはactionPerformedメソッドが追加されたことですが、それよりも前に重要なポイントがあります。この説明はあとですぐに行うとして、とにかく実行してみてください。

「何もしないボタン」をクリックしても、特に何も起こらないはずです。「記入する」ボタンをクリックすると、テキストエリアにランダムな文字が10文字追加されて行きます。何度もクリックしてみてください。また、JBuilderのメッセージペインをみると、「Actionイベントが発生しました。」と表示されているはずです。前のプログラムと比較すると、「ボタンのクリックに反応した」という重要な機能的な違いがあることをよく認識してください。

テキストエリアに文字を入力する

イベント処理の説明をする前に、テキストエリアに文字が追加される部分の説明をしておきましょう。テキストエリアはJTextAreaクラスから生成されたコンポーネントで、テキストエディタのような文字を表示したり編集が可能な領域をウインドウに確保できます。setLineWrapメソッドで設定を行うことで、行の右端で折り返しすることもできるので、長いテキストであっても見やすく表示できます。

actionPerformedメソッドが実際にクリックしたときに呼ばれるメソッドです。テキストエリアから、getTextメソッドを使うと、その中にあるテキストの文字列を得ることができます。これをStringBufferクラスのインスタンスの初期値として設定します。変更や修正が可能なStringBufferを確保しますが、初期値はテキストエリアの内容です。その後、10回の繰り返しが行われています。その中のメソッドをみる前に、配列のalfabetsをみてください。String型の配列で、1つの要素に何かしらの文字が1つ入っています。この配列は、インスタンス変数なので、クラスを生成したときにこのように初期化されているはずです。

変数indexに代入する式をみてください。まず、Math.random()ですが、Mathというのはクラス名で、staticなメソッドrandomによって、0〜1までのdouble型の乱数値が得られます。つまり、staticだから、Math.random()という記述がいきなりできます。そして、alfabets.lengthは配列alfabetsの個数です。乱数と個数をかけてintにキャストすることによって、0〜(配列alfabetsの個数-1)の整数が得られます。

すると、alfabets[index]は、配列alfabetsのどれかが存在する要素ということになり、indexが乱数から求められていることで、要はStringBufferのcurrentTextに、ランダムに文字が10回選ばれて追加されていくということになります。最後は空白を追加し、11文字追加された結果をsetTextでテキストエリアに戻しているというわけです。このような仕組みで、ボタンのクリックにより、ランダムな文字がテキストエリアに増えていくということになります。

11-3 イベントが処理されるしくみ

前の、ButtonAction.javaのプログラムの内容を続けて説明しましょう。イベントを受け取るようにするには、まず、クラスにイベントを受け取るという機能を組み込みます。

ButtonAction.javaでは、イベントを組み込む前の全体的な構成を変えない、つまり新しいクラスを作らないで、イベント処理ができるように組んでみました。ウインドウのクラスであるButtonActionがイベントを受け取るのです。

ButtonActionがイベントを受け取れるようにするには、クラスを定義するclassキーワードの記述の最後に「implements ActionListener」という記述を付けます。このActionListenerというのが、アクションイベントを処理するという機能の一種のひな形だと思ってください。アクションイベントを受け付ける機能を、ButtonActionクラスに組み込むという意味合いです。

ActionListenerは、java.awt.eventというパッケージに所属しています。ここで、ActionListenerを利用できるようにするために、「import java.awt.event.*;」という記述が最初の部分にあるわけです。

ActionListenerを組み込むと、そのクラスでは必ず、actionPerformedという名前のメソッドを定義しなければならなくなります。このメソッドが定義されていないとエラーになります。また、このメソッドはActionEvent型の引数1つだけを取るというのもやはり定義されていて、それに従わなければなりません。

いずれにしても、implements ActionListenerと、public void actionPerformed(Action

Event e)というメソッドを定義することで、そのクラスはアクションイベントを受け付けることになります。

クラスをアクションイベント対応にする

そして、ButtonAction1というクラスでアクションイベントを受け付けることをシステムに教えておく必要があります。

そのために使われるのがaddActionListenerというメソッドです。ここではJButtonオブジェクトに対して利用しています。アクションイベントが発生するコンポーネントには、必ずこの名前のメソッドが用意されています。

これにより、オブジェクトにアクションイベントが発生した場合に、addActionListenerの引数で指定したオブジェクトのactionPerformedメソッドが呼び出されることになります。つまり言い換えれば、ボタンをクリックすると、ButtonAction1クラスのactionPerformedメソッドが呼び出されるのです。

アクションイベントよって呼び出すクラスを指定する

プログラムでは、

activeButton.addActionListener(this);

として、this、つまりButtonActionが、「記入する」ボタンをクリックしたときに呼び出されるようになっています。

複数のコンポーネントのアクションイベントがある場合

このような作りのプログラムにした場合、どういう状況でactionPerformedが呼び出されるかをもう少し考えてみましょう。ここでは、アプレットの中にあるコンポーネントのうち、「記入する」ボタンのJButtonオブジェクトだけaddActionListenerで、イベント発生とクラスの結び付きを指定しています。ここで、もう1つの「何もしないボタン」にも、たとえば、

noActionButton.addActionListener(this);

として、アクションイベントに対応するクラスを登録したとします。すると「記入する」も「何もしないボタン」も、どちらもButtonActionクラスのactionPerformedメソッドを呼び出すことになります。どちらも、this、つまりアプレットが引数に指定されているわけで、ButtonActionクラスのactionPerformedメソッドが呼び出されるというわけです。

同様に、もっとボタンやTextFieldがあったりして、それらに対してアクションイベントに対応する処理が欲しいときに、それを含むアプレットにActionListenerを組み込んでしまうとしましょう。すると、どのオブジェクトでアクションイベントが発生しても、同一のButtonActionクラスのactionPerformedメソッドが呼び出されることになります。従って、actionPerformedメソッドの呼び出しがあったときに、どのコンポーネントからのアクションメソッドなのかを判断して、コンポーネントごとの処理に分岐する必要が、どうしても出てきます。

この場合に、どのコンポーネントで発生したオブジェクトなのかを判断するには、actionPerformedメソッドの引数(この場合はe)を利用します。この引数eは、ActionEventクラスのオブジェクトなのですが、このクラスで利用できるgetSourceメソッドを利用すると、イベントが発生したオブジェクトがわかるわけです。そして、イベントに対してgetSourceメソッドを利用してイベントが発生したオブジェクトを調べます。実はこのサンプルのプログラムはあまり汎用性がありません。イベントの発生したオブジェクトがJButtonだと仮定してキャストしてしまい、ボタン名を取り出すgetTextメソッドを利用しています。そして、ボタン名が「記入する」かどうかを、equalsIgnoreCaseメソッドで比較しています。equalsIgnoreCaseメソッドはStringクラスに定義されたメソッドで、equalsと同様ですが、大文字小文字は無視するというものです。もっとも、漢字の文字列ではあまり意味は持ちませんが。

別の方法としてはgetSourceで調べてイベントの発生元と、実際にJButtonなどを参照している変数(ここではdrawButton)を==演算子で比較するということでも対処できます。これだと、オブジェクトの種類に関係なく、どのコンポーネントからのイベントなのかを判断し、分岐させることができます。

このプログラムは、実質的にボタンが1つなので、actionPerformedメソッドで、どのコンポーネントから来るイベントなのかを判断する必要はありません。

11-4 ビジュアルデザイナでイベント処理を組み込む

JBuilderの機能を使うことで、かえってイベント処理はやりやすくなるかもしれません。基本的には同じ結果になるのですが、ビジュアルデザイナを使ったイベント処理の組み込み方法を紹介しましょう。以下のように作業をしてみます。

1プロジェクト「EventDriven」で、「ファイル」メニューから「新規クラス」を選択して、クラスウィザードを呼び出します。クラス名を「ActionByDesigner」にし、ベースクラスは「javax.swing.JFrame」となるようにして、OKボタンをクリックします。

2新たに「ActionByDesigner.java」というソースファイルが作成されてプロジェクトに登録されました。

3「EventTestApp.java」を修正します。コンストラクタの「public EventTestApp() {」の直下に、以下の2行のプログラムを追加します。

ActionByDesigner frame2 = new ActionByDesigner();
frame2.setVisible(true);

4ActionByDesigner.javaのプログラムを以下のように修正します。イベント処理を組み込む前のActionButton.javaの状態に近いものです。

package eventdriven;

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

public class ActionByDesigner extends JFrame {
  private JPanel contentPane;
  JButton activeButton, noActionButton;
  JTextArea messages;

  //フレームのビルド
  public ActionByDesigner() {
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
  //コンポーネントの初期化
  private void jbInit() throws Exception  {
     contentPane = (JPanel) this.getContentPane();
    this.setSize(new Dimension(400, 300));
    this.setTitle("ボタンをクリックすると");

    contentPane.setLayout(new FlowLayout());
    contentPane.setBackground(Color.white);

    activeButton = new JButton("記入する");
    contentPane.add(activeButton);

    noActionButton = new JButton("何もしない");
    contentPane.add(noActionButton);

    messages = new JTextArea("メッセージ開始¥n", 12, 60);
    messages.setLineWrap(true);
    messages.setBackground(Color.orange);
    contentPane.add(messages);
  }
}

5右側でActionByDesignerのタブが選択されちることを確認して、下側のタブから「設計」を選択します。すると、以前のActionButtonと同様なユーザインタフェースが見えます。

6「記入する」ボタンを選択します。そして、右側の部分で「イベント」のタブを選択します。

7イベントのパネルにある「actionPerformed」と書かれた部分の右側の白いところをクリックします。自動的に文字が入力されますが、変更しないようにそのままにします。

8「actionPerformed」と書かれた部分の右側をダブルクリックします。すると、ソース表示に切り替わり、activeButton_actionPerformedメソッドの記述部分が出てきます。

9以下のプログラムを付け加えます。配列alfabetsの定義は、ActionByDesignerクラスのインスタンス変数となるようにします。activeButton_actionPerformedメソッドはメソッド定義はすでにできているので、そこに追加します。

  private String alfabets[] = {"A","B","C","D","E","F","G","H","I","J","K","L",
    "M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d",
    "e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v",
    "w","x","y","z","0","1","2","3","4","5","6","7","8","9"};

  void activeButton_actionPerformed(ActionEvent e) {
    System.out.println("Actionイベントが発生しました。");

    StringBuffer currentText = new StringBuffer( messages.getText() );
    for ( int i = 0; i < 10; i++ ){
      int index = (int)(Math.random() * alfabets.length);
      currentText.append( alfabets[index] );
    }
    currentText.append( " " );
    messages.setText( currentText.toString() );
  }

10区別しやすいように、テキストエリアの背景をグリーンにします。図のように、message.setBackgroundの引数を修正します。

11実行して同じように動作することを確認します。

イベント処理の組み込み

前に説明した方法と違い、ActionByDesignerはActionListenerをインプリメントしていません。前の方法との違いは、以下の部分にあります。イベント処理をビジュアルデザイナで組み込んだとき、jbInitメソッドの「記入する」ボタンの処理部分が以下のように変化しています。2行目以降が自動的に組み込まれています。

    activeButton = new JButton("記入する");
    activeButton.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        activeButton_actionPerformed(e);
      }
    });
    contentPane.add(activeButton);

ボタンに対してaddActionListenerによってリスナを追加するのは同じですが、その中はあたかも、新しいクラスの定義のような書き方になっています。さらにそのクラスの定義の前にnewがあり、インスタンス生成までしていそうです。そして、そこに「actionPerformed」メソッドがあるのですが、このメソッドはactiveButton_actionPerformedを呼び出しています。activeButton_actionPerformedメソッドは、ActionByDesignerに新たに定義されたメソッドで、結果的にボタンをクリックするとactiveButton_actionPerformedメソッドが呼び出されるわけです。つまり、プログラムをactiveButton_actionPerformedの中に追加すればいいということなのですが、そこに至るまでの部分はなんだかややこしい感じがします。

このように、addActionListenerメソッドの引数内で、あたかもテンポラリな雰囲気でクラスを定義してしまうということも可能です。詳細な文法についてはこのテキストでは説明しませんが、ともかくビジュアルデザイナが自動的に生成するので、その部分は信用することにしましょう。このような、クラス内でクラスを定義する方法は「内部クラス」などと呼ばれていますが、さらにこのように特に名前を新たに付けないクラスは「匿名」クラスと呼ばれています。

この部分について、プログラムがちょっと複雑な構造になるので、解きほぐしましょう。アクションイベント処理の考え方はこれまでと同様ですが、ここではまず、生成した「記入する」ボタンのJButtonオブジェクトに、addActionListenerで、イベント受け付けクラスを結び付けているというところを見てください。つまり、

activeButton.addActionListener(〈イベント受け付けクラス〉);

という大枠の構成になっているのです。〈イベント受け付けクラス〉の部分が何行にも渡ってちょっと長くなっています。今までは、大概その部分にthisとだけ簡単に記述してすんでいたのですが、この匿名クラスという方法は、addActionListenerの引数内にクラス定義があるようなものと考えてもらえればよいでしょう。

もう少し深く掘り下げると、

activeButton.addActionListener(
	new ActionListener()   {	
		〈クラス定義の中身〉
	}
);

という構造になっています。ここで、クラス定義というよりも、インタフェースクラスを直接生成しているような書き方になっている点に注目してください。ActionListenerはインタフェース名です。implementsとかは使わずに、このように、newで直接インタフェース名を記述し、インタフェースで定義した機能を持つクラスをその場で定義するのです。

ActionListenerを組み込んだクラスでは、actionPerformedメソッドが定義されていなければなりません。それをさらに次のように、new ActionListenerに続いて記述してしまいます。

activeButton.addActionListener(
	new ActionListener()   {	
		public void actionPerformed(ActionEvent e)   {
			〈アクションイベントの処理〉
		}
	}
);

このように、匿名クラスを使った書き方だと、1つの場所にアクションイベントの処理についての記述をまとめることができます。こうした書き方がコンパクトでいいと感じることもあるでしょう。

11-5 「クリックに対応するJButton」を作る

アクションイベントに対応する方法はすで説明しましたが、いずれにしても、規定に従ったクラスを作成するということで総括できます。ウインドウにイベントを受け付ける機能を組み込みましたが、少し発想を変えてみましょう。というか、むしろ直接的に考えれば、JButtonというクリック可能なボタンを、クリックして何かの処理(具体的にはテキストエリアに文字を追加する)を行うようなボタンに改良するということを考えます。つまり、既存のJButtonというクラスがありますが、それを発展させて、クリックしたときに描画処理まで行うように機能拡張するのです。

Javaではというか、オブジェクト指向プログラミング環境では、こうした既存の機能の拡張ができるというのが大きな特徴です。これまでも、JFrameというのを拡張して独自のウインドウを作ってきました。これと同じように、JButtonを拡張して、望む機能を組み込んだボタンを作ります。そのための新しいボタンのクラス定義も行います。

ここでは、新しいボタンはTextButtonという名前にしましょう。従って、

class TextButton extends JButton { ... }

のようなクラス定義が加わることになります。

このTextButtonに、イベントの処理機能を付けることにします。つまり、アクションイベントが発生すると、TextButtonのactionPerformedメソッドを呼び出すということにします。そうなると、TextButtonクラスの定義の大枠は次のようになるでしょう。

class TextButton extends JButton implements ActionListener {
	public void actionPerformed(ActionEvent e)   {	
	}
}

これらがイベントまわりの基本方針です。ただ、ここではボタンから直接外部のテキストエリアの処理をしたいと考えます。そのとき、完全に分離したクラスにするとかえって処理が面倒になりますので、TextButtonクラス自体は、ウインドウのクラスの内部クラスとして定義することにします。

プログラムの作成

ここまで使ってきたプロジェクトに、新たにクラスを付け加えます。「ファイル」メニューの「新規クラス」を選択して、TextGenerateButtonクラスを追加します。ベースクラスには、javax.swing.JFrameが選択されていることを確認して、OKボタンをクリックしてください。

クラス名は「TextGenerateButton」にする

アプリケーションとして呼び出されるクラス、EventTestApp.javaで、TextGenerateButtonクラスによるウインドウが表示されるようにします。EventTestAppクラスのコンストラクタに、以下のように2行を追加します。

  public EventTestApp() {

    ActionByDesigner frame2 = new ActionByDesigner();
    frame2.setVisible(true);

    TextGenerateButton frame3 = new TextGenerateButton();	//これを追加
    frame3.setVisible(true);					//これを追加

    ButtonAction frame = new ButtonAction();
    //validate() はサイズを調整する
			:

TextGenerateButtonは次のように変更してください。細かく変更がありますので、前のプログラムからコピー&ペーストした場合には、修正箇所をよく気をつけてください。実行した結果は今までと変わりませんが、分かりやすくするために、テキストエリアの背景はピンク色にしておきました。

package eventdriven;

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

public class TextGenerateButton extends JFrame {
	private JPanel contentPane;
	TextButton activeButton;
	JButton noActionButton;
	JTextArea messages;

	//フレームのビルド
	public TextGenerateButton() {
		enableEvents(AWTEvent.WINDOW_EVENT_MASK);
		try {
			jbInit();
		}
		catch(Exception e) {
			e.printStackTrace();
		}
	}
	//コンポーネントの初期化
	private void jbInit() throws Exception  {
		contentPane = (JPanel) this.getContentPane();
		this.setSize(new Dimension(400, 300));
		this.setLocation(100, 100);
		this.setTitle("ボタンをクリックすると");

		contentPane.setLayout(new FlowLayout());
		contentPane.setBackground(Color.white);

		activeButton = new TextButton("記入する");
		contentPane.add(activeButton);

		noActionButton = new JButton("何もしない");
		contentPane.add(noActionButton);

		messages = new JTextArea("メッセージ開始¥n", 12, 60);
		messages.setLineWrap(true);
		messages.setBackground(Color.pink);
		contentPane.add(messages);
	}

	/* これ以降が、テキストを入力する機能を持ったボタンのクラス */
	public class TextButton extends JButton implements java.awt.event.ActionListener {
		public TextButton(String label){
			super(label);
			this.addActionListener(this);
		}
		private String alfabets[] = {"A","B","C","D","E","F","G","H","I","J","K","L",
			"M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d",
			"e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v",
			"w","x","y","z","0","1","2","3","4","5","6","7","8","9"};

		public void actionPerformed(ActionEvent e) {
			System.out.println("Actionイベントが発生しました。");

			StringBuffer currentText = new StringBuffer( messages.getText() );
			for ( int i = 0; i < 10; i++ ){
				int index = (int)(Math.random() * alfabets.length);
				currentText.append( alfabets[index] );
			}
			currentText.append( " " );
			messages.setText( currentText.toString() );
		}
	}
}
実行結果

リストでは、TextButtonがJButtonを拡張したもので、ActionListenerをインプリメントし、actionPerformedメソッドが定義されている点を、プログラムを見てまずチェックしてください。また、「記入する」ボタンを参照する変数activeButtonは、JButton型ではなくTextButton型としてインスタンス変数で定義されているところも確認してください。そして、activeButtonへの代入は、newでTextButtonクラスのインスタンスが生成されていることもポイントです。

アクションイベントが起こったときに、TextButtonクラスのactionPerformedを呼び出すようにするには、addActionListenerメソッドをどう実行すればよいのでしょうか。これは、TextButtonクラスのコンストラクタで記述されています。コンストラクタは、newでTextButtonクラスを作成するときに呼び出されるもので、クラス名と同名のメソッドのような記述をしている部分がコンストラクタです。そこでは、

this.addActionListener(this);

となっています。thisすなわちTextButton自身が、このボタンのアクションイベントの発生時に呼び出されるようにするということを、ここでシステムに教えているわけです。このように、イベント受け付けクラス内で、自分自身を呼び出すということを記述してしまうことができるのです。

このように、特定の機能を持ったクラスを作った場合、アクションイベントで呼び出されたactionPerformedメソッドでは一般には、どのボタンから呼び出されたものかをいちいち判断しなくてすむようにできます。ここでは描画機能を持ったボタンを定義しましたが、違う機能のボタンの場合は、また別のクラスを定義すればすみます。したがって、一般にはボタンごとに処理を分岐するというよりも、ボタンごとにクラスを分けてしまうので、actionPerformedが呼び出された段階で、各クラスでやることは決まっているという状況にすることができるのです。目的によってクラスを分けてしまうことで、プログラム自体を見通しよいものにすることができるわけです。

このように、クラスの内部でクラスを定義した場合、外側のクラスのインスタンス変数が、内部クラスでも利用できるようになります。つまり、インスタンス変数がクラス間で共通に使えるということにつながります。実際、TextButtonクラスで変数messagesを参照していますが、この変数はTextButtonを含むTextGeneratedButtonクラスに定義されているインスタンス変数です。このように、機能的には別のクラスとして分離して定義できますが、共通に使いたいデータは外側のクラスのインスタンス変数として定義することで、手軽にそれぞれのクラスでやりとりができるようになります。

練習問題

11-1:ちょっと難しいので、この練習問題は課題にもなるものとします。

ウインドウ内に、テキストフィールド(JTextField)が2つ、ドロップダウンリスト(JComboBox)が1つ、ボタンが1つ、ラベル(JLabel)が1つあるようにします。

ドロップダウンリストでは、四則演算の記号、+−×÷を選択できるようにしておく。

そして、テキストフィールドに数字を入れて、ボタンをクリックすると、ドロップダウンリストで選択している演算記号に応じて、それぞれ2つのテキストフィールドの値に対して演算を行った結果をラベルに表示するようにすること。(たとえば、テキストボックスにそれぞれ10と3という数値があって、ドロップダウンリストで×が選択されていれば、ボタンをクリックすると、ラベルに30が表示されるようにする。)

ヒント:JComboBoxで何番目が選択されているかを知るには、getSelectedIndex()メソッドを使えばいい。また、選択されている項目は、getSelectedItem()メソッドを使う。いずれも、引数は指定しない。

画面表示をわかりやすくするために、それ以外のコンポーネントを設定してもいい。

応用問題:ドロップダウンリストの演算記号を選択すると、即座にラベルに選択しなおした演算子による計算結果が入るようにすること。