Java グラフィカルユーザインターフェース

目次


AWTとSwing

AWT とはAbstract Window Toolkit の略で、グラフィックユーザインターフェースのプログラム(クラス)の集まりである。Swing は AWT のコンポーネントにいろいろな機能を付加したコンポーネントの集まりである。コンソール画面でテキストの入出力だけしている分には不要だが、入力中にユーザがプログラムの流れを変えたり、計算結果に応じて新規のデータを入力したりなど、もう 少し使い勝手の良いプログラムを作りたい場合に AWT あるいは swing を利用するとよい。

Javaに用意されている、ウィンドウズの操作でよく使われるボタンやテキスト入力ボックス、メニュー、ダイアログボックスなどを AWTコンポーネントという。実行中にこのコンポーネントを操作することにより「イベント」が発生する。そのイベントの発生をシステムが感知する仕組みをリスナーという。イベントが発生したときの処理をプログラムとして書き、リスナーを利用してイベントの発生を関知したときそのプログラムを動かす、という仕組みを作れば良い。コンポーネントを利用するためには、それを設置するウィンドウが必要となる。ウィンドウは、アプレットを作成して既存のブラウザを利用することも出来るが、Frame と呼ばれるコンポーネントで自作することもできる。

様々なコンポーネント

よく使われるコンポーネントとして次のようなものがある。AWT のコンポーネント名の前に「J」を付けると Swing のコンポーネント名になる事が多いが、Swing で新たに定義されたものも多い。

目的 クラス(AWT, Swing)
ウィンドウを作る
Frame, JFrame
ボタンを作る
Button, JButton
テキスト入力ボックスを作る(単一行)
TextField, JTextField
テキスト表示ボックスるを作る(複数行用)
TextArea, JTextArea
テキスト/図形を表示する
Label, JLabel
(コンポーネントを配置する)枠を作る
Panel, JPanel
画像処理するPanelを作る
Canvas
スライドつまみ入力装置を作る Scrollbar, Slider, JScrollbar, JSlider

他に、(J)Dialog, (J)Checkbox, (J)Choice, (J)List, (J)MenuBar, (J)PupupMenu など。Swing で新たに定義されたものに、 JComboBox, JList, JMenuBar, JPupupMenu などがある。

Swing は、異なるOSでも同じルックアンドフィールを持つように設計されており、機能も充実しているが、その分、フットワークが重くなっている。

レイアウト

コンポーネントをウィンドウに配置するためにレイアウトマネージャーという仕組みを利用する。アプレットのデフォルトレイアウトマネージャーは FlowLayout で、左上から順番に、定義された順番に配置される。Frame のデフォルトレイアウトマネージャーは BorderLayout で、上下左右と中央の5カ所のいずれか指定した場所に配置される(this.add(button, "North"); など)。ウィンドウの座標を指定して、自由にレイアウトしたい場合は setLayout(null) というメソッドを実行する。

よく使われるレイアウトには、他に、GridLayout, BorderBagLayout, CardLayout, BoxLayout などがある。

コンポーネントが次のように定義されているとしたばあい、FlowLayout GridLayout の違いを示す。

// ボタン
	bt = new Button("add");
	bt.addActionListener(this);
	this.add(bt);		
// ラベル
	this.add(new Label(" "));		
	this.add(new Label("加算する値"));		
// テキストフィールド
	tf = new TextField("0", 5);
	this.add(tf);		
// ラベル
this.add(new Label("  合計")); // テキストフィールド tf2 = new TextField("0", 10); tf2.addTextListener(this); this.add(tf2);
次の左は FlowLayout の例、右はGridLayout の例である。

     layout

もどる

ボタン

幾つかの選択肢を示し、それをクリックすることで実行する内容を選ぶ、というプログラミングはボタンを使うことで効率よく実現できる。

ボタンを組み込んだアプレットの例

ブラウザをウィンドウとして利用するプログラムをアプレットという。次のプログラムは、ボタンをクリックすると、クリック情報がブラウザに表示されるというものである。

