Bakulog

獏の夢日記的な何か。

【WPF】ビルド済みのWPFアプリにXAMLアニメーションを動的追加【C#/IronPython】

ほぼデスクトップマスコット専用のTips紹介です。C#で作ったWPFアプリケーションに、組み込み言語としてIronPythonを用いることでアニメーションを後付けします。

本記事は旧ブログであるニコ動ブロマガの記事の再掲です。元記事はコイツ。

【C#/WPF】アニメーションの動的ロード【XAML/IronPython】

上記の記事ではソースコード表示があまりに汚かったのでキレイに表示し直すため再掲しています。本記事の内容を実際に使った動画はコチラ。ゆっくりWindowWalker(YWW)の機能拡張で本記事の方法を実際にやった例になっています。

 

WPFのアニメーション機能は本来GUIを分かりやすく見せるための機能ですが、デスクトップマスコットにとってはいわゆる「アニメ」的演出にも一役買ってくれます。WPF的には邪道ですが、こういう娯楽特化な所からWPFのアニメーション機構に興味を持ってもらえると嬉しいなーと思っています。

 

 

では、前置きはこのくらいにして解説へ。

上の動画ではゆっくりがぴょんぴょん跳ねてますが、この動き、実は実行ファイルの外側で定義しています。コンセプト的な手順は以下の4段階に分けられます。

  1. (XAML)フラグメントとしてStoryboardを実装、つまり実際のアニメーションの内容を記述
  2.  (C#)IronPythonインタプリタを起動し、Windowなどスクリプトから触りたい変数を登録
  3.  (C#)実際にIronPythonスクリプトを読み込む
  4.  (IronPython)XamlServices.Load関数でXAMLをStoryboardとして読み出し、添付プロパティを設定してアニメ開始

こうすることで、C#側をアニメーションの実装と分離できます。ただしこの方法はWindow変数を公開してるので色々とガバガバになりますから、アプリケーションによっては公開変数に注意しましょう。

 

実際の実装もやってみましょう。まずWPFアプリケーションとして"JumpingWindow"というプロジェクトを作成し、NuGetパッケージマネージャでIronPythonをインストールした上で"MainWindow.xaml.cs"を以下のように編集。今回の例では"MainWindow.xaml"は初期状態のままで構いません。

using System.Windows;

using IronPython.Hosting;
using Microsoft.Scripting.Hosting;

namespace JumpingWindow
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            var engineMain = Python.CreateEngine();
            //ビルトインスコープに追加するとスクリプト側が複数ファイルで構成されていてもwindowが参照可能
            ScriptScope builtin = engineMain.GetBuiltinModule();
            builtin.SetVariable("window", this);

            engineMain.ExecuteFile("jumping.py");
        }

    }
}

 

上記の編集に加えて、C#実行ファイルのパス("bin\Debug\"以下)へ次のスクリプトをを"jumping.py"という名前で保存し、配置します。ファイルの最初にも書いてますが保存時のエンコードUTF-8を指定して下さい。

 

# -*- encoding:utf-8 -*-

#ぴょんぴょんアニメーションの定義

import clr
clr.AddReference("PresentationFramework")
clr.AddReference("System.Xaml")

from System.IO import StringReader
from System.Xaml import XamlServices
from System.Windows import PropertyPath
from System.Windows.Media.Animation import Storyboard

xamlStr = """
<Storyboard xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
RepeatBehavior="Forever">
    <DoubleAnimationUsingKeyFrames>
        <DiscreteDoubleKeyFrame KeyTime="0" Value="10"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1" Value="300">
            <EasingDoubleKeyFrame.EasingFunction>
                <QuadraticEase EasingMode="EaseIn"/>
            </EasingDoubleKeyFrame.EasingFunction>
        </EasingDoubleKeyFrame>
        <EasingDoubleKeyFrame KeyTime="0:0:1.25" Value="450">
            <EasingDoubleKeyFrame.EasingFunction>
                <QuadraticEase EasingMode="EaseOut"/>
            </EasingDoubleKeyFrame.EasingFunction>
        </EasingDoubleKeyFrame>
        <EasingDoubleKeyFrame KeyTime="0:0:1.5" Value="300">
            <EasingDoubleKeyFrame.EasingFunction>
                <QuadraticEase EasingMode="EaseIn"/>
            </EasingDoubleKeyFrame.EasingFunction>
        </EasingDoubleKeyFrame>
        <EasingDoubleKeyFrame KeyTime="0:0:2.5" Value="10">
            <EasingDoubleKeyFrame.EasingFunction>
                <QuadraticEase EasingMode="EaseOut"/>
            </EasingDoubleKeyFrame.EasingFunction>
        </EasingDoubleKeyFrame>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames>
        <DiscreteDoubleKeyFrame KeyTime="0" Value="300"/>
        <DiscreteDoubleKeyFrame KeyTime="0:0:1" Value="300"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.25" Value="150">
            <EasingDoubleKeyFrame.EasingFunction>
                <QuadraticEase EasingMode="EaseOut"/>
            </EasingDoubleKeyFrame.EasingFunction>
        </EasingDoubleKeyFrame>
        <EasingDoubleKeyFrame KeyTime="0:0:1.5" Value="200">
            <EasingDoubleKeyFrame.EasingFunction>
                <QuadraticEase EasingMode="EaseIn"/>
            </EasingDoubleKeyFrame.EasingFunction>
        </EasingDoubleKeyFrame>
        <EasingDoubleKeyFrame KeyTime="0:0:2.5" Value="300">
            <EasingDoubleKeyFrame.EasingFunction>
                <ElasticEase EasingMode="EaseOut" Springiness="3" Oscillations="5"/>
            </EasingDoubleKeyFrame.EasingFunction>
        </EasingDoubleKeyFrame>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>
"""

with StringReader(xamlStr) as sr
    sb = XamlServices.Load(sr)

    Storyboard.SetTarget(sb.Children[0], window)
    Storyboard.SetTarget(sb.Children[1], window)
    Storyboard.SetTargetProperty(sb.Children[0], PropertyPath("Top"))
    Storyboard.SetTargetProperty(sb.Children[1], PropertyPath("Height"))

    sb.Begin()

 

以上で準備完了です!これで実行するとただのウィンドウが跳ねます。ちょうど動画の方で紹介してる例ですね。今回の例ではXAMLPythonコード内に文字列リテラルとしてドーンと置いてますが、もちろんxamlファイルとして外部に置いたものを読み出すことも出来ます。詳細はXamlServices.Load関数のドキュメンテーションをご覧ください。

 

内容としては以上ですが、ここで特筆すべきことを2点だけ。

一つ目に、IronPythonじゃなくてもいいですが組み込み言語を用意するとユーザ側の機能拡張がとても自由になります。セキュリティ上の都合から出来ない場合以外では、IronPythonとかjavascriptなど、整備の行き届いたスクリプト言語のエンジンを使うとユーザにとって気軽に拡張しやすい環境が作れますし、製作者としてもラクです。もし拡張可能なプログラムをC#のようなコンパイラ型言語で作りたい場合は覚えといて損はないでしょう。

二つ目に、XAMLは動的読み込み可能なので、外部ファイルでもXAMLを使いたい時はどんどん使いましょう。上の"jumping.py"でもXAMLでアニメーションを記述してますが、これはIronPythonで実装するよりだいぶ簡単(なハズ)です。基本的にビュー周りの実装はXAMLの方が簡単に書けるので、適宜使い分けてエレガントな機能拡張を心がけましょう。

 

もし「こういう事したいんだけど…」みたいな質問があればコメントとかでどうぞ。