最近在寫的 PowerShell 小工具,處理過程會有多個選項要請使用者挑一個,最自然的設計方式通常會像這樣:

$prompt = "請選擇套餐組合"
$options = @("早上三個、晚上四個", "早上四個、晚上三個", "啃! 把拎杯當猴子逆?")
Write-Host $prompt -ForegroundColor Cyan
$idx = 1
$options | ForEach-Object {
    Write-Host "  $idx. $_" -ForegroundColor Yellow
    $idx++
}
$selIdx  = -1
while ($true) {
    $input = Read-Host "請輸入選項編號(1-$($options.Count))"
    if ([int]::TryParse($input, [ref]$selIdx) -and $selIdx -ge 1 -and $selIdx -le $options.Count) {
        break
    }
    else {
        Write-Host "請輸入有效數字。" -ForegroundColor Red
    }
}
Write-Host "您選擇了 $selIdx. $($options[$selIdx - 1])" -ForegroundColor Green

這種 CLI 風格對資訊人員不是問題,甚至有回到家的感覺。

PowerShell 具腳本特性,程式邏輯公開可供檢視,比起二進位執行檔較無包藏惡意程式疑慮,接受度高。簡單小工具若沒用到第三方程式庫 DLL,單一檔案即可運作,不需安裝、部署,具高度可攜性。基於這些優點,縱然 PowerShell 開發起來不如 C# 順手,但這幾年仍是我寫簡單小工具的優先選項。只是要將 PowerShell 推廣給廣大使用者群,純文字操作介面往往成為阻礙。

像這次小工具的使用者便是非資訊背景的行政或業務人員,全無 DOS/Linux 經驗,想到必須要教這輩子只用過 GUI 的老爹老媽大哥小妹凱子馬子學會操作 CLI,那畫面太美我不敢看。

我想到一個解法:既然 PowerShell 可以調用 System.Windows.Forms API,那麼動態產生一個 Winows Form 放三個按鈕不是難事。效果會像這樣:

程式的特色是完全使用 Windows 內建程式庫,不依賴任何第三方元件,用一個 .ps1 檔全部搞定,PowerShell 5.1 / 7 都可執行。

靠 Github Copilot 幫忙,我還嘗試了一些花式技巧,像是:用 [System.Windows.Forms.TextRenderer]::MeasureText() 測量文字寬度決定視窗大小、使用 System.Drawing.Bitmap 動態產生表單左上角的圖示檔。有了 AI,Coding 變得更好玩了~

完整程式範例如下,有興趣的同學請自取參考。

$ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
Function ShowOptionFormUI {
    param(
        [Parameter(Mandatory=$true)]
        [string]$title,
        [Parameter(Mandatory=$true)]
        $options
    )
    $optionCount = $options.Length
    
   # Create a temporary form to help with text sizing
   $tempForm = New-Object System.Windows.Forms.Form
   $tempForm.AutoScaleMode = [System.Windows.Forms.AutoScaleMode]::None
   $tempFont = $tempForm.Font

   $maxBtnWidth = ($options | ForEach-Object {
       $btnTextSize = [System.Windows.Forms.TextRenderer]::MeasureText($_, $tempFont)
       $btnTextSize.Width + 30  # Add padding
   } | Measure-Object -Maximum).Maximum
   $tempForm.Dispose()

    [int]$width = [Math]::Max($maxBtnWidth, 200)
    [int]$height = 25 * $optionCount
    $form = New-Object System.Windows.Forms.Form
    $form.Text = $title
    $form.ClientSize = New-Object System.Drawing.Size(($width + 20), ($height + 20))
    $form.MaximizeBox = $false
    $form.MinimizeBox = $false
    $form.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen

    # 動態產生表單圖示
    $bitmap = New-Object System.Drawing.Bitmap 32, 32
    $graphics = [System.Drawing.Graphics]::FromImage($bitmap)
    $graphics.Clear([System.Drawing.Color]::Transparent)
    $brush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::Red)
    $graphics.FillEllipse($brush, 0, 0, 32, 32)
    $pen = New-Object System.Drawing.Pen([System.Drawing.Color]::Black, 2)
    $graphics.DrawEllipse($pen, 0, 0, 32, 32)
    $font = New-Object System.Drawing.Font("Tahoma", 16, [System.Drawing.FontStyle]::Bold)
    $stringFormat = New-Object System.Drawing.StringFormat
    $stringFormat.Alignment = [System.Drawing.StringAlignment]::Center
    $stringFormat.LineAlignment = [System.Drawing.StringAlignment]::Center
    $brush.Color = [System.Drawing.Color]::White
    $graphics.DrawString("?", $font, $brush, [System.Drawing.RectangleF]::new(4, 4, 26, 26), $stringFormat)
    $font.Dispose()
    $stringFormat.Dispose()
    $graphics.Dispose()
    $brush.Dispose()
    $pen.Dispose()
    $form.Icon = [System.Drawing.Icon]::FromHandle($bitmap.GetHicon())
    $bitmap.Dispose()

    $flowPanel = New-Object System.Windows.Forms.FlowLayoutPanel
    $flowPanel.Dock = [System.Windows.Forms.DockStyle]::Fill 
    $flowPanel.FlowDirection = [System.Windows.Forms.FlowDirection]::TopDown 
    $flowPanel.WrapContents = $false
    $flowPanel.AutoScroll = $true 
    $form.Controls.Add($flowPanel)

    $form.Tag = ''
    $options | ForEach-Object {    
        $button = New-Object System.Windows.Forms.Button
        $button.Text = $_
        $button.Width = $flowPanel.ClientSize.Width - 10
        $button.Height = 25
        $button.Margin = New-Object System.Windows.Forms.Padding(5, 2, 5, 2)
        $button.Add_Click({
            param($senderButton)
            $form.Tag = $senderButton.Text
            $form.DialogResult = [System.Windows.Forms.DialogResult]::OK
            $form.Close()
        })
        $flowPanel.Controls.Add($button)
    }
    $form.Topmost = $true        
    if ($form.ShowDialog() -eq 'OK') {
        return $form.Tag
    }
    return $null
}

$options = @("早上三個、晚上四個", "早上四個、晚上三個", "啃! 把拎杯當猴子逆?")
$result = (ShowOptionFormUI "請選擇套餐組合" $options)
if ($null -ne $result) {
    [System.Windows.Forms.MessageBox]::Show("你選擇了: $result", "點餐結果") | Out-Null
}

This post discusses creating a user-friendly PowerShell tool using Windows Forms for non-technical users. The tool dynamically generates a Windows Form with buttons and an icon, allowing users to select options via a graphical interface. The provided PowerShell script demonstrates how to achieve this without third-party components.


Comments

Be the first to post a comment

Post a comment