// #2: クラス定義
public class AWTsampleApplet extends Applet implements ActionListener {
	int counter = 0;
// #3: 初期設定
public void init() { JButton btn = new JButton("click me"); btn.addActionListener(this); this.add(btn); } // #4: 描画 public void paint(Graphics gr) { if(counter > 0) gr.drawString("ボタンが "+counter+"回クリックされました", 10, 50); } // #5: イベント割り込み処理 public void actionPerformed(ActionEvent ae) { counter++; repaint(); } }

プログラムの説明:

ブラウザでなく、自作のフレームをボタンを組み込むウィンドウとして使う場合は、上のプログラムの #2, #3 のウィンドウ生成に関わる部分が変更されるだけで、AWTコンポーネントに関わる部分は同じである。

ボタンを組み込んだフレームの例

次は、Swing の JFrame を継承したサブクラスを作ってウィンドウを表示させ、上と同じような仕事をするプログラムの例である。イベント処理プログラムは省略した。

class TestJFrame {
// メインメソッド
public static void main(String[] args) { MyJFrame myFrame = new MyJFrame("JFrame のサンプル");
myFrame.setVisible(true); }
}

class MyJFrame extends JFrame {
// コンストラクタ MyJFrame(String title) { setSize(new Dimension(300, 200)); // ウィンドウサイズ setTitle(title); // タイトル表示 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // プログラムの終了 this.setLayout(new FlowLayout()); // レイアウトの設定 // ボタンの定義
JButton btn = new JButton("ボタン"); btn.addActionListener(this);
add(btn); // ボタンの追加 } }

プログラムの説明:

AWT の Frame を生成する場合は、「extends JFrame」を「extends Frame」に変え、「閉じる」ボタンがクリックされたときの処理用メソッド「setDefaultCloseOperation」を次の WindowListener を利用するメソッドに置き換えれば良い。

	this.addWindowListener(new WindowAdapter() {
		public void windowClosing(WindowEvent we) { System.exit(0); }
	});
JFrame を生成して JButton を定義したもの(左図)と、Frame を生成して Button を定義したもの(右図)の実行結果は次の通り。フレームの背景色とボタンのデザインが違っている。

     frame

様々なコンポーネント

パネルを作り、コンポーネントを配置する:JPanel, JButton, JLabel, JTextField, ...

パネルは、既存のウィンドウに新たな(内部)フレームを作る。様々なコンポーネントを用途別にグループ化して別々のパネルに配置すると、見た目もプログラムコードも簡潔になる。

// フレームのレイアウトをフリーレイアウトに変える
setLayout(null);

// パネルを作る JPanel myPanel = new JPanel(); myPanel.setBounds(20, 10, 100, fSize.height - 60); // 位置決め myPanel.setBorder(new BevelBorder(BevelBorder.RAISED)); // 立体表示 myPanel.setLayout(new GridLayout(4, 1)); // 格子状レイアウトにする
// ラベルを作ってパネルに貼り付ける JLabel lblb = new JLabel("制御盤", JLabel.CENTER); // 中央に表示 myPanel.add(lblb); // パネルに貼り付ける // テキストフィールドを作ってパネルに貼り付ける tf = new JTextField(4); // 4文字分の幅を確保 tf.addActionListener(this); // Enter入力時割り込み myPanel.add(tf);
// ボタンを作ってパネルに貼り付ける JButton btn = new JButton("ボタン"); btn.addActionListener(this); myPanel.add(btn);
// グラフィックラベルを作ってパネルに貼り付ける JLabel lblc = new JLabel(); lblc.setIcon(new ImageIcon("./eclipse.png")); // イメージを表示させる lblc.setHorizontalAlignment(JLabel.CENTER); // 中央揃え myPanel.add(lblc);
// パネルをウィンドウ(フレーム)に貼り付ける add(myPanel);

JLabel は文字だけでなく、イメージを表示することも出来る(ATM の Label はできない)。「new ImageIcon("./eclipse.png")」によって新たなイメージファイルを作り、それを「addIcon」メソッドで組み込む。

コンポーネント設置の座標:フリーレイアウトにして座標指定する場合、JFrame と Frame では原点が異なる。Frame の場合、原点はタイトルバーの下なので、隠れて見えない。32ドット下げる必要がある。次は、パネルの左上座標を (20, 10) とした場合の JFrame と Frame に設置した場合の違いである。

         

テキストフィールド

TextField に新たなデータが入力された(Enterキーが押された)ときにある処理を実行するようにしたい場合は、次のようにする。TestField の名前をtf とする。

