PowerShellでポップアップメニューを表示する
PowerShellでポップアップメニューを表示してみたくなった。
ContextMenuStripを使うべきっぽいんだけど、横着してContextMenuで。
一度でも呼ぶと、System.Windows.Formsがロードされるのが不可逆。
追記 20150707
フォント変更可能なバージョンを作成しました。
http://d.hatena.ne.jp/junjun777/20150707/powershell_popupmenu
スクリプト
function Show-PopupMenu() { Param ( [string[]]$Items, [string]$Splitter = ',', [string]$Separator = '^', [int]$x = [int]::MinValue, [int]$y = [int]::MinValue ) if(!('System.Windows.Forms.Form' -as [type])) { Add-Type -AssemblyName System.Windows.Forms } if($Items.Count -eq 1 -and ![string]::IsNullOrEmpty($Splitter)) { $Items = $Items[0].Split($Splitter) } $form = New-Object System.Windows.Forms.Form $form.FormBorderStyle = 'None' $form.Opacity = 0 $form.ShowInTaskbar = $false $form.StartPosition = 'Manual' $onClick = { Param ($sender, $ev) $form.Name = $sender.Name } $context = New-Object System.Windows.Forms.ContextMenu $mis = New-Object 'System.Collections.Generic.List[System.Collections.Generic.List[System.Windows.Forms.MenuItem]]' $mis.Add((New-Object 'System.Collections.Generic.List[System.Windows.Forms.MenuItem]')) $idxb = 0 $idxs = 0 $idxsp = @() for($i = 0; $i -lt $Items.Count; $i++) { $put = $true if([string]::IsNullOrEmpty($Separator)) { $it = @() $it += $Items[$i] } else { $it = $Items[$i].Split($Separator, 2) } if($it[0].Length -gt 0) { if($it[0][0] -eq '\') { switch($it[0][1]) { '>' { $idxsp += $idxs $idxb++ $idxs = 0 $mis.Add((New-Object 'System.Collections.Generic.List[System.Windows.Forms.MenuItem]')) $put = $false } '<' { $idxb--; $idxs = $idxsp[$idxb] $mis[$idxb][$idxs - 1].MenuItems.AddRange($mis[$idxb + 1].ToArray()) $put = $false } default { $it[0] = $it[0].Substring(1); } } } } if($put) { $mis[$idxb].Add((New-Object System.Windows.Forms.MenuItem $it[0],$onClick)) if($it.Count -eq 1) { $mis[$idxb][$idxs].Name = $it[0] } else { $mis[$idxb][$idxs].Name = $it[1] } $idxs++ } } $context.MenuItems.AddRange($mis[0].ToArray()) if($x -eq [int]::MinValue) { $x = [System.Windows.Forms.Cursor]::Position.X } if($y -eq [int]::MinValue) { $y = [System.Windows.Forms.Cursor]::Position.Y } $form.Location = New-Object System.Drawing.Point $x,$y $timer = New-Object System.Windows.Forms.Timer $timer.Interval = 10; $timer.Add_Tick({ $timer.Enabled = $false if($form.FormBorderStyle -eq 'None') { $context.Show($form, [System.Drawing.Point]::Empty) $form.FormBorderStyle = 'Sizable' $timer.Enabled = $true } elseif($form.FormBorderStyle -eq 'Sizable') { $form.Close() } }) $timer.Enabled = $true [void]$form.ShowDialog() $ret = $form.Name $context.Dispose() $form.Dispose() return $ret }
使い方
基本的な使い方は
Show-PopupMenu 'a,b,c'
といった感じ。
選択すると、選択したものが、キャンセルは空文字。
出す場所を指定する場合は
Show-PopupMenu 'a,b,c' -x 100 -y 100
省略した場合は、ポインター位置です。
階層を構成したい場合は、「\>」と「\<」を使う。
Show-PopupMenu 'a,\>,aa,ab,\>,aba,abb,\<,ac,\<,b,c'
なお、アクセッサーを指定したくて、表示と戻りを変えたい場合は「^」を使う
Show-PopupMenu '&a^a,\>,a&a^aa,a&b^ab,\>,ab&a^aba,ab&b^abb,\<,a&c^ac,\<,&b^b,&c^c'
選択肢に「,」を使いたい場合、分割する文字列を変更する。
Show-PopupMenu 'a/,/c' -Splitter '/'
同様に選択肢に「^」を使いたい場合、分割する文字列を変更する。
Show-PopupMenu "あ`t(&a)/a,&b^b,&c/c" -Separator '/'
選択肢の先頭に「\」を使いたい場合、「\\」にする。(先頭以外は「\」で良い)
Show-PopupMenu '\\a,\\b\,\\>'
選択肢は配列でも可能
Show-PopupMenu 'a','\>','aa','ab','\>','aba','abb','\<','ac','\<','b','c'
のうがき
タイマー使って、妙なこすいことしてます。
ContextMenuのShowメソッドは、メニューが閉じると戻ってくるのですが、戻って即Disposeしちゃうと、MenuItemのClickイベントが処理されないのです。
このため、Clickイベントを処理する間にメッセージポンプが回るよう、タイマーを使ってFormをモーダルダイアログにしています。
これで何するかって?
タブ補完をもう少し便利にできるんじゃないかなー、と妄想しているとこ。