(82) ファイルセレクタ

今回は、ファイルの一覧を表示して選択する、なんてプログラムを作ってみたいと思います。

本エントリを書きながら動作を少しずつ試していきますので、果たして選択できるところまでたどり着けるかどうかはたいへん心もとない気もするんですが。

もうこれは ブログライブ と言ってもいいかもしれません。誰得。


ということで、まずはフォルダを指定して、その中にあるファイルの一覧を取得するということをやってみます。

特定フォルダのファイル一覧を取得するオペレーションは File.GetFiles です。


GetFiles

オペレーション : GetFiles ( directoryPath )

指定したフォルダ内のすべてのファイルのパスを取得します。

directoryPath 文字列 ファイルを探す対象のフォルダ。
戻り値 文字列 取得が成功した場合は"SUCCESS"、失敗した場合は"FAILED"。

…って、あれー?

リファレンス作っている時は機械的に作業していたので特に疑問に思いませんでしたけれども、改めて考えると、この仕様では取得したファイル名を受け取ることができませんよ?

しかたがない。とりあえず SUCCESS / FAILED だけでも取得してみますか。

result=File.GetFiles("E:\Data")
TextWindow.WriteLine(result)

 image

…あれあれー??

返ってくるのは 「SUCCESS」 ではなく、そのフォルダにあるファイルの一覧ではないですかい。

私の誤訳ではありません。元ネタにした Small Basic 添付の日本語リソース SmallBasicLibrary.ja.xml でも、

操作が成功した場合は、 "SUCCESS" を返します。それ以外の場合には "FAILED" を返します。

と書いてありますよね。

では、日本語リソースの元ネタであるオリジナルリソース SmallBasicLibrary.XML ではどうかといいますと、

If the operation was successful, this will return the files as an array.  Otherwise, it will return "FAILED".

(操作が成功した場合はファイル名を配列で返します。それ以外の場合には "FAILED" を返します。 )

あっやられた!日本語リソースの誤訳だ!くそー。

ということで、File オブジェクトのリファレンスページ を修正しておくことにします。

で。

成功した場合は配列で返ってくるわけですから、ループで 1 ファイルずつ表示させることもできるはず。

result=File.GetFiles("E:\Data")
cnt=Array.GetItemCount(result)
For i=1 To cnt
  TextWindow.WriteLine(result[i])
EndFor

image

おー、できましたできました。

結果、返ってくる配列は、添字が 1 始まりの連続、Array.GetItemCount で取得できる値までにびっしり詰まっているということですね。

ちなみに、File.GetDirectories も成功時にはフォルダ名の配列が返ってきます。


さて、今度は、取得できたファイル名配列からファイル名だけを切り出してみます。

先ほどから 「ファイル名配列」 って呼んでいますけど、実際にはドライブレターから始まるフルパスで格納されているので、純粋にファイル名だけを取り扱いたい場合には頭についているフォルダパスがじゃまなんですよね。

File.GetFiles は引数に対象となるフォルダパスを指定しますので、ファイル名配列を取得した時には、すでにその中に含まれるフォルダパスはわかっています。

ですから、File.GetFiles に指定するフォルダパスを別の変数に格納しておいて、その文字数 + 1 文字分を読み飛ばせば純粋なファイル名だけになるはずです。

ということで、

folderPath = "E:\Data"
fPathSize = Text.GetLength(folderPath) + 1
result = File.GetFiles(folderPath)
cnt = Array.GetItemCount(result)
For i = 1 To cnt
  fullSize = Text.GetLength(result[i])
  fileName=text.GetSubText(result[i], fPathSize + 1, fullSize - fPathSize)
  TextWindow.WriteLine(fileName)
EndFor

 image

いい感じですね。


で。

やはり TextWindow ではいまいち ファイルセレクタ としては動作させにくいんです。

たぶん表示したファイル名の左側に連番振って、表示し終えた次の行で「何番にしますか?」とか表示させて番号入力させるぐらいしかできないはずですので。

