WSHでファイル選択ダイアログを表示するのに飽きたので、他言語にて。

飽きた。

ファイル選択ダイアログを表示する為の標準的な手段が用意されていないのにダイアログを表示しようだなんて高望みするから妙な苦労をするのだ。やはりここは別の言語に活路を見出すべきだろう。

PowerShellの場合

例えばPowerShellなら.NET Frameworkのオブジェクトを簡単に呼び出すことができる訳で、ネット上で山ほど書かれているように以下の方法でファイル選択ダイアログを表示できる。

Set-StrictMode -version Latest

[void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')

$dialog = New-Object System.Windows.Forms.OpenFileDialog
$dialog.Filter = 'Text Files|*.txt|Csv Files|*.csv|All Files|*.*'
$dialog.InitialDirectory = '.'

if ($dialog.ShowDialog() -eq "OK") {
    "選択したのはこれか?`n`"" + $dialog.FileName + '"'
} else {
    'ファイルは選択されなかったみたいだ……。'
}

これは少し古いサンプルのようだ。

LoadWithPartialName()は互換性の為に残されている。できればLoad()を使うべきらしい。

Set-StrictMode -version Latest

Set-Variable `
    -name ASM_LOAD_PARAM `
    -value 'System.Windows.Forms, Version=2.0.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089' `
    -option constant

[void][System.Reflection.Assembly]::Load($ASM_LOAD_PARAM)

$dialog = New-Object System.Windows.Forms.OpenFileDialog
$dialog.Filter = 'Text Files|*.txt|Csv Files|*.csv|All Files|*.*'
$dialog.InitialDirectory = '.'

if ($dialog.ShowDialog() -eq "OK") {
    "選択したのはこれか?`n`"" + $dialog.FileName + '"'
} else {
    'ファイルは選択されなかったみたいだ……。'
}

しかしバージョン番号やら何やら付加するのは面倒だ。

PowerShell 2.0以降ならAdd-Typeを使えばよいだろう。PowerShell 3.0も出たことだし、そろそろ2.0の機能を積極的に使っても大丈夫だろう。

Set-StrictMode -version Latest

Add-Type -assemblyName System.Windows.Forms

$dialog = New-Object System.Windows.Forms.OpenFileDialog
$dialog.Filter = 'Text Files|*.txt|Csv Files|*.csv|All Files|*.*'
$dialog.InitialDirectory = '.'

if ($dialog.ShowDialog() -eq "OK") {
    "選択したのはこれか?`n`"" + $dialog.FileName + '"'
} else {
    'ファイルは選択されなかったみたいだ……。'
}

細かい点はともかく、今のところはSystem.Windows.Forms.OpenFileDialogを使う方法が鉄板のようだ。.NET Frameworkがインストールされている環境でなら問題なく動作する。

実はこの逆の方法――WSHからSystem.Windows.Forms.OpenFileDialogを使うことができないか試してみたけど、COMコンポーネントとして登録されていないのでダメなようだ。残念。

ところで、上記の3例はSTAで実行しないとダイアログが表示されない可能性がある。手元の環境では、Windows XP上ではMTAでもダイアログが表示されたのだけど、Windows 7 64bit環境ではSTAでないと表示されなかった。表示されないだけでなく、ShowDialog()の所で処理が進まなくなってしまい、タスクマネージャでpowershell.exeのプロセスを強制終了するしかなくなってしまった。

ネット情報によると、どうもVista SP1以降ではSTAでないとダイアログが表示されないようだ。

PowerShellの場合:おまけ

ところでPowerShellからはCOMコンポーネントも使えるので、例えばWindows XPPowerShellをインストールしている場合は以下の方法でもファイル選択ダイアログを表示できる。

Set-StrictMode -version Latest

$dialog = New-Object -com UserAccounts.CommonDialog
$dialog.Filter = 'Text Files|*.txt|Csv Files|*.csv|All Files|*.*'
$dialog.InitialDir = '.'

if ($dialog.ShowOpen()) {
    "選択したのはこれか?`n`"" + $dialog.FileName + '"'
} else {
    'ファイルは選択されなかったみたいだ……。'
}

単にUserAccounts.CommonDialogを呼び出しているだけ。使い方はWSHの場合と同じだ。

F#の場合

ここでF#のコードが出てくるなんてどれ程の人が予想し得ただろうか?

F#の開発環境が整っている場合、F# Interactive(fsi.exe)が使用できるようになっているはずだ。F# Interactiveを使うとF#のコードをコンパイルせずにスクリプトのように実行できる。

D:\tmp>type hello.fsx
printfn "hello, world"

let msg = "hello, world"
printfn "%s" msg

D:\tmp>fsi.exe --exec hello.fsx
hello, world
hello, world

D:\tmp>

ということは、状況次第ではちょっとしたツールをF#のスクリプトで作成しても問題ないはずだ。少なくとも自分用のツールなら……*1

F#の文法はOCamlと同様に、C#などに比べて記述すべき約束事が結構少ない。ファイル選択ダイアログを表示するサンプルコードはPowerShell並に短くて済む。

open System.Windows.Forms

let filter = "Text Files|*.txt|Csv Files|*.csv|All Files|*.*" in
  let initdir = "."
  let dialog = new OpenFileDialog(InitialDirectory = initdir, Filter = filter)

  if dialog.ShowDialog() = DialogResult.OK then
    printfn "選択したのはこれか?\n\"%s\"" dialog.FileName
  else
    printfn "ファイルは選択されなかったみたいだ……。";;

実はF#のコードを書くのは初めてで、しかも以前OCamlのコードを書いてから時間が経っていることもあり、何となく不恰好なコードになってしまったと思う。

ところでファイル選択の処理を関数として切り出そうとした所、引数を取らない関数の定義方法が分からなくて少し混乱した。調べたところunit型を引数として取るように定義するのがお約束らしい。

open System.Windows.Forms

let select_file () =
  let filter = "Text Files|*.txt|Csv Files|*.csv|All Files|*.*"
  let initdir = "."
  let dialog = new OpenFileDialog(InitialDirectory = initdir, Filter = filter)

  if dialog.ShowDialog() = DialogResult.OK then
    dialog.FileName
  else
    ""

match select_file () with
  | "" -> printfn "ファイルは選択されなかったみたいだ……。"
  | s ->  printfn "選択したのはこれか?\n\"%s\"" s

私の頭の中では、

  1. 参照透過な関数は1つ以上の引数を取り、引数の値(の組み合わせ)ごとに常に同じ評価値を返す。
  2. 引数を1つもとらない場合、評価値は常に同じである。つまりそれは関数ではなく単なる値(名前付き定数のようなもの)である。
  3. 引数をとらないにも関わらず「(F#における)関数」である必要がある(「単なる値」として扱うことができない事情がある)ということは、それは多分参照透過でない関数である。
    • 但し、例えば「関数を引数に取る関数」に引き渡す為に関数化する、というケースは除く。

という風に認識しているのだけど、それで正しいのだろうか? うーん、やはりこの辺りに基礎不足が見られるなあ。

*1:しかし自分用のツールならファイル選択ダイアログなんて不要なのだけど。