まあ、タイトルのとおりです。
クソJavaのせいで色々面倒なことになってます。
ああ、これどうしよ・・・。
あ、ちなみにここから下はとても長い&つまらないので
プログラムとかJavaとかに興味がない人は見なくていいですよ。
今日もプログラム。
Java Swingで作ったプログラムの、キーボード操作とマウス操作を組み合わせた時に
変な動きになるということで、色々やってました。
変なことになるのは次のような操作をしたとき。
1.JList上で項目を選択する
2.1番の時にクリックは押したままにする
3.Alt+Tabで別のウィンドウをアクティブにする
4.またAlt+Tabで元のJavaアプリウィンドウをアクティブにする
5.キーボードの上下ボタンを押すとセレクションは動くのにセレクション変更を拾えない!
といったものでした。
何でかなーと思って調べてみました。
そしたら以下が判明。
知っている人は知っていると思いますが、セレクション変更を検知する
ListSelectionListenerはクリックを押した時と放した時で2回は呼ばれます。
このときの重複をはじくためにListSelectionListener.valueChanged()内で
ListSelectionEvent.getValueIsAdjusting()がtrueかfalseのどちらかで処理するようにするのが
どうも定石のようです。
ちなみに、マウスドラッグでセレクションをぐーと動かしたりすると
ListSelectionEvent.getValueIsAdjusting()がtrueになります。
クリックを放す(マウスリリース)とListSelectionEvent.getValueIsAdjusting()がfalseに
なります。
自分のプログラムではfalseの時に処理するようにしてました。
理由としては、キーボードの上下でセレクションを変更すると
ListSelectionEvent.getValueIsAdjusting()がfalseになるためです。
このListSelectionEvent.getValueIsAdjusting()ですが、
「このイベントが複数の変更イベントのうちの 1 つである場合に true を返します」
と、よく分からない感じでAPIに書いてありますが、
まあ、要するに、「連続して変更されているかもしれない時はtrueになりますよ」というような意味かと。
つまり、マウス操作のときはドラッグで連続して変更されるかもしれないので
ドラッグ中はtrueになるらしいです。
で、終わったら(リリースしたら)falseになるんでしょうね。
キーボード操作ではキープレス一個ごとに見るので連続しないから常にfalseになるのではないかと。
押しっぱも連打と同じだと見ているんでしょう。
まあ、そんな理由からListSelectionEvent.getValueIsAdjusting()がfalseになったときに
セレクション変更検知の処理をしてたんです。
で、なぜうまく行かなかったかというと、これがクソJavaの罠でした。
まずは、ListSelectionEvent.getValueIsAdjusting()の変移を調べてみることに。
そしたら、マウスをクリックしたままアクティブウィンドウを別のにして元に戻してから
キーボード操作をするとそのあとはずっとtrueになりました・・・。
もー、What's the fXXkて感じですよ。
もう少し調べるために今度はマウスリスナーとつけて見てやってみました。
そしたら、マウスを押したままアクティブウィンドウを変更したら
マウスリリースが呼ばれないでマウスイクジットが呼ばれてました。
まあ、よくよく考えればこれは当たり前のことですが、
問題はこのときにListSelectionEvent.getValueIsAdjusting()がtrueのままになっていること。
どうもキーボード操作のときにListSelectionEvent.getValueIsAdjusting()がfalseになるのではなく、
前回のListSelectionEvent.getValueIsAdjusting()の値のままになるようです。
なので、マウス操作でtrueのままになるとそのあとは永遠にキーボード操作でもtrueになって
オレの実装ではうまく行かなくなってました。
こんなのJavaのバグかと思うんですが、
今までそういうのが話題に上がってないところを見ると、これが普通のようなので、
なんとか解決できないかなーと思ってちょっと考えて見ました。
まず考えたのは「マウス押しっぱでウィンドウが変わったらリリースと判断するようにする」ということ。
まあ、簡単ですわな。
class MyMouseAdapter extends MouseAdapter {
private JList list = null;
private boolean pressed = false;
public MyMouseAdapter(JList l) {
list = l;
}
public void mousePressed(MouseEvent e) {
pressed = true;
}
public void mouseReleased(MouseEvent e) {
pressed = false;
}
public void mouseExited(MouseEvent e) {
if (pressed) {
MouseEvent newEvent =
new MouseEvent(e.getComponent(), MouseEvent.RELEASED, e.getWhen(),
e.getModifiresEx(), e.getX(), e.getY(), e.getClickCount(),
e.isPopupTrigger(), e.getButton());
list.dispatchEvent(newEvent);
}
}
}
つーのを、JListに付けてやればいいわけですよ。
でも、先輩に「ドラッグ中でもセレクションが変わったら検知できるようにしてほしい」と
言われたので、ListSelectionEvent.getValueIsAdjusting()がtrueで判定することになりました。
もちろんキーボード操作も見越して。
で、考えたのが以下のリスナー。
class ListSelectionChangeListener implements ListSelectionListener {
private boolean truedFlag = false;
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
truedFlag = true;
selectionChanged(e);
}
else {
if (truedFlag) {
selectionChanged(e);
}
truedFlag = false;
}
}
public void selectionChanged(ListSelectionEvent e) {
//ここにセレクションが変わったときの処理を書く
}
}
これならマウスリリースの時をはじけて、かつ、falseとtrueの両方のときのキーボード操作に
対応できました。(簡単にしかテストしてないんですけどね・・・)
ちょっとやってみた感じうまくいってそうなので先輩に見てもらったんですが、
Javaの仕様が(本当に)オレの想定通りなのか疑問という点と、
Ctrl+Aの時にselectionChanged()が2回呼ばれるということでボツになりました。
(なのでここにさらしてるんですけどね)
まず、Ctrl+AでもselectionChanged()が2回呼ばれる原因ですが、
BasicListUIの中にあるHandler(マウス、キーボードなどのリスナーパック)で
Ctrl+Aのアクションを見てみると、setSelectionInterval()とaddSelectionInterval()を
getValueIsAdjuting()がtrueになるように呼んでいる所為でした。
両方のメソッドはセレクションを変更するのでListSelectionEventが発行されます。
もー、何やってんよ。
仕様が想定通りかというのは、ちゃんとドキュメントに書いてあるかが心配されました。
「実際にそうなってもドキュメント(APIとか)に書いてないことは安易に実装すべきでない」と言われ、
しゅんとなりました。
で、午後はずっと考察&やり直し。
色々考えましたよ。
valueChanged()が呼ばれるたびに選択されているインデックスを比べてみる、とか。
でも、これは性能が悪くてですね。
セレクションの変更は基本は追加と解除の2つで、「切り替え」がないので、
選択されている最小インデックス、最大インデックス、選択数だけを保存すればいいんですが、
選択数はListSelectionEventではすぐに取れません。
ListSelectionEvent.getSource()でListSelectionModelを取り出して、
モデルから最小インデックスと最大インデックスを取って、
それをループして何個選択されているかを調べないと選択数が分かりません。
JList.getSelectedIndices()やJTable.getSelectedRowCount()も同じようにループさせてます。
作り方次第ですけど、たくさんデータがあるときに何回もループさせると
ヒジョーに大変なことになるので、却下。
なので、もうListSelectionListenerの中でどうこうするのではなく、
セレクション変更の処理でどっかのメソッドを呼んでどうせJListを参照するんだから
そこで最小、最大、選択数を取って前回呼ばれたときと同じ選択状態が調べることになりました。
はあ。
今日は調査&試行錯誤で何時間無駄にしたんだろ・・・。
てか、Javaがセレクション変更をすぐに検知できるリスナーか何か作れよ。
今日で2つくらいバグが直るかなーと思ったら1つも直らなかった。
もーー。
だんだん時間がなくなってきたのでちょっとイライラしてます。
カルシウム取ります。
定時から少し残って頭の中を整理してました。
明日はソースを整理しないと。
疲れるなー。
ということで、今日はぜんぜん進展してません。
あと2週間?くらいしかないというのに。
Javaはもうちょっと色々できるようにして欲しいです。
あと、何をどうするとどうなるのか全部書きやがれ(怒)。
中身のデフォルト実装もどういう動作か書きやがれ(怒×2)。
さて、もう風呂に入るかな。
上がったらDQMJ2で選手権に登録して、それからXenobladeでもやるか。
あ、上のほうに書いたソース、別に使ってもいいですけど
あんまテストしてないし今書いたからスペルミスとかあるかもですよ?
使うときはそれを覚悟してください。
長々と駄文失礼しました。
[0回]