  1. ActionListener を実装する。
  2. tf.addActionListener(this);」によってActionListener の割り込み処理を可能にする。
  3. actionPerformed」メソッドをオーバーライドする。「if(getSource() == tf) { ... }」の条件文内部に必要な処理を書く。
  4. TextField の内容は「getText」メソッドで取り出すことが出来、数値に変換するには「Integer.parseInt(tf.getText());」あるいは「Double.parseDouble(tf.getText());」とする。
public class Test extends Applet implements ActionListener {
	TextField tf;
	public void init() {
		tf = new TextField("123");
		tf.addActionListener(this);
		add(tf);
	}

	public void actionPerformed(ActionEvent e) {
		if(e.getSource() == tf) {
			System.out.println("データ入力 " + Integer.parseInt(tf.getText()));
		}
	}	
}

あるいは、多様な割り込みを処理するようになると、割り込み処理用のプログラムは別の新たなクラスとして定義したほうがすっきりするかもしれない。その場合には、コンストラクタによって、呼び出し元のクラス名を引き渡しておくとよい。

public class Test extends Applet {
	TextField tf;
	public void init() {
		tf = new TextField("123");
		tf.addActionListener(new MyActionListener(this));
		add(tf);
	}
}
class MyActionListener implements ActionListener {
	Test tclass;
	MyActionListener(Test tclass) {
		this.tclass = tclass;
	}
	public void actionPerformed(ActionEvent e) {
		if(e.getSource() == tclass.tf) {
			System.out.println("データ入力 " + Integer.parseInt(tclass.tf.getText()));
		}
	}		
}

スクロールバーの付いたテキストエリアを作る:TextArea

複数行のテキストを表示するために TextField の代わりに TextArea を使う。表示エリアを越えた場合にスクロールバーを表示させるためには、Scroll ペインを使わなければいけない。

// テキストエリア
JTextArea ta = new JTextArea(10, 25); // 1行25文字で10行分の領域を確保
// スクロールペインを定義、テキストエリアを組み込む
JScrollPane scroll = new JScrollPane(ta); add(scroll);

.

もどる

データ入力用スライダーを作る:JSlider, Scrollbar

数値を入力する際、キーボードでなく、アナログ式に入力する道具としてスライダーがある。感度分析のように、パラメータの値を徐々に変えたいなどの場合に便利である。JSlider は、Swing で提供されるスライダーコンポーネントである。AWT では Scrollbar を使う。

JSlider の割り込みイベントは ChangeListener を実装し、ChangeEvent を処理する stateChanged メソッドをオーバーライドすることで処理される。

例:JSlider の書き方

// 親クラスに実装
class SliderTest extends JFrame implements ChangeListener {

// スライダーの定義
SliderTest() {
JSlider slider = new JSlider(0,1000); // 最小値、最大値 slider.setPreferredSize(new Dimension(fsize.width-20, 70)); // 表示領域指定 slider.addChangeListener(this); // 割り込み処理用 slider.setMajorTickSpacing(1000); // ティックマークの指定 slider.setPaintTicks(true); // それを表示 slider.setPaintLabels(true); // ティックの数値を表示 add(slider);

// イベント処理:スライダーの状態変化 public void stateChanged(ChangeEvent e) { ...
}

ティックラベルに実数値を表示したい場合は、ラベル用の表を作って対処する必要がある。次のその例である。

