【PowerShellでGUI 7】DataGridView 応用編2 画像表示列

前回に引き続き、、PowerShellで作成するWindwos Formアプリの部品データグリッドビューの応用編で、今回はデータグリッドビューの中に
 ・画像を表示する列
を設定する方法です。

DataGridViewImageColumn

今回のサンプルアプリの画面イメージはこちら。
今回も、前々回作成した簡易なエクスプローラーライクなものをベースにしています。

そして上記画面のソースコードがこちら

using namespace System.Windows.Forms
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

[Application]::EnableVisualStyles()

# フォーム
$frame = New-Object Form -Property @{
    Text            = 'Sample App'
    Size            = New-Object Drawing.Size(650, 600)
    MaximizeBox     = $false
    FormBorderStyle = 'FixedDialog'
    Font            = New-Object Drawing.Font('Meiryo UI', 8.5)
}

# 表示対象フォルダー設定用テキストボックス
$tbxFolder = New-Object TextBox -Property @{
    Location      = New-Object Drawing.Point(20, 20)
    Width         = 400
    ReadOnly      = $True
}
$frame.Controls.Add($tbxFolder)

# フォルダー選択ダイアログ表示ボタン
$btnRef = New-Object Button -Property @{
    Location      = New-Object Drawing.Point(425, 20)
    Text          = '...'
    Width         = 30
}
$frame.Controls.Add($btnRef)

$fbdRef = New-Object FolderBrowserDialog -Property @{
    ShowNewFolderButton = $false
}

# データグリッドビュー
$DataGridV = New-Object DataGridView -Property @{
    Location        = New-Object Drawing.Point(20, 50)
    Size            = New-Object Drawing.Size(600, 500)
    AutoSizeColumnsMode = "AllCells"
    ReadOnly            = $True
    AllowUserToAddRows  = $false
    ColumnCount         = 3
    RowHeadersVisible   = $false
    MultiSelect         = $false
    SelectionMode       = 'FullRowSelect'
}
$DataGridV.ColumnHeadersDefaultCellStyle.Alignment = 'MiddleCenter'
$DataGridV.RowTemplate.Height = 150

# 各列の情報設定
$DataGridV.Columns[0].Name  = "名前"
$DataGridV.Columns[1].Name  = "更新日時"
$DataGridV.Columns[1].DefaultCellStyle.Format = 'yyyy/MM/dd HH:mm:ss'
$DataGridV.Columns[2].Name  = "サイズ"
$DataGridV.Columns[2].DefaultCellStyle.Format = '#,##0 KB'
$DataGridV.Columns[2].DefaultCellStyle.Alignment = 'MiddleRight'

# 画像イメージ列
$colImage = New-Object DataGridViewImageColumn -Property @{
    Name = "Image"
    ImageLayout = 'Zoom'
    Width = 250
    AutoSizeMode = 'Fill'
}
$colImage.DefaultCellStyle.NullValue = $null
$colImage.DefaultCellStyle.Padding = New-Object Padding(3)
$colImage.DefaultCellStyle.Alignment = 'MiddleCenter'
[void]$DataGridV.Columns.Add($colImage)

# 表示可能な画像形式の拡張子一覧を配列に格納
$ImageExts = @()
foreach ($exts in ([System.Drawing.Imaging.ImageCodecInfo]::GetImageDecoders()).FilenameExtension){
    $ImageExts += ($exts.Replace('*','') -split ';')
}
# DataGridViewに値を設定する処理
$scrbSetGrid = {
    $DataGridV.Rows.Clear()
    $files = (Get-ChildItem $tbxFolder.Text | Where-Object { $_.Extension -in $ImageExts })
    foreach ($fi in $files){
        switch ($fi.Length) {
            {$_ -lt 1024} { $size = 1  }
            Default {$size = ($_ / 1024)}
        }
        # 画像の回転情報を取得
        $bmporg = New-Object System.Drawing.Bitmap($fi.FullName)
        $rpi = ($bmporg.PropertyItems | Where-Object { $_.ID -eq 0x0112 })
        $rft = [System.Drawing.RotateFlipType]::RotateNoneFlipNone
        if (($rpi | Measure-Object).Count -gt 0){
            switch ($rpi.Value[0]) {
                3 { $rft = [System.Drawing.RotateFlipType]::Rotate180FlipNone }
                6 { $rft = [System.Drawing.RotateFlipType]::Rotate90FlipNone }
                8 { $rft = [System.Drawing.RotateFlipType]::Rotate270FlipNone }
            }
        }
        $bmporg.Dispose()

        # 開いたセッションを残さないために、FileStreamで読み込んで最後にClose
        $fs = New-Object System.IO.FileStream($fi.FullName, 'Open', 'Read')
        $bmpclone = New-Object System.Drawing.Bitmap($fs)
        $bmpclone.RotateFlip($rft)
        $fs.Close()

        $DataGridV.Rows.Add($fi.Name, $fi.LastWriteTime, $size, $bmpclone)
    }
}

