PowerShellでファイルマージャのような代物をつくる。

コンピュータ

PowershellでGUIアプリケーションを作っていると、部品としてエクスプローラの様なファイルマネージャが欲しくなります。
今回はListViewの学習がてらファイルマネージャのような代物を作ってみました。
機能的にはディレクトリ内のファイルを表示することとディレクトリを移動する事だけですので、アプリケーションとしては役に立つ代物では無いですが、これをベースに何かを作れればと思います。

<#
.SYNOPSIS
 ファイルマネージャ的なアプリ
#>

using namespace System.Windows.Forms
using namespace System.Drawing
using namespace System.IO

Param(
    [string]$Path = "."
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "STOP"

# アセンブリのロード
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing


function Main
{
    # コントロールのサイズ変更
    function controls_resize($form) {
        
        $y = 0
        $btn_width = 60

        $m = $form.Controls["MENUBAR"] | % {
            $_.Location = ("0,{0}" -f $y)
            $y = $y + $_.Height
            $_
        }
        $t = $form.Controls["CURRENTPATH"] | % {
            $_.Location = ("0,{0}" -f $y)
            $_.Width = $form.ClientSize.Width - $btn_width
            $_
        }

        $b = $form.Controls["CHANGE"] | % {
            $_.Location = ("{0},{1}" -f $t.Width, $y)
            $y = $y + $_.Height
            $_.Width = $btn_width
            $_
        }
        

        
        $form.Controls["FILELIST"] | % {
        
            $w = $form.ClientSize.Width
            $h = $form.ClientSize.Height - $y
            $_.Location = ("0,{0}" -f $y)
            $y = $y + $h
            $_.Size = ("{0},{1}" -f $w, $h)
        }
    }
    
    # フォームのロード
    function form_load($form) {
        $view = $form.Controls["FILELIST"]
        $txtb = $form.Controls["CURRENTPATH"]
        $btn = $form.Controls["CHANGE"]


        # 詳細表示
        $view.View = [View]::Details

        $view.Columns.Add("名称",240,[HorizontalAlignment]::Left)
        $view.Columns.Add("サイズ",60,[HorizontalAlignment]::Right)
        
        $btn.Text = "変更"

        filelist_update $form
    }

    # ファイルリストの更新
    function filelist_update($form) {
        $view = $form.Controls["FILELIST"]
        $txtb = $form.Controls["CURRENTPATH"]

        # カレントディレクトリをセット
        $txtb.Text = (Convert-Path .)
        
        # リストビューのアイテムをクリア
        $view.Items.Clear()


        if ((Get-Item .).Parent -ne $null) {
            $lvi = [ListViewItem]::new("..")
            $lvi.SubItems.Add("<DIR>")
            $view.Items.Add($lvi)
        }

        Get-ChildItem | % {
            $lvi = [ListViewItem]::new($_.Name)
            
            if ($_.PSIsContainer) {
                $lvi.SubItems.Add("<DIR>")
            } else {
                $lvi.SubItems.Add($_.Length)
            }


            $view.Items.Add($lvi)
        }
    }

    # メインフォームの生成
    $form = [Form]::new() | % {
        $_.Name = "MAIN_FORM"
        $_.Size = "800,600"
        $_.Add_Load({form_load($this)})
        $_.Add_Shown({controls_resize($this)})
        $_.Add_Resize({controls_resize($this)})
        $_
    }
    
    # コントロールの初期化配列
    $controls = @(
        
        # リストビュー
        [ListView]::new() | % {
            $_.Name = "FILELIST"
            
            $_.Add_ItemActivate({
                $type = $this.FocusedItem.SubItems[1].Text
                if ($type -eq "<DIR>") {
                    # ディレクトリが選択された
                    Set-Location (Join-Path (Convert-Path .) $this.FocusedItem.Text)
                    filelist_update $this.Parent
                } else {
                    # ファイルが選択された
                }
            })
            
            $_
        }

        # ボタン
        [Button]::new() | % {
            $_.Name = "CHANGE"

            # クリックイベント
            $_.Add_click({
                $o = [FolderBrowserDialog]::new() | % {
                    $_.SelectedPath = (Convert-Path .)
                    $_
                }
                if ($o.ShowDialog() -eq "OK") {
                    Set-Location $o.SelectedPath
                    filelist_update $this.Parent
                }

            })

            $_
        }

        # テキストボックス
        [Textbox]::new() | % {
            $_.Name = "CURRENTPATH"
            $_
        }

        # メニューバー
        [MenuStrip]::new() | % {
            $_.Name = "MENUBAR"

            $menu_items = @(
                # ファイル
                [ToolStripMenuItem]::new() | % {
                    $_.Name = "MENU_FILE"
                    $_.Text = "ファイル"

                    # 閉じる
                    $sub_items = @(
                        [ToolStripMenuItem]::new() | % {
                            $_.Name = "MENU_CLOSE"
                            $_.Text = "閉じる"

                            # クリックイベント
                            $_.Add_Click({
                                $form = $this.OwnerItem.Owner.Parent
                                $form.Close()
                            })

                            $_
                        }
                    )

                    # ドロップダウンメニューアイテムの配置
                    $_.DropDownItems.AddRange($sub_items)
                    $_
                }
            )

            # メニューアイテムの配置
            $_.Items.AddRange($menu_items)
            $_
        }

    )

    # フォームにコントロールを配置
    $form.Controls.AddRange($controls)

    # フォームの表示
    $form.ShowDialog()
}

if ($Path -eq '.') {
    $Path = (Convert-Path .)
}
Main $Path

機能説明
「変更」ボタンを押すとディレクトリ選択のダイアログボックスが表示され、ディレクトリを選択することでカレントディレクトリを変更することが出来ます。
また、リストビュー内の「サイズ」項目が<DIR>がディレクトリになります。ダブルクリックすると、そのディレクトリに移動します。ちなみに「..」は親ディレクトリになります。

テキストボックスにカレントディレクトリのパスが表示されていますが、表示しているだけで変更しても何もおきません。

終了は×又はメニューの「ファイル」→「閉じる」で終了します。

機能拡張

ファイルマネージャなのでファイルを操作させたいわけですが、ファイルをダブルクリックした場合の処理を追加する場合は、コメントの「ファイルが選択された」の辺りにコードを追加すれば良いと思います。

右クリックでコンテキストメニューを表示させたり、ファイル選択に連動して何かを実行したりなどは、今後機能を追加できたらなぁと思います。

 

あとがき

 

当初はエクスプローラとそっくりな代物を作ろうと思いましたが、ListViewにファイルアイコンを表示させる方法を調べてみて面倒なので挫折しました。システムアイコンが使えれば良かったのですがAPIで無いと取得出来ないとか、.Netだとファイルに関連付けられたファイルのアイコンを取得する方法はあるようですが、フォルダアイコンが取得出来ないとか…。自前でアイコンデータを作れば良いのかもしれませんが、その予定はありません。

今後これをベースにファイルに紐づく何かを作ろうかと思います。何を紐づけるかは置いておいて、紐づけの管理をどうするか検討中です。連想配列とその連想配列を何らかの形式で保存するのが比較的簡単そうですが、ファイル件数の増加に伴い処理が重くなるのが予想されます。作り方にもよりますが、今時のPCのパワーなら少々問題ある作りでも何とかなりそうな気もします。

安心安全を考えれば、データベースで管理するのが好いと思いますが大げさな様な気もします。一つのアプリケーションの為にデータベースの構築が必須となるとさすがに…。以前MS Accessのランタイム版を使う方法は調べたので、それでいけるとは思いますが…

MS Acessのランタイムと言えば、製品版のMS Accessで作成した.mdbファイル(.accdbファイル)を実行する為の環境を構築するパッケージです。ランタイムだけでは基本的にフォームやレポートなどの開発することが出来ません。開発する為には製品版のMS Accessが必要となります。

ランタイムでも、データベースの構築(.mdbファイルの作成)はADODBやADOX経由で行うことはできるので、これと.Netのフォームを組み合わせるとMS Accessのランタイムでデータベースを使ったアプリケーションの開発が出来ます。MS AccessのGUIによる開発機能を使えないので全てプログラミングによる開発になりますが…

バックエンドのデータベースにMS Accessを使いPowerShellかC#で.Netのフォームを使ったアプリケーションも、そのうち試したいと思います。(それがファイルマネージャになるかは未定ですが…)

一昔前と比べると無償でソフト開発がしやすい環境が整ってきました。ネットの情報を得られる点でもプログラミングの学習のしやすさは飛躍的に向上しています。私自信がそれについていけてないのが残念なところです。「うん十年前に今の環境があれば…」といつも考えますが、きっとあったとしても状況はあまり変わらないような気もします。昔は必要に迫られてプログラミングをしていましたが、今はネットを調べれば大概の解決案が見つかります。プログラミングを学ぶよりネットの検索術を学ぶ方が有用な時代かもしれません。それでもプログラミングを学んだ経験と開発した青果物成果物が残りますので、これからもコードの粗製乱造をしていきたいと思います。

コメント