9.レイアウト

MIDIを作れるJavaアプレットを作っているのだ。
前回デザインの改良を考えた。

よりスマートな(もといマシな)デザインへ

前回作ってみた配置はどうでもいい余白がむやみに多くて使い物になりませんでした。
いや、そもそもマウスイベントを拾わないので配置云々以前に動かなかったのですが。
というわけで、真ん中のピアノロールを大きく、上のツールバーを必要最小限の大きさにしてくれる配置を探すことにしましょう。
LayoutManagerを実装したクラスを次々試していって見つけたのがBorderLayoutです。
真ん中は可能な限り大きく、周りは適当な大きさにサイズを自動調整してくれる優れものです。
というわけで実際のコードで見てみましょう。

setLayout(new BorderLayout());
add("North",toolbar);
add("Center",view);

で、実際に表示すると次のようになります。
MIDIAP写真
なんと、素晴らしく洗練された画面です!!(以前と比べれば)
ツールバーもピアノロールも以前と少し変わっていますがそのことについては後述します。

トラック編集画面

えっとですね。ピアノロールがメインなのですがこの画面では将来的にはピアノロール+トラック固有情報の編集もできるようにする予定なので、ピアノロールを内部に含み、なおかつ将来的にピアノロールと並べて編集画面を表示できるようにしなければならないので、トラック編集画面クラスの仲にピアノロールクラスを内包してしまいます。

public class MIDIViewTrack extends MIDIView {
	int track;
	class PanelPianoRoll extends Panel implements MouseListener {
		(前に作ったピアノロールをほぼそのまま持ってくる)
	}
	PanelPianoRoll piano;
	Scrollbar vscroll = new Scrollbar(Scrollbar.VERTICAL);
	Scrollbar hscroll = new Scrollbar(Scrollbar.HORIZONTAL);
	public MIDIViewTrack(MIDIApplet parent, int track) {
		applet = parent;
		this.track=track;
		piano = new PanelPianoRoll();
		Panel p = new Panel(new BorderLayout());
		p.add("Center",piano);
		p.add("East",vscroll);
		p.add("South",hscroll);
		removeAll();
		setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
		add(p);
	}
}

ついでに近い将来絶対使うことになるスクロールバーもつけています。
pというオブジェクトはピアノロール周りをまとめて扱うために使っています。
BoxLayoutは縦に並べて配置するために使っています。(今は無意味ですが)

ツールバー

以前と目立って変わっている部分といえばなんといっても左側のコンボボックスでしょう。
コンボボックスを使うにはAWTのChoiceと、項目選択に対応するためにItemListenerを使います。

public class MIDIAPToolbar extends Panel implements ActionListener, ItemListener {
	protected MIDIApplet applet;
	Button btSend;
	Choice cMenu;
	public MIDIAPToolbar(MIDIApplet parent) {
		applet = parent;
		btSend = new Button("送信");
		btSend.addActionListener(this);
		cMenu = new Choice();
		cMenu.add("トラック1");
		(中略。スマートな方法じゃないけどこれで充分)
		cMenu.add("MIDIAPについて");
		cMenu.addItemListener(this);
		cMenu.select(0);
		add(cMenu);
		add(btSend);
	}

	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == btSend) {
			applet.doc.Send();
		}
	}
	public void itemStateChanged(ItemEvent e) {
		String s = e.getItem().toString();
		if (s.equals("トラック1")) applet.changeView(new MIDIViewTrack(applet,0));
		(中略。スマートな方法じゃないけどこれで充分)
		if (s.equals("MIDIAPについて")) applet.changeView(new MIDIView());
	}
}

そうです。このコンボボックスは表示を切り替えるためにあるのです。
例によってadd~Listenerを忘れると何も反応しなくなってしまいます。
e.getItem().toString()で選択項目の文字列を得て比較しているというわけですね。
気になるのがapplet.changeViewです。
applet.viewはpublicメンバだから直接変えてもいいんじゃないの?って思ったりするのですがviewという変数の中身を変えたところで表示には直接影響されないというわけで。

表示切替

というわけで最初に書いたchangeViewを載せておきます。

public void changeView(MIDIView v) {
	if (view!=null) remove(view);
	view = v;
	add("Center",view);
}

一見すると素直でわかりやすくどこにも問題が見当たらないのですが、問題はその「見当たらない」というところにあるのです。
これを実際に実行してみると、コンボボックスを触った瞬間にピアノロールが消滅することと思います。
なぜか。
それはこの手の変更はきっかけがないと反映されないからです。
じゃあなんで最初のstart()段階でのaddでは表示されたのかというと、それは「アプレットの実行が始まる」というきっかけがあったからです。
実際、消えてしまった後も、サイズ変更など、配置を見直さなければならないきっかけを与えてやると再び正しい画面を表示するようになります。
だけどそういうこと抜きにして変更を反映するには無理矢理きっかけを作ってやるしかありません。
そのきっかけを与えるために使うのが、validate()です。
変更が終わったら変更終了の決まり文句の如くvalidateしておきましょう。

public void changeView(MIDIView v) {
	if (view!=null) remove(view);
	view = v;
	add("Center",view);
	// これを忘れちゃいかん!!必須!!
	validate();
}

要点

いやほんと、「きっかけ」が必要だって気付くのにずいぶんと時間がかかってしまいましたよもう。
例によって現状のコード

続く