ブログ「サイバー少年」

ブログ「サイバー少年」へようこそ!
小学六年生ごろからプログラミングを趣味にしている高校生のブログです。
勉強したことについての記事などを書いています。フリーソフトも制作、公開しています。
(当ブログについて詳しくは「ブログ概要紹介」を参照)

サイバー少年が作ったフリーソフトは「サイバー少年の作品展示場」へ

PowerShellで.NETのウィンドウを生成する

ずっとブログのトップ記事がギャラリーになっていて、もう鏡開きなのでそろそろ普通の記事を出さないといけないと思っていたのですが、

なにしろネタがありませんでして、更新するにも出来ない状態だったのですが、今日は例によって祝日ではありませんか。

祝日は更新しなければならないという謎の使命感が今宵もクソ記事を生み出します。


最近、PowerShellを触っていたわけでもないのですが、ネタをひねり出した結果、PowerShellの記事を書くことにしました。


PowerShellでは.NET Frameworkのライブラリをコールできるので、もちろんWindows FormsやWPFなどGUI用のライブラリも使用できます。

これを使って、PowerShellからウィンドウを生成して表示することができるわけでして、
GUIで使える使い捨てツールを作りたいときとかいいんじゃないかと思いましたので、その方法について簡単に紹介していきます。


まあ、GUIで使える使い捨てツールは、記事「シェルスクリプトとかExcelとか」で書いたように、Excelで作るのもなかなかよい選択なんじゃないかと思ったりもしますけどね。


それでも今回はPowershellと.NETで、
・Windows Forms
・WPF(コード)
・WPF(XAML)
の三編でお送りします。

おまけとしてイベントハンドラについても述べます。

まず、私もよく理解していない話なのですが、C#とかでGUIアプリケーションを作るときと同様に、PowerShellでもSTA(シングルスレッドアパートメント)でコードを実行しないとエラーになってしまうようです。

PowerShellのバージョン3.0からはデフォルトでSTAになっているんですが、それより前のバージョンはMTAがデフォルトなので、調整が必要です。

そのためには、色々方法は考えられると思いますが、簡単なのはPowerShellを起動するとき普通に起動するのではなくコマンドから起動して、-staオプションを付けるという方法です。

まずそこを確認しておかなければなりませんね。



Windows Forms

まず、System.Windows.Forms名前空間をインポートします。

そのためのコマンドとして、Add-Typeコマンドレットがあります。
このコマンドに-AssemblyNameを指定していきます。

Add-Type -AssemblyName System.Windows.Forms

これでOKです。
続いて、Formをインスタンス化して適当な変数に格納します。

$tekito = New-Object System.Windows.Forms.Form

これで早くもフォーム完成です。
空のフォームは寂しいので、Buttonを貼り付けます。

また、ButtonのLocationとSizeを設定するために、System.Drawingもインポートしておきます。

Add-Type -AssemblyName System.Drawing
$button = New-Object System.Windows.Forms.Button
$button.Location = New-Object System.Drawing.Point(10,10)
$button.Size = New-Object System.Drawing.Size(200,100)
$button.Text = 'こんにちは世界'
$tekito.Controls.Add($button)


これであとは表示するだけなのですが、普通にShowメソッドを呼ぶのでは上手いこといきません。

それはスレッドの問題で、Showメソッドの仕組みがどうなっているのか詳しくは知りませんが、
Showメソッドを呼ぶと、とりあえずフォームを出して、そのフォームの管理はメッセージループ用のスレッドに任せて、Showを呼んだスレッドは普通に次の処理に移ります。

ただ、PowerShellで同じことをやろうとすると、PowerShellではメッセージループ用のスレッドが配備されていないので、出てきたフォームはフリーズして、PowerShellは普通に処理を続けてしまいます。

そこでどうするかというと、フォームを自分のスレッドで管理するShowDialogメソッドを呼びます。
このときPowerShellのコマンドラインは閉じるまで待機になります。

$tekito.ShowDialog()

これを実行すると期待通りにフォームが表示されました。

ただ、ビジュアルスタイルが古いタイプのやつになっているのが気になります。
その設定は必要なのかもしれません。

なお、ShowDialogを呼ぶのではなく、ApplicationクラスのRunメソッドに渡すという方法もあって、
こちらもコマンドラインを待機させる感じなるのですが、このふたつをどのように使い分けるべきなのかは調べていません。



WPF(コード)

WPFのウィンドウを作成します。
こちらもWindows Formsの場合とほぼ同じです。

まずはWPFの本体といえるPresentationFrameworkをインポートします。

Add-Type -AssemblyName PresentationFramework

そしてWindowのインスタンスを作ります。

$window = New-Object System.Windows.Window

こちらもボタンを貼り付けましょう。
WPFではButtonは名前空間がWindowクラスと異なります。

WPFのButtonは位置とサイズの調整をしなくても、WindowのContentに設定すればウィンドウ一面がボタンになりますので楽ですね。

$button = New-Object System.Windows.Controls.Button
$button.Content = 'こんにちは世界'
$window.Content = $button


それで、ウィンドウの表示ですが、Windows Formsと同様で、ShowDialogを呼びます。

Appication.Runでもいいですが、WPFではまずApplicationクラスをインスタンス化するところから始めないといけないので面倒です。