ここはやはり、カーソルキーとかで選択したいわけですよ。

ということで、取得したファイル名情報を GraphicsWindow に表示させてみることにします。

GraphicsWindow で文字を表示させるオペレーションは、DrawText しか用意されていません。ので、ファイル名は「背景」としてしか描画することができません。

これは表示制御とかする上で、けっこうコントロールしにくかったりします。

とりあえず、取得した純粋なファイル名を列挙させてみることにします。

folderPath = "E:\Data"                        'フォルダパス
fPathSize = Text.GetLength(folderPath) + 1    'フォルダパスの文字長
result = File.GetFiles(folderPath)            'ファイル名の取得
cnt = Array.GetItemCount(result)              '取得したファイル名の数
left=200                                      '文字列表示左上のXマージン
top=50                                        '文字列表示左上のYマージン
marginY=35                                    '改行増分(Y方向)
GraphicsWindow.FontName = "Showcard Gothic"
GraphicsWindow.BrushColor = GraphicsWindow.GetColorFromRGB(255,0,0)
GraphicsWindow.FontSize = 36
For i = 1 To cnt
  fullSize = Text.GetLength(result[i])
  fileName = text.GetSubText(result[i], fPathSize + 1, fullSize - fPathSize)
  GraphicsWindow.DrawText(left, (i - 1) * marginY + top, fileName)
EndFor

 image

ちなみに、今回使ったフォントは 「Showcard Gothic」 というヤツで、たいていの Windows OS には標準で入っているんですないかと思います。

もし入っていない場合は GraphicsWindow.FontName のオペレーションは無視されて、Small Basic 標準のフォント ( たぶん Consolas ) が使われることになります。


さて。

なんとかファイル名を表示できるようになりましたので、少し体裁を整えていこうと思います。

各種定数を Init サブルーチンとして切り出します。

その際、表示位置の微調整を行えるようにしたいので、横方向マージン用定数も追加します。

'各種定数
folderPath = "E:\Data"                        'フォルダパス
fPathSize = Text.GetLength(folderPath) + 1    'フォルダパスの文字長
left=200                                      '文字列表示左上のXマージン
top=50                                        '文字列表示左上のYマージン
marginX=240                                   '改行増分(Y方向)
marginY=35                                    '改行増分(Y方向)

どのファイルを選択しているかのカーソルもほしいので、半透明の四角形を作っておきます。

'カーソルの定義
GraphicsWindow.PenColor = GraphicsWindow.GetColorFromRGB(255, 0, 0)
GraphicsWindow.BrushColor = GraphicsWindow.GetColorFromRGB(255, 0, 0)
cursor = Shapes.AddRectangle(marginX, marginY)
Shapes.HideShape(cursor)
Shapes.SetOpacity(cursor, 30)

最後に、ファイルの表示に必要な定数もここに収めます。

これらを合わせると、Init サブルーチン全体では以下のようになります。

'初期設定
Sub Init
  '各種定数
  folderPath = "E:\Data"                        'フォルダパス
  fPathSize = Text.GetLength(folderPath) + 1    'フォルダパスの文字長
  left=200                                      '文字列表示左上のXマージン
  top=50                                        '文字列表示左上のYマージン
  marginX=240                                   '改行増分(Y方向)
  marginY=35                                    '改行増分(Y方向)
  'カーソルの定義
  GraphicsWindow.PenColor = GraphicsWindow.GetColorFromRGB(255, 0, 0)
  GraphicsWindow.BrushColor = GraphicsWindow.GetColorFromRGB(255, 0, 0)
  cursor = Shapes.AddRectangle(marginX, marginY)
  Shapes.HideShape(cursor)
  Shapes.SetOpacity(cursor, 30)
  'ファイル表示の定義
  GraphicsWindow.FontName = "Showcard Gothic"
  GraphicsWindow.FontSize = 36
  files = File.GetFiles(folderPath)             'ファイル名の取得
  cnt = Array.GetItemCount(files)               '取得したファイル名の数
  dispFilesCnt = 8                              '一度に表示するファイル名の数