	double min, max;
Hashtable<Integer, JComponent> tab = new Hashtable<Integer, JComponent>(); tab.put(0, new JLabel(String.format("%.2f", min))); tab.put(500, new JLabel(String.format("%.2f", (min+max)/2))); tab.put(1000, new JLabel(String.format("%.2f", max))); // スライダー定義 js = new JSlider(0, 1000);
...
js.setLabelTable(tab); // 目盛りラベルの対応表
js.setPaintLabels(true);

例(スライダーの使用例) 次は、最小値最大値を動的に入力できるフィールドを付けたスライダーをパネルに組み込んだ例である。

class MySliderSubb extends JPanel implements ChangeListener {
	Dimension size = new Dimension(300, 65);
	JTextField fmin, fmax;
	JSlider js;
	MyJFrame mf;							// 呼び出し元のクラス
	
	MySliderSubb(String name, double min, double max, MyJFrame mf) {
		this.mf = mf;
		this.setPreferredSize(size);
		this.setBackground(Color.blue);
		this.setLayout(null);

// 変数名 JLabel jb = new JLabel(name); jb.setBounds(0, 5, 80, 25); jb.setHorizontalAlignment(JTextField.CENTER); add(jb); // スライダー定義 js = new JSlider(0, 1000); js.setBounds(80, 5, size.width-85, 30); js.addChangeListener(this); // 割り込み処理用 js.setMajorTickSpacing(100); // ティックマークの指定 js.setPaintTicks(true); // それを表示 add(js); // 最小値、最大値の入力用テキストフィールド定義 fmin = new JTextField(""+min); fmin.setBounds(80, 40, 40, 20); fmin.setHorizontalAlignment(JTextField.CENTER); add(fmin); fmax = new JTextField(""+max); fmax.setBounds(255, 40, 40, 20); fmax.setHorizontalAlignment(JTextField.CENTER); add(fmax); } // スライダーが動いたときの処理(呼び出し元に値を返す) public void stateChanged(ChangeEvent e) { double min = Double.parseDouble(fmin.getText()); double max = Double.parseDouble(fmax.getText()); double z = js.getValue() * 0.001 * (max-min) + min; mf.setSliderValue(z); } }

次は、このクラスのインスタンスを作成したときの表示例である。

     スライダ

Slider  の使用例:次は、Slider を使って RGB 値を指定し、色見本を表示させるプログラムである。

public class AWTsampleSlider extends Applet implements ChangeListener {
	JSlider[] colorBar = new JSlider[5];
	TextField tf;
	// 初期設定
	public void init() {
		this.setSize(400, 400);
		this.setLayout(new FlowLayout());
		for(int i=0; i<3; i++) {
			colorBar[i] = new JSlider(0, 255);
			colorBar[i].addChangeListener(this);
			colorBar[i].setPreferredSize(new Dimension(this.getWidth()-40, 70));
			colorBar[i].setMajorTickSpacing(128);
			colorBar[i].setPaintTicks(true);
			colorBar[i].setPaintLabels(true);
			add(colorBar[i]);
		}
		tf = new TextField("RGB", 20);
		add(tf);
	}		
	// スライダーの割り込み処理
	public void stateChanged(ChangeEvent e) {
		setBackground(new Color(colorBar[0].getValue(),
			colorBar[1].getValue(), colorBar[2].getValue()));
		tf.setText("R,G,B = " + colorBar[0].getValue() + ", " + 
			colorBar[1].getValue() + ", " + colorBar[2].getValue());
	}
}

プログラムの説明

次は実行例の画面である。

     slider

.Slider と同じような機能を持つ Scrollbar も同じように使える。Scrollbar の割り込みイベントは AdjustmentListener を実装し、ChangeEvent を処理する stateChanged メソッドをオーバーライドすることで処理される。

例:Scrollbar の書き方

// 親クラスに実装
class ... implements AdjustmentListener
// スクロールバーの定義
Scrollbar sb = new Scrollbar(Scrollbar.HORIZONTAL, 128, 10, 0, 255); sb.addAdjustmentListener(this); sb.setBounds(20,50+30*i,200,20); add(sb);

 // イベント処理:スクロールバーの状態変化 public void stateChanged(ChangeEvent e) { ...
}

.

もどる

ポップアップメニュー

ボタンを右クリックするとメニューが表示されて、一つのメニュー項目を選べるようになる、という仕掛けを実現するのが JPopupMenu である。次は、ボタンをクリックするとポップアップメニューが表示され、それぞれの割り込み処理を行うように設定されたプログラムの例(部分)である。

class MyJFrame extends JFrame {
JPopupMenu popm; // コンストラクタで定義
MyJFrame() {
JButton btn = new JButton("右クリック"); btn.addMouseListener(new MyMouseAdapter(this)); add(btn); // ボタンの追加
popm = new JPopupMenu(); JMenuItem menuA = new JMenuItem("menuA"); // 3行をメニューの個数だけ繰り返す menuA.addActionListener(this); popm.add(menuA); JMenuItem menuB = new JMenuItem("menuB"); menuB.addActionListener(this); popm.add(menuB); } // 割り込み処理
public void actionPerformed(ActionEvent e) { if(e.getActionCommand() == "menuA") { System.out.println("menuA が選択されました"); } if(e.getActionCommand() == "menuB") { System.out.println("menuB が選択されました"); } }
// ボタンクリック時の割り込み処理(ポップアップメニュー表示)
class MyMouseAdapter extends MouseAdapter { MyJFrame mf; // 呼び出し元のクラス MyMouseAdapter(MyJFrame mf) { this.mf = mf; } public void mouseReleased(MouseEvent e) { if(e.isPopupTrigger()) { mf.popm.show(e.getComponent(), e.getX(), e.getY()); } // クリックされた位置にポップアップメニュー表示 } }



テンプレート

制御パネルの例

ボタンやテキストフィールドなどを一つのフレーム(パネル)にまとめ、モニター用制御盤としている。グラフィックス表示用として、別のフレームを定義することを想定している。

public class ControlPanelPlus extends Frame { // Frame の場合

	Dimension fSize = new Dimension(800, 600);			// フレームサイズ
	Dimension pSize = new Dimension(fSize.width - fSize.height - 10,
			fSize.height - 60);				// コントロールパネルサイズ
	Dimension gSize = new Dimension(pSize.height, pSize.height);	// グラフパネルサイズ
	Point pORG = new Point(20, 35); 	// 	// コントロールパネル左上座標(Frame の場合)
	// Point pORG = new Point(10,10); 	// コントロールパネル左上座標(JFrame の場合)
	Point gORG = new Point(pORG.x + pSize.width + 20, pORG.y);	// グラフパネル左上位置座標

	ControlPanelPlus(String title) {
		...
this.setLayout(null); GraphicCanvas grfCanvas = new GraphicCanvas(gORG, gSize); this.add(grfCanvas); this.add(new ControlPanelSub(grfCanvas, pORG, pSize)); } }
// コントロールパネル class ControlPanelSub extends JPanel implements ActionListener { GraphicCanvas gCanvas; Dimension size; JButton[] bt = new JButton[10]; // ボタン配列 int Nbuttons = 7; // ボタンの個数 int bHeight = 35; // ボタンの高さ String[] bname = { "drawCircle", "button2", "button3", "button4", "button5", "button6", "button7" }; JTextField[] tf = new JTextField[10]; // テキストフィールド配列 int Nfields = 4; // テキストフィールドの個数 int fHeight = 30; // テキストフィールドの高さ String[] tvalue = { "100", "0", "0", "0" }; String[] lname = { "radius", "param2", "param3", "param4" }; JTextArea ta; ControlPanelSub(GraphicCanvas gCanvas, Point p, Dimension size) { this.gCanvas = gCanvas; this.setBounds(p.x, p.y, size.width, size.height);; this.size = size; this.setLayout(null); // ボタン用パネルの定義 JPanel btnPanel = new JPanel(); btnPanel.setBounds(0, 0, size.width, (Nbuttons + 1) / 2 * bHeight); btnPanel.setLayout(new GridLayout((Nbuttons + 1) / 2, 2)); // ボタンの定義 for (int i = 0; i < Nbuttons; i++) { bt[i] = new JButton(bname[i]); bt[i].addActionListener(this); // 割り込み処理用 btnPanel.add(bt[i]); } add(btnPanel); // テキストフィールド用パネルの定義 JPanel txtPanel = new JPanel(); txtPanel.setBounds(0, btnPanel.getHeight() + 10, size.width, Nfields * fHeight); txtPanel.setLayout(new GridLayout(Nfields, 2)); // テキストフィールド+ラベルの定義 for (int i = 0; i < Nfields; i++) { txtPanel.add(new JLabel(lname[i], JLabel.CENTER)); tf[i] = new JTextField(tvalue[i]); tf[i].setHorizontalAlignment(JTextField.CENTER); // 中央揃え tf[i].addActionListener(this); // 割り込み処理用 txtPanel.add(tf[i]); } add(txtPanel); // 情報表示用として、テキストエリアを下方に設置 int ht = size.height - btnPanel.getHeight() - txtPanel.getHeight() - 20; ta = new JTextArea((ht - 30) / 16, 23); ta.setText("結果表示用エリア:\n"); JScrollPane scroll = new JScrollPane(ta); scroll.setBounds(0, size.height - ht, size.width, ht); add(scroll); } // 割り込み処理 public void actionPerformed(ActionEvent e) { ta.append(e.getActionCommand() + " がクリックされました\n"); // 描画 if (e.getActionCommand() == "drawCircle") { gCanvas.drawCircle(Integer.parseInt(tf[0].getText())); } } }

結果は次の通り。

  

もどる

描画専用キャンバスの例

グラフィックス用の Panel として Canvas がある。マウスのイベントを受け付けるリスナーを実装する。ちらつきを抑えるために、描画は、イメージファイルを使い、ダブルバッファリングで更新する。実際の描画は、この中にメソッドとして書き込む。フレームを作って、この GraphicBoard のインスタンスを貼り付ければよい。

public class ScrollScreen extends Frame {
...
ScrollScreen(String title) { ...
this.setLayout(null); this.add(new GraphicBoard2(15,35,dim.width-30,dim.height-50)); } } class GraphicBoard2 extends Canvas { Image img = null; Graphics g; Dimension dim; Point prev, now = new Point(0,0); GraphicBoard2(int x, int y, int width, int height) { this.setBounds(x, y, width, height); this.addMouseListener(new MyMouseAdapter()); this.addMouseMotionListener(new MyMouseMotionAdapter()); this.addMouseWheelListener(new MyMouseWheelListener()); } // グラフィックス public void paint(Graphics gr) { super.paint(gr); if (img == null) { dim = this.getSize(); img = this.createImage(dim.width, dim.height); g = img.getGraphics(); } gr.drawImage(img, 0, 0, this); // イメージを描き込む } // ちらつき防止 public void update(Graphics g) { paint(g); } // マウスクリック時の割り込み処理:クリックされた点に「+」表示 class MyMouseAdapter extends MouseAdapter { public void mouseClicked(MouseEvent me) { now = me.getPoint(); g.drawLine(now.x-5, now.y, now.x+5, now.y); g.drawLine(now.x, now.y-5, now.x, now.y+5); repaint(); } } // マウスドラッグ時の割り込み処理:ドラッグに合わせて軌跡を描く class MyMouseMotionAdapter extends MouseMotionAdapter { public void mouseDragged(MouseEvent me) { prev = now; now = me.getPoint(); if(Math.abs(now.x-prev.x) + Math.abs(now.y-prev.y) < 20) g.drawLine(prev.x, prev.y, now.x, now.y); repaint(); } } // マウスホイール回転時の割り込み処理:スクリーンをスクロールする class MyMouseWheelListener implements MouseWheelListener { public void mouseWheelMoved(MouseWheelEvent me) { int rt = me.getWheelRotation(); if(rt > 0) { // スクロールアップ g.copyArea(0, rt, dim.width, dim.height, 0, -rt); g.clearRect(0, dim.height-rt, dim.width, rt); } else { // スクロールダウン g.copyArea(0, 0, dim.width, dim.height, 0, 1); g.clearRect(0, 0, dim.width, 1); } repaint(); } } }


もどる