$window.ShowDialog()

これでWPF版ウィンドウを表示できました。



WPF(XAML)

WPFなので、XAMLからWindow生成もできます。

コードからまともなGUIを設計するのはやりづらく、PowerShellだと特にどう書けばいいのかよく分からないことも頻発すると思うので、XAMLで書けるというのは強みですね。

なので現実的には、もしPowerShellでウィンドウを生成する機会があったらこの方法を取るのが良いと思います。

わざわざXAMLファイルを作ることなく、リテラルでささっと書いたXAMLを使えるので楽です。

まずはXAMLから書いてしまいましょう。

また、これも私はよく知らないのですが、C#とかで普通に作るときと同じく、XAMLの最初のほうで名前空間をあれこれしないといけません。

そこは、とりあえずコピペですね。

$xaml = @'
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml" >
<StackPanel>
<Button Content="おはよう世界" />
<Button Content="こんにちは世界" />
<Button Content="こんばんは世界" />
</StackPanel>
</Window>
'@

これでXAMLのテキストが完成したので、これをWindowクラスのオブジェクトにします。

そのために、System.Windows.Markup.XamlReaderクラスのParseメソッドを呼びます。
新しいアセンブリをインポートする必要はありません。

なお、Parseメソッドはテキストから作るためのメソッドであり、ストリームやファイルから作りたい場合はLoadメソッドを使えばいいです。

$window = [System.Windows.Markup.XamlReader]::Parse($xaml)

戻り値はObject型なので、本来キャストが必要ですが、PowerShellなのでテキトーに操作可能です。

これで、あとは表示するだけです。

$window.ShowDialog()

これでXAMLで記述したようなウィンドウが表示されました。
手間が少ない割に表現力が半端ないです。



さて、以上の方法でウィンドウを生成、表示することはできるわけですが、表示しただけではただの見世物でしかありません。

つまり実際には、コントロールのイベントハンドラに処理を書くことになりますね。

PowerShellにおいてイベントハンドラの設定は変な方法です。

Add_イベント名という名前のメソッドを呼び出して、引数にスクリプトブロックという匿名関数のようなものを渡します。

本当はそんな名前のメソッドは無いと思うのですが、PowerShellだとなぜかそのような方法をとっています。


ただ、PowerShellはコマンドレットが独自に発展していて、調べてみたらRegister-ObjectEventなどのコマンドがあるようで、これを使えばもう少し楽なのかもしれません。

今回は前者の方法でいきます。

先ほどのXAMLから生成するWPFのウィンドウで、$windowのLoadedイベントを設定します。

$window.Add_Loaded({
[System.Windows.MessageBox]::Show('ロードされました')
})


これで、ウィンドウを表示したときにメッセージボックスが出てきます。
まあ簡単ですね。

引数を受け取れないじゃないかと言われるかもしれませんが、それはスクリプトブロックが引数をどう受け取るのかという問題であり、説明は省きます。(調べていない)


また、ウィンドウではなくその子要素、たとえばButtonにイベントハンドラを設定したいときは、ちょいと面倒です。

まずXAMLのほうでButtonにNameプロパティを設定しておいて、親要素のFindNameメソッドでNameに基づく要素を取ってきます。

その要素に対してイベントハンドラを設定するという感じになると思います。(確認していない)


C#などのように、XAMLのほうからイベントハンドラ名を指定する方法があればいいんですが、PowerShellの場合XAMLとクラスを結びつけることができませんからね~。

コードのほうからイベントハンドラに設定しないといけないので、親子関係があると面倒です。



というわけで、調べ物とか確認作業もあまり行わず、殴り書きした超クソ記事でしたが、いかがでしたでしょうか。

役には立たないとしても、「なんかPowerShellすげえ!」みたいな印象を与えることができたら幸いです。

いや、それだとMicrosoftの回し者みたいですが…。


あと、そういえば地味に、プログラミングの記事、それも理論を扱うんじゃなくて具体的に何かを作る記事を書いたのって、めちゃめちゃ久しぶりなんじゃないかと思います。

tag: PowerShell プログラム GUI WindowsForms WPF XAML スレッド イベント

コメント

すげぇ

改めて思いました。
C#ってすげぇ!!

課題制作をするために
最近またC#を使ってるんですけど
すごく便利ですよね。
.NETの充実感やWindowsとの結びつき
その他諸々素敵だと再実感しました。

簡単かつ、かゆいところに手が届くような言語仕様もステキです。

そんなことを考えている私がいますw

  • 2016/01/26(火) 19:44:29 |
  • URL |
  • aridai #-
  • [ 編集 ]

Re: すげぇ

いや、これはC#はわりと関係ないですが…。
関係ないというのはすこし言いすぎですが、まあ関係ないですね。

この記事で取り扱ったのはPowershellというバッチみたいなシェルスクリプト言語です。
.NET Frameworkとの連携が強いのでほぼC#と同じことができます。

なので、C#がウィンドウを作れるように、Powershellでもウィンドウを作れるのです。

コメントの投稿

トラックバック

トラックバック URL
http://cyberboy6.blog.fc2.com/tb.php/440-d47774bc
この記事にトラックバックする(FC2ブログユーザー)

当ブログをご利用(閲覧等)になる場合は必ず「当ブログの利用規定」をお守りください。