Itsukaraの日記

最新IT技術を勉強・実践中。最近はDeep Learningに注力。

VisualStudioでPowerShell用GUIを作成する方法

PowerShellGUIを使う記事を下記2件書きましたが、PowerShellのプログラムを動かすまでGUIを確認できず不便と思っていました。もしかしたら、VisualStudioを使うと、画面設計ができなかと思ったら、簡単に出来たので報告します。

VisualStudioを使ったGUI設計

  • VisualStudioを立ち上げる。
  • ファイル⇒新規作成⇒プロジェクトを選択。

f:id:Itsukara:20170322232630p:plain

  • WPFアプリケーションを選択。

f:id:Itsukara:20170322232635p:plain

  • 下記ようなGUI設計画面になるので、左のツールボックスから部品を選んで、中央のウィンドウにドラッグして配置しGUIを設計*1

f:id:Itsukara:20170322232640p:plain

自動生成されたXAMLを使いPowerShellプログラム作成

  • 例えば下記のようにGUI部品を配置。

f:id:Itsukara:20170322232649p:plain

  • 画面下部のXAML(文字列)を抜き出してPowerShellのプログラムを作成。
  • ただし、一部の文字列は削除が必要(下記の黄色枠部分)。

f:id:Itsukara:20170322235005p:plain

  • 下記がプログラムの例。「@'」と「'@」で囲まれた部分がXAML
$ErrorActionPreference = "stop"
Set-PSDebug -Strict
Add-Type -AssemblyName PresentationFramework

[xml]$xaml = @'
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Label x:Name="label01" Content="名前" HorizontalAlignment="Left" Margin="15,26,0,0" VerticalAlignment="Top"/>
        <TextBox x:Name="textName" HorizontalAlignment="Left" Height="26" Margin="68,26,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="439"/>
        <Label x:Name="label02" Content="ふりがな" HorizontalAlignment="Left" Margin="15,57,0,0" VerticalAlignment="Top"/>
        <TextBox x:Name="textFurigana" HorizontalAlignment="Left" Height="26" Margin="68,57,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="439"/>
        <Label x:Name="label03" Content="性別" HorizontalAlignment="Left" Margin="15,88,0,0" VerticalAlignment="Top"/>
        <StackPanel x:Name="stackGender" HorizontalAlignment="Left" Height="26" Margin="68,88,0,0" VerticalAlignment="Top" Width="129" Orientation="Horizontal">
            <RadioButton x:Name="radioButtonMale" Content="Male" Height="26" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="5,5,5,0"/>
            <RadioButton x:Name="radioButtonFemale" Content="Female" Height="26" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="5,5,5,0"/>
        </StackPanel>
        <Label x:Name="label04" Content="国籍" HorizontalAlignment="Left" Margin="15,119,0,0" VerticalAlignment="Top"/>
        <Label x:Name="label05" Content="年齢" HorizontalAlignment="Left" Margin="15,150,0,0" VerticalAlignment="Top"/>
        <TextBox x:Name="textAge" HorizontalAlignment="Left" Height="26" Margin="68,150,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="52"/>
        <ComboBox x:Name="comboBoxCountry" HorizontalAlignment="Left" Height="26" Margin="68,119,0,0" VerticalAlignment="Top" Width="439"/>
        <Button x:Name="buttonRegist" Content="登録" HorizontalAlignment="Left" Height="33" Margin="242,276,0,0" VerticalAlignment="Top" Width="48"/>
        <TextBlock x:Name="textBlock" HorizontalAlignment="Left" Height="90" Margin="15,181,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="492"/>

    </Grid>
</Window>
'@

$reader = New-Object System.Xml.XmlNodeReader $xaml
$frm = [System.Windows.Markup.XamlReader]::Load($reader)

$countries = "U.S.A China Japan Germany France England Russia".split(" ")
$comboBoxCountry = $frm.FindName("comboBoxCountry")
foreach ($country in $countries) {
  [void]$comboBoxCountry.Items.Add($country)
}

$textName = $frm.FindName("textName")
$comboBoxCountry = $frm.FindName("comboBoxCountry")
$textFurigana = $frm.FindName("textFurigana")
$stackGender = $frm.FindName("stackGender")
$textAge = $frm.FindName("textAge")
$textBlock = $frm.FindName("textBlock")

