14
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Xaml (WPF) の中に C# script を記述する

Last updated at Posted at 2016-06-05

タイトルのとおりです。
コードビハインド等を使わずにスクリプトで記述することの良し悪しはさておき、
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");
        }
    }
}

ご意見ありましたらよろしくお願いします。

14
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?