🐶

PowerShellにも設定画面がほしい

2024/12/23に公開

何言ってやがんだとか思われるんでしょーがおっさんは本気です。
テキストで設定シコシコ書くのはさ、もーDOS時代にとっくに飽きてんだわ。おっさん。
ってわけで面倒事はPropertyGridに全部放り投げます。おいコラ、働けよ、計算機。

ちなみにPropertyGridってのはソフト屋なら某IDEで絶対見たことあるコレのコト

本題

いやもーここ数年弄ってない動的フォーム生成とか中々脳にキますな

function ShowSettingDialog {
    param (
        [Parameter(Mandatory = $true)] [string]        $Title,
        [Parameter(Mandatory = $true)] [System.Object] $Setting
    )
    begin {}
    process {
        # フォーム生成
        $frmMain = New-Object System.Windows.Forms.Form -Property @{
            Text          = $Title                                                      # タイトル
            StartPosition = 'CenterScreen'                                              # 表示位置
            Size          = New-Object System.Drawing.Size(480,320)
            Padding       = New-Object System.Windows.Forms.Padding(5)
        }

        $tlpMain = New-Object System.Windows.Forms.TableLayoutPanel -Property @{
            Dock     = [System.Windows.Forms.DockStyle]::Fill
            RowCount = 2
        }

        $pnlBody = New-Object System.Windows.Forms.Panel -Property @{
            Dock = [System.Windows.Forms.DockStyle]::Fill
        }
        $grdProp = New-Object System.Windows.Forms.PropertyGrid -Property @{
            Dock           = [System.Windows.Forms.DockStyle]::Fill
            SelectedObject = $Setting
        }

        $pnlTail = New-Object System.Windows.Forms.Panel -Property @{
            Dock = [System.Windows.Forms.DockStyle]::Fill
        }
        $btnOK = New-Object System.Windows.Forms.Button -Property @{
            Dock                    = [System.Windows.Forms.DockStyle]::Right
            Size                    = New-Object System.Drawing.Size(128, 0) # ボタン巾のみ指定可能
            Text                    = "OK"
            UseVisualStyleBackColor = $true
            DialogResult            = [Windows.Forms.DialogResult]::OK
        }
        $btnCancel = New-Object System.Windows.Forms.Button -Property @{
            Dock                    = [System.Windows.Forms.DockStyle]::Right
            Size                    = New-Object System.Drawing.Size(128, 0) # ボタン巾のみ指定可能
            Text                    = "Cancel"
            UseVisualStyleBackColor = $true
            DialogResult            = [Windows.Forms.DialogResult]::Cancel
        }
        
        $null = $pnlBody.Controls.Add($grdProp)
        $null = $pnlTail.Controls.Add($btnOK)
        $null = $pnlTail.Controls.Add($btnCancel)
        $null = $tlpMain.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100)))
        $null = $tlpMain.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 50))) # ボタン高さはコレ
        $null = $tlpMain.Controls.Add($pnlBody, 0, 0)
        $null = $tlpMain.Controls.Add($pnlTail, 0, 1)
        $null = $frmMain.Controls.Add($tlpMain)

        # フォーム表示
        $null = $frmMain.ShowDialog()

        return $frmMain.DialogResult
    }
    end {}
}

使い方

で、まぁコレをこんな感じで使うんですわ

class AppSettings {
    [System.ComponentModel.Description("名前")]
    [string]$AppName
    [int]$Version
    [bool]$AutoUpdate
    [string]$LogFilePath
    [System.Diagnostics.SourceLevels]$LogLevel
}
$settings = [AppSettings]@{
    AppName = "My Application"
    Version = 1
    AutoUpdate = $true
    LogFilePath = "C:\app.log"
    LogLevel = [System.Diagnostics.SourceLevels]::Information
}
$ret = ShowSettingDialog "Title" $settings
if ($ret -eq "OK") {
    $settings
}

ってわけでコレが冒頭のアレになります
つまりクラス定義して変数ブチ込んであとは.Net様に全てお任せ

説明

まんま使える属性はこの辺見てChatGPTなり何なりにお願いすれば大体オッケーです
ただしカスタム属性は単発利用では苦労に見合わない感じなんで避けたほうがいいかと
https://dobon.net/vb/dotnet/control/propertygrid.html

ところでコイツ、現実的にはConvertFrom-Jsonで取ってきた設定を編集すんですが
何も考えずConvertFrom-Jsonすると戻り値はPSCustomObjectになってますんで
そのまま突っ込まずに一度クラスに変換してください

面倒なんでおっさんはとりあえず以下のように強引にリフレクションで変換してますが
多分この話と同じでコンストラクタ作っちゃえばいいんでしょうねぇ...
何時か試す
https://zenn.dev/npwshy/articles/063c540e0f3252

function ConvertFromPSCO {
    param (
        [Parameter(Mandatory = $true)] [System.Type]   $Type,
        [Parameter(Mandatory = $true)] [PSCustomObject] $Data
    )
    begin {}
    process {
        $inst = New-Object -TypeName $Type.FullName
        $Data.PSObject.Properties | ForEach-Object {
            $PSCOName = $_.Name
            $PSCOData = $_.Value
            $InstProp = $Type.GetProperty($PSCOName)
            if ($null -ne $InstProp) {
                if ($InstProp.PropertyType.IsPrimitive) {
                    $inst.($InstProp.Name) = $PSCOData
                } elseif (($InstProp.PropertyType.IsEnum)) {
                    $inst.($InstProp.Name) = $PSCOData
                } elseif (($InstProp.PropertyType.Name -eq "string") -or ($InstProp.PropertyType.Name -eq "datetime") -or ($InstProp.PropertyType.Name -eq "decimal")) {
                    $inst.($InstProp.Name) = $PSCOData
                } elseif ($InstProp.PropertyType.IsArray) {
                    $list = @()
                    foreach ($elm in $PSCOData) {
                        if ($InstProp.PropertyType.GetElementType().IsPrimitive) {
                            $list += $elm
                        } elseif (($InstProp.PropertyType.GetElementType().IsEnum)) {
                            $list += $elm
                        } elseif (($InstProp.PropertyType.GetElementType().Name -eq "string") -or ($InstProp.PropertyType.GetElementType().Name -eq "datetime") -or ($InstProp.PropertyType.GetElementType().Name -eq "decimal")) {
                            $list += $elm
                        } else {
                            $list += ConvertFromPSCO -Type $InstProp.PropertyType.GetElementType() -Data $elm 
                        }
                    }
                    $inst.($InstProp.Name) = $list
                } else {
                    $inst.($InstProp.Name) = ConvertFromPSCO -Type $InstProp.PropertyType.GetElementType() -Data $PSCOData
                }
            }
        }
        return $inst
    }
    end {}    
}

Discussion