タイトルのとおりです。
コードビハインド等を使わずにスクリプトで記述することの良し悪しはさておき、
C# Script の機能を使ってみたかったのでやってみました。
出来上がりイメージ
先に出来上がりの使用方法を書いておくと以下のようになります。
Arg0 に MainWindow がバインドされ、 EventTrigger で Load イベント発生時に
if (Arg0.ActualWidth > Arg0.ActualHeight) ...
が実行されます。
※ただし、イベントの初回実行時にめちゃくちゃ時間がかかります。
<Window x:Class="ScriptOnWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:local="clr-namespace:ScriptOnWpf"
mc:Ignorable="d"
Name="mainWin"
Title="MainWindow" Height="350" Width="525">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<local:ScriptAction Arg0="{Binding ElementName=mainWin}">
<local:ScriptAction.Script>
if (Arg0.ActualWidth > Arg0.ActualHeight)
{
Arg0.Height = Arg0.ActualWidth;
}
else
{
Arg0.Width = Arg0.ActualHeight;
}
</local:ScriptAction.Script>
</local:ScriptAction>
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid />
</Window>
作成したコード
まずは NuGet で Microsoft.CodeAnalysis.CSharp.Scripting をインストールします。
そして、参照に System.Windows.Interactivity, Microsoft.Expression.Interactions を追加します。
そこに以下のような、 TriggerAction を作成しました。
public class ScriptAction : TriggerAction<DependencyObject>
{
private bool _scriptElementChanged;
private ScriptRunner<object> _runner;
private ScriptArguments _args;
#region Script
public string Script
{
get { return (string)GetValue(ScriptProperty); }
set { SetValue(ScriptProperty, value); }
}
public static readonly DependencyProperty ScriptProperty =
DependencyProperty.Register(
"Script",
typeof(string),
typeof(ScriptAction),
new FrameworkPropertyMetadata(
"",
(d, e) => ((ScriptAction)d).OnScriptChanged((string)e.OldValue, (string)e.NewValue)));
protected virtual void OnScriptChanged(string oldValue, string newValue)
{
_scriptElementChanged = true;
}
#endregion
#region ScriptOptions
public ScriptOptions ScriptOptions
{
get { return (ScriptOptions)GetValue(ScriptOptionsProperty); }
set { SetValue(ScriptOptionsProperty, value); }
}
public static readonly DependencyProperty ScriptOptionsProperty =
DependencyProperty.Register(
"ScriptOptions",
typeof(ScriptOptions),
typeof(ScriptAction),
new FrameworkPropertyMetadata(
null,
(d, e) => ((ScriptAction)d).OnScriptOptionsChanged((ScriptOptions)e.OldValue, (ScriptOptions)e.NewValue)));
protected virtual void OnScriptOptionsChanged(ScriptOptions oldValue, ScriptOptions newValue)
{
_scriptElementChanged = true;
}
#endregion
#region Arg0
public object Arg0
{
get { return GetValue(Arg0Property); }
set { SetValue(Arg0Property, value); }
}
public static readonly DependencyProperty Arg0Property =
DependencyProperty.Register(
"Arg0",
typeof(object),
typeof(ScriptAction),
new FrameworkPropertyMetadata(
null,
(d, e) => ((ScriptAction)d).OnArg0Changed(e.OldValue, e.NewValue)));
protected virtual void OnArg0Changed(object oldValue, object newValue)
{
if (oldValue?.GetType() != newValue?.GetType())
{
UpdateArguments();
}
else
{
_args?.SetArg(0, newValue);
}
}
#endregion
#region Arg1
public object Arg1
{
get { return GetValue(Arg1Property); }
set { SetValue(Arg1Property, value); }
}
public static readonly DependencyProperty Arg1Property =
DependencyProperty.Register(
"Arg1",
typeof(object),
typeof(ScriptAction),
new FrameworkPropertyMetadata(
null,
(d, e) => ((ScriptAction)d).OnArg1Changed(e.OldValue, e.NewValue)));
protected virtual void OnArg1Changed(object oldValue, object newValue)
{
if (oldValue?.GetType() != newValue?.GetType())
{
UpdateArguments();
}
else
{
_args?.SetArg(1, newValue);
}
}
#endregion
#region Arg2
public object Arg2
{
get { return GetValue(Arg2Property); }
set { SetValue(Arg2Property, value); }
}
public static readonly DependencyProperty Arg2Property =
DependencyProperty.Register(
"Arg2",
typeof(object),
typeof(ScriptAction),
new FrameworkPropertyMetadata(
null,
(d, e) => ((ScriptAction)d).OnArg2Changed(e.OldValue, e.NewValue)));
protected virtual void OnArg2Changed(object oldValue, object newValue)
{
if (oldValue?.GetType() != newValue?.GetType())
{
UpdateArguments();
}
else
{
_args?.SetArg(2, newValue);
}
}
#endregion
#region Arg3
public object Arg3
{
get { return GetValue(Arg3Property); }
set { SetValue(Arg3Property, value); }
}
public static readonly DependencyProperty Arg3Property =
DependencyProperty.Register(
"Arg3",
typeof(object),
typeof(ScriptAction),
new FrameworkPropertyMetadata(
null,
(d, e) => ((ScriptAction)d).OnArg3Changed(e.OldValue, e.NewValue)));
protected virtual void OnArg3Changed(object oldValue, object newValue)
{
if (oldValue?.GetType() != newValue?.GetType())
{
UpdateArguments();
}
else
{
_args?.SetArg(3, newValue);
}
}
#endregion
private void UpdateArguments()
{
// 各引数の型から動的に ScriptArguments<> オブジェクトを作り出す。
var argTypes = new Type[]
{
Arg0?.GetType() ?? typeof(object),
Arg1?.GetType() ?? typeof(object),
Arg2?.GetType() ?? typeof(object),
Arg3?.GetType() ?? typeof(object),
};
_args = typeof(ScriptArguments<,,,>)
.MakeGenericType(argTypes)
.GetConstructor(argTypes)
.Invoke(new object[] { Arg0, Arg1, Arg2, Arg3 }) as ScriptArguments;
_scriptElementChanged = true;
}
protected override async void Invoke(object parameter)
{
if (_scriptElementChanged)
{
// スクリプトをコンパイルしてデリゲートを作成する。
_runner = CSharpScript
.Create(Script, ScriptOptions, _args?.GetType())
.CreateDelegate(); ;
_scriptElementChanged = false;
}
// デリゲートを実行
await _runner?.Invoke(_args);
}
}
public abstract class ScriptArguments
{
public abstract void SetArg(int index, object value);
}
public class ScriptArguments<T0, T1, T2, T3> : ScriptArguments
{
public ScriptArguments(T0 arg0, T1 arg1, T2 arg2, T3 arg3)
{
Arg0 = arg0;
Arg1 = arg1;
Arg2 = arg2;
Arg3 = arg3;
}
// 4個も引数があれば足りるでしょ。
public T0 Arg0 { get; set; }
public T1 Arg1 { get; set; }
public T2 Arg2 { get; set; }
public T3 Arg3 { get; set; }
public override void SetArg(int index, object value)
{
switch (index)
{
case 0:
Arg0 = (T0)value;
break;
case 1:
Arg1 = (T1)value;
break;
case 2:
Arg2 = (T2)value;
break;
case 3:
Arg3 = (T3)value;
break;
default:
throw new ArgumentOutOfRangeException("index");
}
}
}
ご意見ありましたらよろしくお願いします。