目次
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,
など。Swing で新たに定義されたものに、(J)
PupupMenu 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
の例である。 幾つかの選択肢を示し、それをクリックすることで実行する内容を選ぶ、というプログラミングはボタンを使うことで効率よく実現できる。
ブラウザをウィンドウとして利用するプログラムをアプレットという。次のプログラムは、ボタンをクリックすると、クリック情報がブラウザに表示されるというものである。
// #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(); } }
プログラムの説明:
Applet
クラスを継承したサブクラスを作り(extends Applet
)、リスナーインターフェースをを実装する(implements ActionListener
)。JButton
クラスのインスタンス(一例)を作成し(new
)、ボタンが押された時に作動するリスナーを組み込み(addActionListener
)、アプレットに貼り付ける(add
)。 actionPerformed
メソッドをオーバーライドする形で、ボタンがクリックされたときに生ずるイベント ActionEvent
の処理プログラムを書く。ブラウザでなく、自作のフレームをボタンを組み込むウィンドウとして使う場合は、上のプログラムの #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); // ボタンの追加 } }
プログラムの説明:
MyJFrame
は
JFrame
クラスを継承するので、「extends JFrame
」とする。setSize(300,200)
」により、画面左上に、横300ピクセル、縦20ピクセルのウィンドウが表示される。さらに「setLocationRelativeTo(null);
」を追加すると、画面中央に表示されるようになる。画面の指定位置に表示させたい場合は setBounds
メソッドを使う。setDefoaltCloseOperation
」メソッドを組み込む。BorderLayout
になっているが、ここでは「setLayout(new FlowLayout())
」として、「流し込み」レイアウトを設定している。自由に配置を決めたい場合は「setLayout(null)
」とする。MyJFrame
のインスタンスを作成し(new
)、それを可視化する(setVisible(true)
)。可視化はフレーム定義のコンストラクタで実行しても良い。MyJFrame
の中に書くこともできる。クラスの内部で自分自身のインスタンスを作る形になるが、それでも構わない。AWT の Frame
を生成する場合は、「extends JFrame
」を「extends Frame
」に変え、「閉じる」ボタンがクリックされたときの処理用メソッド「setDefaultCloseOperation
」を次の WindowListener
を利用するメソッドに置き換えれば良い。
this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } });
JFrame
を生成して JButton
を定義したもの(左図)と、Frame
を生成して Button
を定義したもの(右図)の実行結果は次の通り。フレームの背景色とボタンのデザインが違っている。
パネルは、既存のウィンドウに新たな(内部)フレームを作る。様々なコンポーネントを用途別にグループ化して別々のパネルに配置すると、見た目もプログラムコードも簡潔になる。
// フレームのレイアウトをフリーレイアウトに変える
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 とする。
ActionListener
を実装する。tf.addActionListener(this);
」によってActionListener
の割り込み処理を可能にする。actionPerformed
」メソッドをオーバーライドする。「if(getSource() == tf) { ... }
」の条件文内部に必要な処理を書く。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())); } } }
複数行のテキストを表示するために TextField
の代わりに TextArea
を使う。表示エリアを越えた場合にスクロールバーを表示させるためには、Scroll
ペインを使わなければいけない。
// テキストエリア
JTextArea ta = new JTextArea(10, 25); // 1行25文字で10行分の領域を確保
// スクロールペインを定義、テキストエリアを組み込む
JScrollPane scroll = new JScrollPane(ta); add(scroll);
.
数値を入力する際、キーボードでなく、アナログ式に入力する道具としてスライダーがある。感度分析のように、パラメータの値を徐々に変えたいなどの場合に便利である。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()); } }
プログラムの説明
setMajorTickSpacing
は目盛りの間隔を設定し、setPaintTicks(true)
は目盛りを表示、setPaintLabels(true)
は目盛りの数値を表示することを指示する。 getValue()
メソッドで知ることが出来る。次は実行例の画面である。
.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(); } } }