PowerShell + WPF(XAML) を使った際に add_click
等を使わずにイベントを登録することはできないか以前から気になっていたが、調べた結果実現出来そうだったので調査・検証をしてその結果を本記事に記す。
本記事の対象ユーザ
- PowerShell + XAML を使っているがコード内で後からイベントを登録したくない人
起稿時の環境
- Windows 10 Pro(x64)
実装手法
以下の実装方法について紹介と検証を行う。
動作検証としてボタンのクリックによりメッセージボックスを表示する実装を行う。
- Window 作成後にイベントハンドラーを後から登録する方法
これが現在割りと一般的な手法(と思われる)。 - DLL を使ったイベントハンドラーの連携の紹介
事前に DLL を作成して、実行時にロードする手法。 - DLL を使用しないイベントハンドラーの連携
本記事で調査した手法。
Window 作成後にイベントハンドラーを後から登録する方法
一般的に利用される手法。
sample01\
+ ui01.xaml (1)
+ script01.ps1 (2)
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window" Height="450" Width="800">
<StackPanel>
<Button Content="Click Me" Name="buttonClickeMe" />
</StackPanel>
</Window>
System.Windows.Window
クラスでウィンドウを作成する。ウィンドウ内にはボタンを設置している。
using namespace System.Xml # XMLパーサ用
Add-Type -Assembly System.Windows.Forms # メッセージボックス用
Add-Type -AssemblyName PresentationFramework # WPF用
[xml]$xamlStr = Get-Content .\ui01.xaml
$nodeReader = (New-Object XmlNodeReader $xamlStr)
$window = [Windows.Markup.XamlReader]::Load($nodeReader)
$window.findName("buttonClickeMe").add_click({
[System.Windows.Forms.MessageBox]::Show("メッセージ表示", "Test")
})
$window.ShowDialog()
必要なモジュール類をロードして、XAML からウィンドウを作成している。
ウィンドウ内のボタンにイベントを登録しているシンプルな構成となっている。
コードはこちらのサイトを参考に作成(参考#1,2,3)。
実行結果
DLL を使ったイベントハンドラーの連携の紹介
DLL を使ったケースについては下記サイトを参照するのが良い。
- PowerShellでWPFとかイベントとか ~ 枠組みをC#で書く ~ - ブログ「サイバー少年」
http://cyberboy6.blog.fc2.com/blog-entry-445.html
DLL を使用しないイベントハンドラーの連携
本記事の主旨となる情報です。
sample02\
+ ui02.xaml (1)
+ script02.ps1 (2)
<WindowsOverview:Window1
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WindowsOverview="clr-namespace:WindowsOverview;assembly={dllname}"
>
<StackPanel>
<Button Click="Button_Click">Click me</Button>
</StackPanel>
</WindowsOverview:Window1>
<WindowsOverview:Window1
は C# コードの名前空間とクラス名を指定する。
xmlns:WindowsOverview="clr-namespace:WindowsOverview;assembly={dllname}"
では名前空間を明示し、assembly
では本来 DLL 名を指定するがここではスクリプト実行時にコンパイルするメモリ内に構築した静的アセンブリ名を指定する。{dllname}
はコンパイルするたびに基本的に名前が変わる為、スクリプト内で置換して XmlNodeReader を通す前に埋め込む。
using namespace System.Xml
Add-Type -ReferencedAssemblies System.Xaml,WindowsBase,PresentationCore,PresentationFramework @'
using System.Windows;
namespace WindowsOverview
{
public partial class Window1 : Window
{
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("ボタンがクリックされました。");
}
}
}
'@
$dllName = ([System.AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object { $_.DefinedTypes.Name -eq "Window1" }).getName().Name # 静的なアセンブラ名を取得する
[xml]$xamlStr = (Get-Content .\ui02.xaml) -replace ("{dllname}",$dllName)
$nodeReader = (New-Object XmlNodeReader $xamlStr)
$window = [Windows.Markup.XamlReader]::Load($nodeReader)
$window.ShowDialog()
Add-Type
で C# コードを追加する際に -ReferencedAssemblies で C# 内から利用するライブラリを追加しておく必要がある。
XAML が参照する静的ライブラリ名を取得して、XAMLReaderに読み込ませる前に予め置換して埋め込んで置く。今回 Class 名を Window1 としているため Where-Object でも Window1 でフィルタしているが、Class 名を変更する場合はここも変更する。
実行結果
想定した通りに動きました。ウィンドウタイトルを入れ忘れました。
あとがき
PowerShell で XAML を使った場合、ルートインスタンスとして <Window
を使う情報が見つかるためこれを変更できないと考えていましたが、「サイバー少年」(参考#4)さんのページで Window のサブクラス(別名)を指定する方法を見かけて今回の調査に至りました。その後、参考#5,6の情報を元に無事当初の目的が達成できました。
記事内容について、誤りや補足があればコメントでお知らせいただけると幸いです。
参考
記事内で参照しているもの、参照していないが参考とさせていただいたもの。
- PowerShellでWPFを使う - Qiita
https://qiita.com/potimarimo/items/1eca0516bd8c690872dc - XAML の概要 - WPF .NET | Microsoft Docs
https://docs.microsoft.com/ja-jp/dotnet/desktop/wpf/xaml/?view=netdesktop-6.0 - PowerShellをはじめよう ~PowerShell入門~: PowerShellでMessageBoxを使用する
https://letspowershell.blogspot.com/2015/06/powershellmessagebox.html - PowerShellでWPFとかイベントとか ~ 枠組みをC#で書く ~ - ブログ「サイバー少年」
http://cyberboy6.blog.fc2.com/blog-entry-445.html - WPF - XAML, with C# code behind : PowerShell
https://www.reddit.com/r/PowerShell/comments/9sd0ex/wpf_xaml_with_c_code_behind/ - c# - Dynamic DataTemplate in a PowerShell WPF app - Stack Overflow
https://stackoverflow.com/questions/52023418/dynamic-datatemplate-in-a-powershell-wpf-app/ - PowerShell でロード済みのアセンブリを確認する
http://www.vwnet.jp/windows/PowerShell/CheckAssemblis.htm