リストビューでアイテムの複数選択を実装しようとしていて詰まったポイント。
insert
メソッドの挙動
insert
メソッドで配列オブジェクトに値を格納するには、例えば以下のようなコードになる。
Array := object()
Array.insert("hoge")
Array.insert("fuga")
Array.insert("foo")
Array.insert("bar")
配列のキーを省略した場合、整数値のインデックスで最大値のもの+1がインデックスとなる。既存の値がない場合は1になる。
つまり、↑のコードは↓と同じ。
Array := object()
Array.insert(1,"hoge")
Array.insert(2,"fuga")
Array.insert(3,"foo")
Array.insert(4,"bar")
ところで、配列のインデックスは連番である必要はない。飛び飛びの数値も取る事ができる。
たとえば↓
Array := object()
Array.insert(1,"hoge")
Array.insert(3,"fuga")
Array.insert(7,"foo")
Array.insert(15,"bar")
ここまではなんの問題もない。
ここで、既存のインデックス以下の数値のインデックスに値を格納するとき、注意が必要になる。
Array := object()
Array.insert(15,"bar")
Array.insert(3,"fuga")
Array.insert(1,"hoge")
Array.insert(7,"foo")
↑のコードで、配列を書き下ろすと↓のようになる。
1, hoge
4, fuga
7, foo
18, bar
つまり、手前に格納した分、インデックスがずれている。
(参照:Objectオブジェクト - AutoHotkey Wiki#註釈)
さらにややこしいのが、ずれた結果インデックスが等数値になる場合。
Array := object()
Array.insert(5,"foo")
Array.insert(3,"fuga")
Array.insert(1,"hoge")
Array.insert(8,"bar")
↑コードだと、配列は↓。
1, hoge
4, fuga
7, foo
8, bar
このように、空いている数値までずれる、といった挙動になる。
remove
メソッドでの問題
冒頭で書いたように、今回はリストビューで複数選択を実装しようとしていた。
どうも、マウスクリックなりのイベント発生時に「選択されている行のすべて」を取得することはできないらしい。
なので、「選択したときinsert
」「選択解除したときremove
」という形で処理しようとした。
1列のリストビューなので、あとでremove
できるように行番号をインデックスにしようとした。
しかし、このケースだと、前節で書いたようなインデックスのずれが発生してしまい、行番号を指定してremove
することができなくなってしまう。
つまり、以下のコードは期待通り動作しない。インデックス3
には値が格納されていないので。
Array := object()
Array.insert(5,"foo")
Array.insert(3,"fuga")
Array.insert(1,"hoge")
Array.insert(8,"bar")
Array.remove(3)
どうすれバインダー。
「数値」ではなく「数字」の連想配列
「数値」のインデックスだからずれるのであって、「数字」つまり文字列のキーとして扱えばよくね?
Array := object()
Array.insert("5","foo")
Array.insert("3","fuga")
Array.insert("1","hoge")
Array.insert("8","bar")
1, hoge
3, fuga
5, foo
8, bar
このように、ずれることなく格納できる。
その代わり、キーがかぶると上書きされてしまうのだが、この場合キーはユニークな行番号なので問題はない。
当然、remove
メソッドも文字列キーに対して行う必要がある。
Array := object()
Array.insert("5","foo")
Array.insert("3","fuga")
Array.insert("1","hoge")
Array.insert("8","bar")
Array.remove("3")
で、A_EventInfo
変数は数値なの、文字列なの?
リストビューからアイテムの値を取得するには、LV_GetText()
関数を用いる。
こんな感じ。
; 前略
If (A_GuiEvent = "I" and InStr(ErrorLevel, "S", true))
{
LV_GetText(RowText, A_EventInfo)
Selection.insert(A_EventInfo, RowText)
Return
}
If (A_GuiEvent = "I" and InStr(ErrorLevel, "s", true))
{
Selection.remove(A_EventInfo)
Return
}
; 後略
で、このA_EventInfo
変数に行番号が格納されるのだが、数のみが格納された組み込み変数は数値扱いになる。
変数の内容を直書きできる場合は普通にクォートすれば文字列扱いになるのだが、組み込み変数の場合はそうもいかない。
var = "2" ; クォートしているので「数値」ではなく「文字列」
var += 1 ; 加算。「数値」としては空なので……
MsgBox, , , %var% ; 「1」と表示される
つまりこの場合、A_EventInfo
変数は数値なので、Selection
のインデックスがずれてしまう。
じゃあどうするかというと、文字くっつけて文字列にしてしまうしかない。
; 前略
If (A_GuiEvent = "I" and InStr(ErrorLevel, "S", true))
{
LV_GetText(RowText, A_EventInfo)
Selection.insert("r" . A_EventInfo, RowText)
Return
}
If (A_GuiEvent = "I" and InStr(ErrorLevel, "s", true))
{
Selection.remove("r" . A_EventInfo)
Return
}
; 後略
さいわい、文字列をキーとしても配列はキー降順で整列してくれるので、あとでループ処理などする場合も特に問題はない。
なお、キーから文字部分を削除して数値のみを再格納すれば、改めて数値でインデックスされた配列として扱うこともできる。
けっこういろいろやりようがある。
; 前略
; 文字列キーを数値インデックスに直す
for index, element in Selection
{
if (A_Index = 1) ; 1ループ目のみ配列を初期化
Array := Object()
StringTrimLeft, index, index, 1 ; 文字"r"を削除
Selection.insert(index, element)
}
; 後略
; 前略
; 文字列キーを数値インデックスに直す
for index, element in Selection
{
; キーごとに削除と再格納を行う
Selection.remove(index)
StringTrimLeft, index, index, 1 ; 文字"r"を削除
Selection.insert(index, element)
}
; 後略
; 前略
; 文字列キーを数値インデックスに直す
for index, element in Selection
{
; キーごとに削除と再格納を行う
Selection.remove(index)
StringTrimLeft, index, index, 1 ; 文字"r"を削除
Selection.insert(A_Index, element) ; 連番インデックスに直す
}
; 後略