function getChecked($parent) {
    $checked = ""
    foreach ($child in $parent.Children) {
        if ($child.isChecked) {
            $checked += $child.Content
        }
    }
    return $checked
}

function regist {
    $name = $textName.Text
    $furigata = $textFurigana.Text
    $gender = getChecked $stackGender
    $country = $comboBoxCountry.SelectedValue
    $age = $textAge.Text

    $msg = ""
    $msg += "名前:" + $name + "`n" 
    $msg += "ふりがな:" + $furigata + "`n" 
    $msg += "性別:" + $gender + "`n" 
    $msg += "国籍:" + $country + "`n" 
    $msg += "年齢:" + $age + "`n" 

    $textBlock.Text = $msg
}

$buttonRegist =  $frm.FindName("buttonRegist")
$buttonRegist.Add_Click({regist})

$result = $frm.ShowDialog()
  • プログラム実行例。名前等を入力後、「登録」ボタンを押したところ。

f:id:Itsukara:20170322232657p:plain

あとがき

VisualStudioを使うと、PowerShellGUIがとても簡単に設計できるので、ぜひ試してみてください。

おまけ

ListBoxやComboBoxは、PowerShellから簡単に項目を追加できるが、RadioButtonは簡単に追加できない。これはとても不便だが、次のようにプレースホルダを沢山書いておき、必要なものだけ表示するようにPowerShellで制御すれば、ある程度対応できることが分かった。ご参考まで。

  • プログラム例:
$ErrorActionPreference = "stop"
Set-PSDebug -Strict
Add-Type -AssemblyName PresentationFramework

[xml]$xaml = @'
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="xaml test #2"
    Width="170pt" Height="170pt">
  <StackPanel>
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
      <StackPanel x:Name="country">
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
      </StackPanel>
      <Label Content="  " FontSize="10pt"/>
      <StackPanel x:Name="fruits">
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
        <RadioButton Content="PlaceHolder"></RadioButton>
      </StackPanel>
    </StackPanel>
    <Label x:Name="msg"  Content="  " FontSize="10pt"/>
    <Button x:Name="btn" Content="Push Me"  FontSize="10pt" />
  </StackPanel>
</Window>
'@

$reader = New-Object System.Xml.XmlNodeReader $xaml
$frm = [System.Windows.Markup.XamlReader]::Load($reader)
$country = $frm.FindName("country")
$fruits = $frm.FindName("fruits")
$msg = $frm.FindName("msg")

# 全てのRadioButtonを非表示にする
foreach ($s in $country.Children) {
    $s.Visibility = "Collapsed"
}

# 全てのRadioButtonを非表示にする
foreach ($b in $fruits.Children) {
    $b.Visibility = "Collapsed"
}

# RadioButtonのContentを設定し、表示する
$countryList = "U.S.A China Japan Germany France England Russia".split(" ")
$n = 0
$children = $country.Children
foreach ($c in $countryList) {
    $radioButton = $children[$n]
    $radioButton.Content = $c
    $radioButton.Visibility = "Visible"
    $n += 1
}

# RadioButtonのContentを設定し、表示する
$fruitsList = "Apple Orange Grape".split(" ")
$n = 0
$children = $fruits.Children
foreach ($f in $fruitsList) {
    $radioButton = $children[$n]
    $radioButton.Content = $f
    $radioButton.Visibility = "Visible"
    $n += 1
}

# 選択された項目を返す
function getChecked($parent) {
    $checked = ""
    foreach ($child in $parent.Children) {
        if ($child.isChecked) {
            $checked += $child.Content
        }
    }
    return $checked
}

function clicked {
    $selectedCountry = getChecked $country
    $selectedFruits  = getChecked $fruits
    $msg.content = "Country=" + $selectedCountry + "`n" + "Fruits=" +  $selectedFruits
}

$btn = $frm.FindName("btn")
$btn.Add_Click({clicked})
$result = $frm.ShowDialog()
  • 実行例:

f:id:Itsukara:20170323002422p:plain

*1:それぞれの部品の意味は、別のサイト等で調べてください。