EndSub

実際にファイルを表示する機能も、DispFiles サブルーチンとして切り出します。

取得したファイルがたくさんあってもいいように、1 回に表示できるファイルの数は、Init サブルーチンで変数 dispFilesCnt に格納してありますので、その個数だけ表示するようにロジックを修正します。

'ファイルの表示
Sub DispFiles
  GraphicsWindow.BrushColor = GraphicsWindow.GetColorFromRGB(255, 255, 255)
  GraphicsWindow.FillRectangle(left + 10, top - 6, marginX, (dispFilesCnt) * marginY + 20)
  cntTo = filePr + dispFilesCnt - 1
  If cntTo >= cnt Then
    cntTo = cnt
  EndIf
  GraphicsWindow.BrushColor = GraphicsWindow.GetColorFromRGB(255, 0, 0)
  For i = 1 To cntTo - filePr + 1
    fullSize = Text.GetLength(files[filePr + i - 1])
    fileName = text.GetSubText(files[filePr + i - 1], fPathSize + 1, fullSize - fPathSize)
    GraphicsWindow.DrawText(left + 10, (i - 1) * marginY + top - 6, fileName)
  EndFor
EndSub

ここで初めて出てきた変数 filePr は、何番目のファイルから表示させるかを保持するポインタです。
この変数 filePr の初期化はメインルーチンの中で行うことにしました。

メインルーチンで行うことは、filePr と cursorPr ( こちらはカーソルが表示しているファイルの何番目に当たっているかを保持するポインタ ) の初期化、最初のファイル名の表示、カーソルの表示です。

また、メインルーチンの最後に、カーソルキーでカーソルを移動させる KeyUpEvent サブルーチンを、キーを押し離したタイミングで実行するよう KeyUp イベントに割り当てます。

'Main ----------

'初期処理
Init()

'表示ファイルポインタ、カーソルポインタの初期化
filePr = 1                                      '表示するファイルのポインタ
cursorPr = 1                                    'カーソルポインタ(y方向)

'初期表示
DispFiles()                                     'ファイル
Shapes.Move(cursor, left, (cursorPr - 1) * marginY + top)
Shapes.ShowShape(cursor)

'カーソル移動用キー操作のフック
GraphicsWindow.KeyUp = KeyUpEvent

'End Main ----------

最後に、KeyUpEvent サブルーチンです。

まず、[↓] キーを押した場合は、カーソルポインタ cursorPr を 1 大きくし、カーソルをその位置に移動させてやればいいだけです。

ただし、表示しているファイルの数より下へは移動しないように抑制します。

If cursorPr < dispFilesCnt Then
  cursorPr = cursorPr + 1
  Shapes.Animate(cursor, left, (cursorPr - 1) * marginY + top, 100)
EndIf

[↑] キーの場合は、その逆を行います。もちろん、表示しているファイルを超えて上へカーソルが移動しないような抑制もかけます。

If cursorPr > 1 Then
  cursorPr = cursorPr - 1
  Shapes.Animate(cursor, left, (cursorPr - 1) * marginY + top, 100)
EndIf

[→] キーを押したら、「次のページをめくる」イメージで、表示しているファイルの続きを表示させることにします。

新しく表示するファイル群が最後のページの場合、必ずしも dispFilesCnt で指定した数だけ存在しているとは限りません。もっとも表示するファイル数が少なかった時の対処は上述のファイル表示のロジックに組み込んでありますので、ここでは単純に filePr を 1 ページ分増加させてやるだけです。。

ファイルが表示されていない位置にカーソルが取り残されることもあり得ますので、その場合は表示されている最後のファイルの位置までカーソルを移動させてやる必要があります。

てことでちょっと長くなりますがこんな感じ。

