PowerShellでWPFを使うとき、変更通知つきのプロパティにBindingする方法です。
結論
ラップしたC#を書くのがオススメ
難点1:そもそもPowerShellでプロパティが書けない
正確に言うと、普通の書き方ではgetter/setterを書けません。
以下のようにいわゆるフィールドのように書くと、それはプロパティ扱いになりますが、
肝心の処理はかけず、自動プロパティのようになります。
class ViewModel{
[String] $SelectedItem
}
もちろん変更通知を書けないので、これは使えません
難点2:無理やり付け加えると、Bindingできない
Add-MemberとScriptPropertyを組み合わせて使うと、getter/setterつきのプロパティを書くことができますが、Add-Memberで追加したプロパティにはBindingできません。
using namespace System.ComponentModel
using namespace System.Collections.ObjectModel
class ViewModel: INotifyPropertyChanged
{
hidden [string]$m_slectedItem
[System.Collections.ArrayList]$PropertyChanged = @()
[void] add_PropertyChanged([PropertyChangedEventHandler]$handler)
{
$this.PropertyChanged.Add($handler)
}
[void] remove_PropertyChanged([PropertyChangedEventHandler]$handler)
{
$this.PropertyChanged.Remove($handler)
}
[void] OnPropertyChanged([string]$propname)
{
if ($this.PropertyChanged)
{
$evargs = [PropertyChangedEventArgs]::new($propname)
$this.PropertyChanged.Invoke($this, $evargs)
}
}
[ObservableCollection[string]] $Items = [ObservableCollection[String]]::new()
MainViewModel()
{
#SelectedItemにBindingしても無反応
$vm = $this
$this | Add-Member -Name "SelectedItem" -MemberType ScriptProperty `
-Value {
return $vm.m_slectedItem
}.GetNewClosure() `
-SecondValue {
param($value)
$vm.m_slectedItem = $value
$vm.OnPropertyChanged("SelectedItem")
}.GetNewClosure()
$this.Items.Add("Apple")
$this.Items.Add("Banana")
$this.Items.Add("Cherry")
}
}
仕方ないのでC#書く
どうしようもないので、C#で変更通知を実装した、なんちゃってReactivePropertyを書きます。
PowerShellで読み込めるC#のバージョンは古いので、
get =>_value とか nameof(Value) とか PropertyChanged?.Invoke とか使えないので要注意。
using System;
using System.ComponentModel;
using System.Windows.Input;
public class ReactiveProperty<T> : INotifyPropertyChanged
{
private T _value;
public T Value
{
get { return _value; }
set {
if (!Equals(_value, value)){
_value = value;
OnPropertyChanged("Value");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null){
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
一応、これをモジュールで読み込みます
Split-Path $MyInvocation.MyCommand.Path -Parent | Set-Location
Add-Type -Path ".\ReactiveProperty.cs"
これをusing moduleすれば使えます。