$btnRef.Add_Click({
    if ($fbdRef.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK){
        $tbxFolder.Text = $fbdRef.SelectedPath
        . $scrbSetGrid
    }
})

# ダブルクリック時の処理
$DataGridV.Add_CellDoubleClick({
    if ($_.RowIndex -ge 0){
        Start-Process -FilePath (Join-Path $tbxFolder.Text $DataGridV.Rows[$_.RowIndex].Cells[0].Value)
    }
})

$frame.Controls.Add($DataGridV)

$frame.ShowDialog()

解説

60~69行目が画像を表示する列の定義をしている部分になります。

$colImage = New-Object DataGridViewImageColumn -Property @{
    Name = "Image"
    ImageLayout = 'Zoom'
    Width = 250
    AutoSizeMode = 'Fill'
}
$colImage.DefaultCellStyle.NullValue = $null
$colImage.DefaultCellStyle.Padding = New-Object Padding(3)
$colImage.DefaultCellStyle.Alignment = 'MiddleCenter'
[void]$DataGridV.Columns.Add($colImage)

列のオブジェクトをDataGridViewImageColumnクラスとして定義し、定義したオブジェクトをデータグリッドビューに追加するという流れになります。


設定しているプロパティについて、特徴的なものを以下に記載しておきます。

ImageLayout画像の表示の仕方の設定で、既定値(Nomal)だと等倍表示されてしまうため、セルの大きさに合わせて自動的に拡大/縮小されて表示されるようにZoomを設定しています。
AutoSizeMode列幅について、他のセルの幅は表示内容によって自動調整ように、DataGridViewのプロパティで設定していますが、画像表示をする列幅は、DataGridViewの幅いっぱいいっぱいを使って表示できるように、Fillで設定しています。
DefaultCellStyle.Padding画像を表示したときに、上下のセルとの間に多少は余白が欲しかったので、設定しています。

72~75行目では、DataGridViewImageColumnに表示可能なフォーマット(拡張子)の一覧を取得しています。
GetImageDecodersでは、FilenameExtensionが

*.BMP;*.DIB;*.RLE
*.JPG;*.JPEG;*.JPE;*.JFIF
*.GIF
*.EMF
*.WMF
*.TIF;*.TIFF
*.PNG
*.ICO

といった形式で取得されてきますが、 Get-ChildItem の結果の Extension の項目に対して、-in 演算子で絞り込みを行いたいので、取得結果から’*’は削除(Replace('*',''))し、’;’で配列に分割(-split ';')したものを配列に格納しています。

86行目~102行目がグリッドビューに画像を表示させるための処理になってて、まずは前半の86~96行目が、EXIFのOrientationタグの情報を取得し、後半でRotateFlipにより画像を回転させるための設定値を設定している部分です。

        $bmporg = New-Object System.Drawing.Bitmap($fi.FullName)
        $rpi = ($bmporg.PropertyItems | Where-Object { $_.ID -eq 0x0112 })
        $rft = [System.Drawing.RotateFlipType]::RotateNoneFlipNone
        if (($rpi | Measure-Object).Count -gt 0){
            switch ($rpi.Value[0]) {
                3 { $rft = [System.Drawing.RotateFlipType]::Rotate180FlipNone }
                6 { $rft = [System.Drawing.RotateFlipType]::Rotate90FlipNone }
                8 { $rft = [System.Drawing.RotateFlipType]::Rotate270FlipNone }
            }
        }
        $bmporg.Dispose()

後半の99行目~102行目がDataGridViewImageColumnに表示する画像を設定している部分になります。

        $fs = New-Object System.IO.FileStream($fi.FullName, 'Open', 'Read')
        $bmpclone = New-Object System.Drawing.Bitmap($fs)
        $bmpclone.RotateFlip($rft)
        $fs.Close()


DataGridViewImageColumnに紐づけるSystem.Drawing.Bitmapオブジェクトの定義の仕方の部分がポイントです。
前半と同じようにファイル名を指定してSystem.Drawing.Bitmapオブジェクトを定義すると、Disposeで破棄するまで、対象の画像ファイルをロックしてしまうのですが、System.Drawing.BitmapオブジェクトをDisposeしてしまうと、データグリッドビューで画像が表示できなくなってしまいます。
そこで、画像ファイルを一旦FileStreamに読み込んで、読み込んだFileStreamを使ってSystem.Drawing.Bitmapオブジェクトを定義し、その後、FileStreamはCloseすることで、画像ファイルへのロックが残らずにデータグリッドビューに画像が表示されることになります。

今回はここまで。
次回何を記載しようかはちょっと悩み中です。。。

以上、参考になれば幸いです。