PowerShellでGUI: 部品配置はGridとテキスト編集が良い
2つ前の記事「PowerShellでGUI: 可変個数RadioButton実現方法(別法) - Itsukaraの日記」で、PowerShellのGUIをVisual Studioで作成する方法を書きました。ただ、GUIを実現するWPFの勉強が不足していたので、GUI画面上にGUI部品を適当に貼り付けていました。そのため、部品の位置等を表す数字「Margin="68,119,0,0"」が沢山あり、後で調整するのが大変そうと感じました。
その後、WPFを少し調べたところ、GRIDを使うことにより部品をテーブル状に整然と並べることが出来ると分かりました。下記のような感じです。
ただ、GRIDへのGUI部品配置をGUIでやると、微調整が結構面倒なので、1~2個部品を並べたら、あとは、XAMLテキストをコピー・ペーストし適切な修正を行うのが良いです。
また、GUIで部品を配置すると、HeightやMarginなどに、色々な数字が入りますが、これらが部品ごとに入っていると、後で纏めて変えるのが面倒なので、それらの数字はできるだけ削っておいた方が良いです。順序としては、上記のコピー・ペーストの前にやっておく必要がありますね。
なお、Syleを使うと、後でTextBoxのHeightを纏めて30にしたりといったことが、簡単にできます。Styleは、<Window.Resources>というタグ内に記述します。
結果として、次のようなプログラムになりました。表示内容は、依然とあまり変わりませんが、無駄な数字がなくなって、すっきりしています。その分、後で変更するのが楽(なはず)です。
- プログラム例-
$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"> <Window.Resources> <Style TargetType="TextBox"> <Setter Property="Height" Value="30"/> </Style> <Style TargetType="Button"> <Setter Property="Height" Value="30"/> </Style> <Style TargetType="TextBlock"> <Setter Property="Foreground" Value="Blue"/> </Style> </Window.Resources> <Grid Margin="10,10,10,10"> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition Height="30"/> <RowDefinition Height="30"/> <RowDefinition Height="30"/> <RowDefinition Height="30"/> <RowDefinition Height="10"/> <RowDefinition Height="30"/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="50"/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Label x:Name="label01" Content="名前" Margin="0" VerticalAlignment="Center" HorizontalAlignment="Right"/> <TextBox x:Name="textName" Margin="0" TextWrapping="Wrap" VerticalAlignment="Center" Grid.Column="1"/> <Label x:Name="label02" Content="ふりがな" Margin="0" VerticalAlignment="Center" HorizontalAlignment="Right" Grid.Row="1"/> <TextBox x:Name="textFurigana" Margin="0" TextWrapping="Wrap" VerticalAlignment="Center" Grid.Column="1" Grid.Row="1" /> <Label x:Name="label03" Content="性別" Margin="0" VerticalAlignment="Top" HorizontalAlignment="Right" Grid.Row="2"/> <StackPanel x:Name="stackGender" HorizontalAlignment="Left" Margin="0" VerticalAlignment="Center" Orientation="Horizontal" Grid.Column="1" Grid.Row="2" Width="120"> <RadioButton x:Name="radioButtonMale" Content="Male" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,10,0"/> <RadioButton x:Name="radioButtonFemale" Content="Female" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0"/> </StackPanel> <Label x:Name="label04" Content="国籍" Margin="0" VerticalAlignment="Center" HorizontalAlignment="Right" Grid.Row="3"/> <ComboBox x:Name="comboBoxCountry" HorizontalAlignment="Left" Margin="0" VerticalAlignment="Center" Grid.Row="3" Grid.Column="1" Width="100"/> <Label x:Name="label05" Content="年齢" Margin="0" VerticalAlignment="Center" HorizontalAlignment="Right" Grid.Row="4"/> <TextBox x:Name="textAge" Margin="0" TextWrapping="Wrap" VerticalAlignment="Center" Grid.Column="1" Grid.Row="4" Width="50" HorizontalAlignment="Left" /> <Button x:Name="buttonRegist" Content="登録" Margin="0" VerticalAlignment="Center" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="6" /> <TextBlock x:Name="textBlock" HorizontalAlignment="Left" Margin="0" TextWrapping="Wrap" VerticalAlignment="Top" Grid.ColumnSpan="2" Grid.Row="7"/> </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()
- 画面表示例
PowerShellでGUI: 可変個数RadioButton実現方法(別法)
「VisualStudioでPowerShell用GUIを作成する方法 - Itsukaraの日記」という記事で、PowerShellのGUI作成用にVisualStudioを使う方法を紹介し、可変個数のRadioButtonを表示する方法も書きました。しかし、可変個数のRadioButtonは、もっと良い実現方法を考えたので、紹介します。
とても簡単で、GUIの構造を表すXAML文字列を書き換えるだけです。下記にプログラム例を示します。下記の「add_items_to_xaml」という関数が、XAMLを書き換える処理です。汎用性があるので、RadioButton以外でも使えると思います。
- 可変個数のRadioButtonを作る例:
$ErrorActionPreference = "stop" Set-PSDebug -Strict Add-Type -AssemblyName PresentationFramework $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"> </StackPanel> <Label Content=" " FontSize="10pt"/> <StackPanel x:Name="fruit"> </StackPanel> </StackPanel> <Label x:Name="msg" Content=" " FontSize="10pt"/> <Button x:Name="btn" Content="Push Me" FontSize="10pt" /> </StackPanel> </Window> '@ # 項目一覧を$xamlに追加 # $head : 置換前文字列 # $add : 置換後文字列のひな型 # $placeholder: $add中のプレスホルダー文字列 # $items: 項目一覧(空白で区切られたワード列) function add_items_to_xaml($head, $add, $placeholder, $items) { $list = $items.split(" ") [array]::Reverse($list) foreach ($item in $list) { $add_item = $add.Replace($placeholder, $item) $global:xaml = $global:xaml.Replace($head, $add_item) } } # countryへの項目追加 $country_head = @' <StackPanel x:Name="country"> '@ $country_add = @' <StackPanel x:Name="country"> <RadioButton Content="PlaceHolder"></RadioButton> '@ $countries = "U.S.A China Japan Germany France England Russia" add_items_to_xaml $country_head $country_add "PlaceHolder" $countries # fruitへの項目追加 $fruit_head = @' <StackPanel x:Name="fruit"> '@ $fruit_add = @' <StackPanel x:Name="fruit"> <RadioButton Content="PlaceHolder"></RadioButton> '@ $fruits = "Apple Orange Grape" add_items_to_xaml $fruit_head $fruit_add "PlaceHolder" $fruits $xaml $xaml = [xml]$xaml $reader = New-Object System.Xml.XmlNodeReader $xaml $frm = [System.Windows.Markup.XamlReader]::Load($reader) $country = $frm.FindName("country") $fruit = $frm.FindName("fruit") $msg = $frm.FindName("msg") # 選択された項目を返す function getChecked($parent) { $checked = "" foreach ($child in $parent.Children) { if ($child.isChecked) { $checked += $child.Content } } return $checked } # ボタンクリック時の処理 function clicked { $selectedCountry = getChecked $country $selectedfruit = getChecked $fruit $msg.content = "Country=" + $selectedCountry + "`n" + "Fruit=" + $selectedfruit } $btn = $frm.FindName("btn") $btn.Add_Click({clicked}) $result = $frm.ShowDialog()
- 書換後の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="U.S.A"></RadioButton> <RadioButton Content="China"></RadioButton> <RadioButton Content="Japan"></RadioButton> <RadioButton Content="Germany"></RadioButton> <RadioButton Content="France"></RadioButton> <RadioButton Content="England"></RadioButton> <RadioButton Content="Russia"></RadioButton> </StackPanel> <Label Content=" " FontSize="10pt"/> <StackPanel x:Name="fruit"> <RadioButton Content="Apple"></RadioButton> <RadioButton Content="Orange"></RadioButton> <RadioButton Content="Grape"></RadioButton> </StackPanel> </StackPanel> <Label x:Name="msg" Content=" " FontSize="10pt"/> <Button x:Name="btn" Content="Push Me" FontSize="10pt" /> </StackPanel> </Window>
- 画面表示例:
あとがき
今回の件に関して、Webで色々と見つけたのですが、よい方法が見つからず、結局、自分で考えました。でも、後から考えると当たり前の内容であり、Webで色々調べた労力が無駄でした。
VisualStudioでPowerShell用GUIを作成する方法
PowerShellでGUIを使う記事を下記2件書きましたが、PowerShellのプログラムを動かすまでGUIを確認できず不便と思っていました。もしかしたら、VisualStudioを使うと、画面設計ができなかと思ったら、簡単に出来たので報告します。
自動生成されたXAMLを使いPowerShellプログラム作成
- 例えば下記のようにGUI部品を配置。
- 画面下部のXAML(文字列)を抜き出してPowerShellのプログラムを作成。
- ただし、一部の文字列は削除が必要(下記の黄色枠部分)。
- 下記がプログラムの例。「@'」と「'@」で囲まれた部分が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()
- プログラム実行例。名前等を入力後、「登録」ボタンを押したところ。
あとがき
VisualStudioを使うと、PowerShellのGUIがとても簡単に設計できるので、ぜひ試してみてください。
おまけ
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()
- 実行例:
*1:それぞれの部品の意味は、別のサイト等で調べてください。
PowerShell+WPFが動かない場合の原因と対策
仕事の関係でPowerShellを使ったので、PowerShellでの勉強内容と作成プログラムを記事に書きましたが、本番で動かず、困ってしまいました。同様の問題で困っている方がいるかもしれないので、解決先を書きます。
問題点
- スクリプトがネットワークドライブ上にあるとエラーとなる。
- PowerShell ISEでは動作するが、PowerShellではエラーとなる。
原因と解決方法
- スクリプトがネットワークドライブ上にあるとエラーとなる。
- 原因:スクリプトがネットワークドライブ上にあると、リモートスクリプト(ネットワークから入手したスクリプト)とみなされエラーとなる。
- 対策:powershellにオプション付加「powershell -ExecutionPolicy Bypass」
- 別案:「powershell -file ファイル名」で起動(2/18追記)
- PowerShell ISEでは動作するが、PowerShellではエラーとなる。
- 原因:powershellの古いバージョン(1.0と2.0)のPowerShellのデフォルトはMTAモードになっている。なお、PowerShell ISEは、古いバージョン(1.0と2.0)でもSTAモードで動作している。
- 対策:powershellにオプション付加「powershell -sta」
まとめ
PowerShellは、スクリプトがネットワークドライブにあるか、どのバージョンか、PowerShellかPowerShell ISEか、によって動作が異なるので注意が必要。]
- 対策:「powershell -sta -ExecutionPolicy Bypass ファイル名」で起動。
- 別案:「powershell -sta -file ファイル名」で起動(2/18追記)。
PaintsChainerが爆速になっていた
http://itsukara.hateblo.jp/entry/2017/01/29/064926:embed:先週の記事で書いた線画着色(PaintsChanier)の件、久しぶりに最新版をダウンロードして試してみたら、当方の貧弱なGPU RAM(2GB)でも動くようになっており、サイズが500x500位の画像では3秒ぐらいで処理が完了。爆速になっていた。
線画着色最新版が妙に青みがかっている
線画着色の件、最新版(2017/1/31 21:39; 03739fd)を本家github(下記)からダウンロードして試したところ、残念ながら、特に速くなったり、機能が追加されている気はしません。また、残念ながら、色が妙に青みがかっています。
1/29ダウンロード版で生成した画像
1/31 21:39ダウンロード版で生成した画像
githubサイトを見ると、OpenCVのインストール方法として、結構面倒な方法へのリンク(下記)が書かれているので、それをやらないとだめなのかと思ったのですが、下記サイトに書かれたテスト用のデモを現在環境で実行したところ、下記サイトに表示される絵が、そのままの色で表示され、何の問題も発生しませんでした。ということは、下記の面倒はインストールをしなくても、"conda install opencv"で十分だったということです。Preferred Networkさん、どうなっているでしょう... (線画着色の件、githubにcommitしている人を見ると、Preferred Networkの人が多数関わっています。Preferred Networkが開発したChainerの宣伝にもなるので、Taizanだけでなく、Preferred Network総がかりで取り組んでいるのだと思います)
- チェック用プログラムを走らせ、上記サイトのウィンドウに重ねて表示したところ。
上記表示結果を基に、本家サイトのIssueで質問予定です。
ちなみに、以前のソースと比べたら、img2imgDataset.pyの旧版と新版で下記のように変わってますが、学習データは旧版のソースコードで学習させていると思いますので、「現在のソースコード」と「旧ソースコードで作成した学習データ」の間での不整合が原因な気がします。
旧版:image2 = cv2.cvtColor(image2, cv2.COLOR_BGR2YUV)
新版:image2 = cv2.cvtColor(image2, cv2.COLOR_RGB2YUV)
本件、githubのissueで議論中です。下記を参照ください。学習データはYUVなので、単に、画像の入出力がRGBかBGRかという問題とのこと。これは、@abbychauさんが解決しました(OpenCVのバージョンを見て自動的に判定できるらしいです)
github.com
線画着色で満員御礼!
当サイトはこれまで平均100PV/日程度のアクセス数だったのですが、昨日と本日は、各1日で1か月分のPVを頂きました。関心を持って頂き、まことにありがとうございます。とても励みになります。
ついでに、他の記事も見てくれると更にうれしいです。例えば、OpenAI GymのMontezuma's Revengeで1位になると共に、Deep Mindの論文に書かれていない部屋にも到達した話とか... これがきっかけでOpenAIの本社に招待され、12月にサンフランシスコに行ってきました(費用はOpenAI負担)。
itsukara.hateblo.jp
DeepMindの論文に書かれていない部屋に到達した件は、論文の著者Marc G. Bellemare氏にもお知らせ済で、素直に喜んでくれています(下記youtubeのKorekara Itsukaraのコメントと、Marc G. Bellemare氏のコメント参照)。
Playing Montezuma's Revenge with Intrinsic Motivation - YouTube
OpenAI Gymのスコアは下記参照。1位~3位が当方です。
gym.openai.com