If filePr + dispFilesCnt <= cnt Then
  filePr = filePr + dispFilesCnt
  DispFiles()
  If cursorPr > cnt - filePr + 1 Then
    cursorPr = cnt - filePr + 1
    Shapes.Move(cursor, left, (cursorPr - 1) * marginY + top)
  EndIf
EndIf

[←] キーの場合は「前のページをめくる」動作となりますが、これはファイルが 1 ページ分あることが保証されていますので、特にファイル数やカーソル位置の考慮は要りません。

If filePr - dispFilesCnt > 0 Then
  filePr = filePr - dispFilesCnt
  DispFiles()
EndIf

これらの制御に、Enter キーを押したときに選択されているファイル名のフルパスダイアログボックスで表示するロジックを追加して、KeyUpEvent サブルーチン全体としては以下のようになります。

'カーソル移動用キー操作
Sub KeyUpEvent
  If GraphicsWindow.LastKey = "Up" Then
    If cursorPr > 1 Then
      cursorPr = cursorPr - 1
      Shapes.Animate(cursor, left, (cursorPr - 1) * marginY + top, 100)
    EndIf
  ElseIf GraphicsWindow.LastKey = "Down" Then
    If cursorPr < dispFilesCnt Then
      cursorPr = cursorPr + 1
      Shapes.Animate(cursor, left, (cursorPr - 1) * marginY + top, 100)
    EndIf
  ElseIf GraphicsWindow.LastKey = "Right" Then
    If filePr + dispFilesCnt <= cnt Then
      filePr = filePr + dispFilesCnt
      DispFiles()
      If cursorPr > cnt - filePr + 1 Then
        cursorPr = cnt - filePr + 1
        Shapes.Move(cursor, left, (cursorPr - 1) * marginY + top)
      EndIf
    EndIf
  ElseIf GraphicsWindow.LastKey = "Left" Then
    If filePr - dispFilesCnt > 0 Then
      filePr = filePr - dispFilesCnt
      DispFiles()
    EndIf
  ElseIf GraphicsWindow.LastKey = "Return" Then
      GraphicsWindow.ShowMessage(files[filePr + cursorPr - 1],"")
  EndIf
EndSub

これで修正全部です。実行するとこんな感じです。

 image  image
HBF770

もっとも、ローカルディスクにアクセスしますので Web 実行はできませんが。


ついでに。

表示されたダイアログボックスを Enter で閉じると、そのまま KeyUpEvent が実行され、再度ダイアログボックスが表示されます。
これは GraphicsWindow.LastKey をクリアできないので、今のところ回避できません。
このへんの仕様は今後に期待するとして、今のバージョンでモノを作るのであれば、「Enter キーに意味を持たせるならダイアログボックスとの併用を避ける」などの工夫が必要になると思います。

また、今回は透明度を指定して逃げましたが、Add~系の描画は枠だけ・中抜きの図形は描けないということに気づきました。
そんなカーソルが欲しい場合は、枠だけ・中抜きの画像を用意して AddImage とかで制御した方がいいですね。

さらに、GraphicsWindow.LastKey がどんな値を保持するのかの資料がないことに気づきました。
これ、アスキーコードとかではなく、キーを意味する文字列を持つんですね。

GraphicsWindow.LastKey

このへんの一覧表もほしいところですね。

2 コメント

  1. hiro より:

    はじめまして、こんにちわ
    最近SmallBasicを知り、勉強のため写真と文字の合成を考えていましたところ
    ちょうどこちらのサイトを発見しました
    副読本のように重宝しています

  2. さるべーじ より:

    > 副読本のように

    おおぅ、それは嬉しい。ありがとうございます。

    画像合成をされるのであれば、(80) で紹介しました FC SmallBasic Complements を使うと完成画像をキャプチャできるようになったりします。
    保存まで考えておられるのであればお試しになるだけの価値はあると思いますよー。

コメントを投稿