読者です 読者をやめる 読者になる 読者になる

grunt-typescript の watch オプションでさらに速く

TypeScript
この内容は grunt-typescript 0.3.4 (typescript 1.0.0) をもとに記述しています。

ちょっとまえに grunt-typescript 0.3.2 を公開して、「TypeScriptのコンパイルは遅いけど、それでもgrunt-typescriptが最速じゃないだろうか?」ってのを書いたんやけど、思うところあってちょっと触ったらさらに早くなった。バージョンが 0.3.2 から 0.3.4 と 0.3.3 が飛んでるのは、デバック用の console 出力が残っていたっていうご愛嬌。

で、なにをしたかっていうと、コンパイルを実際に実行する TypeScriptCompiler ってのが中にあってそいつのインスタンスを毎回生成してたんやけど、それを使いまわすようにした。で、 TypeScriptCompiler には addFile っていうコンパイル対象になるファイルを追加するメソッドがあるんやけど、ちょっと調べたら updateFile ってのと getDocument ってメソッドがあったので、コンパイル実行前に getDocument メソッドすでにファイルが保持されているか調べて、保持されててかつ変更されたファイルやったら updateFile メソッドを使うように変更した。ファイルが保持されてなかったらもちろん addFile メソッドで追加。さらに内部ではファイルそれぞれに対して Document ってクラスのインスタンスが生成されて管理されてるんやけど、そこらへんのもろもろの処理が updateFile ですっ飛ばされる模様。

で、どうなったかは以下。

$ grunt typescript:watch
Running "typescript:watch" (typescript) task

Watching directory.... c:\Work\Git\typescript\src\compiler
>> Changed c:/Work/Git/typescript/src/compiler/tsc.ts
117 files created. js: 117 files, map: 0 files, declaration: 0 files (14493ms)

Watching directory.... c:\Work\Git\typescript\src\compiler
>> Changed c:/Work/Git/typescript/src/compiler/tsc.ts
1 file created. js: 1 file, map: 0 files, declaration: 0 files (4862ms)

Watching directory.... c:\Work\Git\typescript\src\compiler

前の記事では1回目は14036msだったのが2回目は8761msで約4.5秒ほど速くなっていたのが、今回は約10秒速くなってます。いい感じ。

updateFile は最後の引数に TextChangeRange クラスを渡せるようになってて、多分、変更された部分だけを指定できるようにもなってるようやけど、さすがに diff とかとるほうが遅いと思われる(というかそのコードを書くほうがしんどい)ので、却下。エディタでのライブコンパイルとかやったら使えそうです。

前後に動かすタスクも指定できるよ!

ちなみに watch オプションは、コンパイル前後に動かすタスクも指定できるようになってるんで、今まで grunt-contrib-watch などで一連の処理を流していたものも再現できるのいい感じです。

typescript: {
  base: {
    src: ['path/to/typescript/files/**/*.ts'],
    options: {
      watch: {
        before: ['beforetasks'],   //Set before tasks. eg: clean task
        after: ['aftertasks']      //Set after tasks.  eg: minify task
      }
    }
  }
}

TypeScriptのコンパイルは遅いけど、それでもgrunt-typescriptが最速じゃないだろうか?

TypeScript
この内容は grunt-typescript 0.3.2 (typescript 1.0.0) をもとに記述しています。

ついさっき grunt-typescript 0.3.2 をリリースしたわけですが、ちょっとコンパイルを早くしてみました。ってことで typescript 自体のコンパイルの速さを tsc と比較して見ました。

grunt-typescript は以下のように指定してます。

typescript:{
  base:{
    src: ["src/compiler/tsc.ts"]
  }
}


まずは tsc コマンド。

$ time tsc src/compiler/tsc.ts

real    0m15.641s
user    0m0.000s
sys     0m0.015s

で、じつは私の環境が WindowsVisual Studio 2013 の Update 2 RC が当たってる状態なので、この tsc コマンドは WSH で動いてて、node で動いてないです。なので、node で動くように ntsc ってコマンド作ってそちらでも試してみました。内部的には node コマンドで tsc.js を呼んでるだけです。

$ time ntsc src/compiler/tsc.ts

real    0m15.543s
user    0m0.030s
sys     0m0.030s

0.1秒くらい早そうですが、完全に誤差の範囲ですね。

最後に grunt-typescript 。

$ time grunt typescript:base
Running "typescript:base" (typescript) task
117 files created. js: 117 files, map: 0 files, declaration: 0 files (13875ms)

Done, without errors.

real    0m14.700s
user    0m0.015s
sys     0m0.060s

grunt-typescript のほうが 1 秒くらい早いです。

なぜ早くなっているか

grunt-typescript は基本的には tsc.ts のコードを参考に作ってて、少し Grunt 用の処理をいれて作ってます。なので tsc を直接実行するのにくらべて若干遅くなるくらいでした。で、今回(実際は0.3.0から)ちょっと内部処理に手を出して、速度アップをはかってみました。

tscコンパイルのステップとして各ファイルごとにざっくり書くと Syntax のチェック、 Semantic のチェック、 JS の生成、 d.ts の生成とやっているのですが、この Syntax のチェックと、Semantic のチェックですが、lib.d.ts についても実行しています。ですが、 lib.d.ts って typescript の組み込みみたいなもので、そもそも syntax や semantic にエラーがあったら駄目で、前提としてチェックしなくっても問題ないはずなので、 lib.d.ts を対象から外すようにしました。 lib.d.ts ファイルは結構大きなファイルなので、これを外すことで 1 秒くらい稼げました。

ちなみに、 grunt-typescript 0.3.1 で lib.d.ts に syntax チェックと semantic チェックをを含むようにしたら以下の実行速度でした。

$ time grunt typescript:base
Running "typescript:base" (typescript) task
117 files created. js: 117 files, map: 0 files, declaration: 0 files (15373ms)

Done, without errors.

real    0m16.190s
user    0m0.030s
sys     0m0.031s

今後 typescript の内部構造が変更されたら動かなくなる可能性もあるので少し怖いところもあるのですが、、、。
というか、 lib.d.ts を ES3 のみとか ES5 のみとか HTML DOM用とかの何種類かに分けてくれたらもっと嬉しいのですが、リクエストでも投げてみようかしら。

さらに早い watch オプション を使った2回目以降のコンパイル

さらに watch オプションでファイル監視しているときの2回目以降のコンパイルは、変更されたファイルのみ読み込みと書き出しをするのでさらに早いです。以下のような感じ。

$ grunt typescript:watch
Running "typescript:watch" (typescript) task

Watching directory.... c:\Work\Git\typescript\src\compiler
>> Changed c:/Work/Git/typescript/src/compiler/tsc.ts
117 files created. js: 117 files, map: 0 files, declaration: 0 files (14036ms)

Watching directory.... c:\Work\Git\typescript\src\compiler
>> Changed c:/Work/Git/typescript/src/compiler/tsc.ts
1 file created. js: 1 file, map: 0 files, declaration: 0 files (8761ms)

Watching directory.... c:\Work\Git\typescript\src\compiler

tsc.tsを試しに変更-保存して結果のところを確認すると、1回目は117ファイルが出力されていますが、2回目は1ファイルのみが出力されていますね。ファイルの変更日付を確認しているので複数ファイル更新されたら、そのファイル数分だけ対象にします。実行速度についても大体6秒ほど早くなってますね。これは対象となっていないファイルは読み込みと書き出しを実施していないのは書いた通りですが、それだけではなく、先に書いたコンパイルのステップの JS の生成も外しているからです。

ただし、単一ファイルで出力する tsc でいうところの --out オプションがついている状態だと lib.d.ts が外れている恩恵だけなのでここまでは速くならないのですが。まぁ、それでも tsc コマンドを叩くよりかはコンパイルが早くなるかと。


と、ここまで書いて Syntax のチェックと、 Semantic のチェックもいらないような気がしてきました。"対象になっていない = 先の結果と同じ" なはずなのでイケそうな気がします。ちょっと確認してみよう。。。

TypeScript で関数にプロパティをはやしたい(その2)

この内容は TypeScript 0.9.1.1 をもとに記述しています。

TypeScript 0.9.1.1 がでましたね。次はやっと1.0かな?

さてさて、前回 は Declaration Merging で関数にプロパティをはやしたのですが、やはりというか予想通りというか対応できないケースが出てきました。

で、頭をひねってみて Interface にキャストしてみました。

interface IGreeting {
  foo(): string;
}

function greeting(): string {
  return "hello";
}

var g:IGreeting = <IGreeting>greeting;

g.foo = function(): string {
  return "world";
}

予想通り怒られました。

error TS2012: Cannot convert '() => string' to 'IGreeting':

ってことで、無理やり Any にキャストしてからさらにインターフェイスにキャストすることに。

var g:IGreeting = <IGreeting><any>greeting;

g.foo = function(): string {
  return "world";
}

無事に?いけました。生成された JavaScript は以下。

interface IGreeting{
  foo(): string;
}

function greeting(): string{
  return "hello";
}

var g:IGreeting = <IGreeting><any>greeting;

g.foo = function(): string{
  return "world";
}

完全に TypeScript の意味なしですね。。。

TypeScript で関数にプロパティをはやしたい

TypeScript JavaScript
この内容は TypeScript 0.9.1 をもとに記述しています。

TypeScript の 0.9.1.1 のブランチが切られていて、もうそろそろ登場しそうな今日この頃です。

表題の件、 生 JavaScript なら結構やることなのですが、 TypeScript でやると怒られます。例えば以下のようなコードを書いたとして、

function greeting(): string{
    return "hello";
}

greeting.name = function(): string{
    return "k_maru";
}

コンパイルすると、以下のように怒られます。

error TS2094: The property 'name' does not exist on value of type '() => string'.

さて、以下のようなコードの書き方をさせたいとします。

//キャッシュを設定する
Lib.cache("user", {
   name: "hoge"
});

//キャッシュから取得
var user = Lib.cache("user");

//キャッシュの設定
Lib.cache.defaultDefinition({
    maxSize: 10,
    expire: 10000
});

Lib.cache 関数でキャッシュの値の設定と取得をさせてます。で、Lib.cache 関数からはえた defaultDefinition 関数でキャッシュの設定を行ってます。まぁ、こまかい突っ込みは置いといて、よくやるコードですよね。多分。
でもこれ、 TypeScript で普通に書くと以下のような感じになると思うのですが、最初の例の通りに怒られてしまいます。

module Lib{

  var items: {[index: string]: any;} = {};

  export function cache(name: string, value?: any): any{
    if(!value){
      if(name in items){
        return items[name];
      }
      return;
    }
      items[name] = value;
  }

  cache.defaultDefinition = function(def: any): void{
    //set definition
  }
}
The property 'defaultDefinition' does not exist on value of type '(name: string, value?: any) => any'.

まぁ、はやせないものは仕方がないのですが、 TypeScript 0.9 から Declaration Merging って機能で module を関数にすることができるようになってます。ってことで、早速つかってみます。

module Lib{

  var items:{[index: string]: any;} = {};

  export function cache(name:string, value?:any):any {
    if (!value) {
      if (name in items) {
        return items[name];
      }
      return;
    }
    items[name] = value;
  }

  export module cache{
    export function defaultDefinition(def:any):void {
      //set definition
    }
  }
}

これで無事にコンパイルが通って、思ったようなコードの書かせ方ができるようになりました。すべての場合にこれで対応できるかどうかはなぞっていうか難しい気がしますが、まぁ OK でしょう。以下のような JavaScript がはかれてます。

var Lib;
(function (Lib) {
  var items = {};

  function cache(name, value) {
    if (!value) {
      if (name in items) {
        return items[name];
      }
      return;
    }
    items[name] = value;
  }
  Lib.cache = cache;

  (function (cache) {
    function defaultDefinition(def) {
      //set definition
    }
    cache.defaultDefinition = defaultDefinition;
  })(Lib.cache || (Lib.cache = {}));
  var cache = Lib.cache;
})(Lib || (Lib = {}));

って、ここで終わったら万々歳なんですが、もうちょっと話を進めてみて、いまは cache 関数で設定されている生の値を返してますが、 生の値を保持した CacheItem クラスのインスタンスを返したいとします。これも結構よくやりますよね。多分。

以下のようなコードを書かせたいとします。

//キャッシュを設定すると、 CacheItem が返ってくる
var cacheItem = Lib.cache("user", {
  name: "hoge"
});
//キャッシュを取得すると、 CacheItem が返ってくる
cacheItem = Lib.cache("user");
//CacheItem には色々プロパティがあって、状態とか条件を確認したり変更したりできる
if(!cacheItem.expired){
  cacheItem.expirationDateTime = new Date(now + (1000 * 60 * 60));
}
//もちろん値も取れる
var user = cacheItem.value();

Lib.cache.defaultDefinition({
  maxSize: 10,
  expire: 10000
});

実装としては cache 関数は CacheItem クラスのインスタンスを返すんやけど、シグネチャとしてはインターフェイスである ICacheItem としたい。 CacheItem クラスは外から見えないようにして、 new させたくないってところです。 なぜって、 JavaScript で new するのってなんか気持ち悪いってだけですが。

さらに、 ICacheItem インターフェイスは Lib モジュールの下にいるんじゃなくって、 Lib.cache モジュールの下にいるようにします。 Lib.cache は関数であり同時に名前空間的なモジュールなので、そっちのほうがきれいです。

module Lib{

  var items:{[index: string]: cache.ICacheItem;} = {};

  // ICacheItem を返す
  export function cache(name:string, value?:any): cache.ICacheItem {
    if (!value) {
      if (name in items) {
        return items[name];
      }
      return;
    }
    // CacheItem を作る
    var item = new CacheItem(value);
    items[name] = item;
    return item;
  }

  export module cache{
    export function defaultDefinition(def:any):void {
      //set definition
    }
    export interface ICacheItem{
      //properties
    }
  }
  // cache モジュールの下に作ると、 cache 関数から見えるようにするためには
  // export しないとだめやけど、 export すると全体に見えてしまうから
  // Lib モジュールの下に作る
  class CacheItem implements cache.ICacheItem {
    constructor(public value: any){}
    //properties
  }
}

CacheItem クラスが Lib モジュールの下で、実装しているインターフェイスの ICacheItem が Lib モジュールの下の cache モジュールってので、上の階層が下の階層を見てるってので気持ち悪いですが、まぁ、仕方がないです。

そんなこんなで、TypeScript の時はインターフェイスの考え方をちょっと JavaScript とは変える必要があるかなぁと思ったり思わなかったり。TypeScript だけですべて書ききる、かつ他の JavaScript ライブラリも使わないのであれば、 new が気持ち悪いとかないと思うのですがどうしてもやっぱり混ざっちゃうのですよね。そうした場合にできる限り書かせ方は近いほうがよいと考えてて、合わせようとすると、 TypeScript 側に無理が出てしまうので、どっちに重きを置くかの違いなのですがなかなか悩ましい。


最後はだいぶタイトルから話がそれたような気はしますが、以上。

tsc で out オプションを付けても階層維持されてた。

TypeScript
この内容は TypeScript 0.9.0-1 をもとに記述しています。

いつのまにか、いつからか tsc で out オプションにディレクトリを指定しても、フラットに展開されずに、階層をいい感じに維持したまま出力してくれるようになってました。ということで、この間書いたのはウソでした。

ということで、実験。以下のような構成で ts ファイルがあったとします。

├ build
└ src
  └ core
  │ └ base.ts
  └ ui
    └ control.ts

で、以下のような tsc コマンドを蹴ったとします。

tsc src/**/*.ts --out build

とすると、こんな感じで出力されます。( src 以下は省略)

├ build
│ └ core
│ │ └ base.js
│ └ ui
│   └ control.js
└ src

しっかり、階層が維持されてます。で、もうちょっと実験。

├ build
└ src
│ └ core
│ │ └ base.ts
│ └ ui
│   └ control.ts
└ src2
  └ ext
    └ ext.ts

で、こう。

tsc src/**/*.ts src2/**/*.ts --out build

そしたら、こうなった。

├ build
│ ├ src
│ │ └ core
│ │ │ └ base.js
│ │ └ ui
│ │   └ control.js
│ └ src2
│   └ ext
│     └ ext.js
└ src
└ src2

どうやら、すべてのファイルから親フォルダをたどって同じフォルダの下のフォルダから構成される模様です。
一つ目と二つ目のオプションの違いって、src2/**/*.ts が増えただけなんですが、それで出力される構成が変わるってのはちょいと微妙な気もしないでもないですね。まぁ、でもこうなるのも分からんでもないし、じゃ、どうすればいいのよってもの思い浮かばんのですが。

最後に親ディレクトリを指してみた場合どうなるのか試してみました。

├ dir
│ ├ build
│ └ src
│ │ └ core
│ │ │ └ base.ts
│ │ └ ui
│ │   └ control.ts
│ └ src2
│   └ ext
│     └ ext.ts
└ src3
  └ other.ts

いままでと同様に以下のような tsc コマンドを投げてみます。ちなみにカレントディレクトリは dir です。

tsc src/**/*.ts src2/**/*.ts ../src3/*.ts --out build

予想通り以下のようになりました。

├ dir
│ ├ build
│ │ └ dir
│ │ │ ├ src
│ │ │ │ └ core
│ │ │ │ │ └ base.js
│ │ │ │ └ ui
│ │ │ │   └ control.js
│ │ │ └ src2
│ │ │   └ ext
│ │ │     └ ext.js
│ │ └ src3
│ │   └ other.js
│ └ src
│ └ src2
└ src3

Internet Explorer 11 の JavaScript の新機能

JavaScript
この内容は Windows 8.1 Preview および Internet Explorer 11 Preview をもとに記述しています。

Windows 8.1 Preview が発表されて、同時に Internet Explorer 11 の Preview も発表されました。新機能としては WebGL とか Developer Tools が結構取り上げられてますが、今回は置いといてブラウザに絡まないあくまでも JavaScript 部分を探ってみました。

結構、インパクトの大きいのが見つかったです。見つけたのは以下。

  • let 宣言
  • const 宣言
  • Set オブジェクト
  • Map オブジェクト
  • WeakMap オブジェクト

let/const 宣言、Set/Map/WeakMap オブジェクトの詳細は個別に別途アップしたいと思います。

いや、しかしこの新機能って結構コーディングにインパクトあると思うのですよ。今まで var 一本で頑張ってたところをより安全に確実にかけるようになったり、今まで Object で頑張ってたところを同じくより安全に確実にかけるようになるとおもうのです。今までバッドノウハウ的な、残念な機能をよりベターに使うため的なコーディング作法だったところを置き換えれそうな。

そうすると、例えばIE8以降はサポートの必須の場合の実装作法とか、IE11でかつほかのブラウザも最新だけのサポートでOKとか(まぁ、当分出てこないでしょうけど)って場合の実装作法とか、NodeJS の実装作法とか、とかとかとか。利用する実行環境のサポート状況によって使い分ける必要のある未来がなんとかなくちらついてます。今まではブラウザの差異で悩まされてたのが、JavaScript の実装状況の差異で悩まされる(まではいかなくてもきれいなコードがかけずイラッとする)ことも出てきそうですね。

JavaScript で作るストアアプリにもBehaviorが!

JavaScript Metro Windows 8
この内容は Windows 8.1 Preview および Visual Studio 2013 Preview (の Blend) をもとに記述しています。

Windows 8 Dveloper Preview が出てから、 WinRT アプリ for XAML がでてから、ずーっと Behavior が欲しくって、でも DependencyObjectCollection がなく DataContext の伝搬ができないので、まっとうなのが作れないので、諦めてたのですが、今回からやっとこさ DependencyObjectCollection が乗りましたね。ちょっと Generic じゃないのが気になりますが、良しとしましょう。

で、MSDN ながめても Behavior らしきものはなかったので、作ろうかと思ってるところに乗ってるよって噂を聞いたので探してみました。結論的には XAML のほうはまだ見つかってません。 Build のビデオをまだ見切ってないのと、 Blend をいじり倒してないのであるかどうかは不明ですが。

上記で "XAML のほう" はって書いたのにはわけがあって、っていうかもうすでにタイトルにつけているのですが、なんと JavaScript のほうには乗ってました。予想外です。

以下のように Blend の「アセット」ペインをみるとしっかりと表示されています。

f:id:k_maru:20130630004354p:plain

ということで、さっそく使ってみました。使い方は簡単で、いつものように画面にドラッグドロップするだけ。今回はとりあえずボタンを置いて ToggleClassAction を貼り付けてみました。たぶん、何かのイベント(予想ではデフォルトは click で後から変更可能) をもとに指定した class 属性の値を設定、解除できると思われます。で、出来上がった HTML は以下のような感じ。

<button type="button" 
    data-blend-behavior="BehaviorActionTree_1">button</button>

data-blend-bahavior 属性に BehaviorActionTree_1 ってのが設定されていますが、詳細はなにも書かれてません。あれーっと思いながらプロジェクトの中を眺めてると actionList.json ってファイルが追加されてます。

f:id:k_maru:20130630005125p:plain

で、この中を見るとありました。

[
  {
    "name": "BehaviorActionTree_1",
    "behaviors": [
      {
        "type": "Blend.Behaviors.EventTriggerBehavior",
        "event": "click",
        "triggeredActions": [
          {
            "type": "Blend.Actions.ToggleClassAction"
          }
        ]
      }
    ]
  }
]

もう、この時点で「コレジャナイ」感が満載なわけですが、そこはめげずに見ていきましょう。

Silverlight/WPF では Behavior と一括りでいってますが、実際のところは "Behavior" と "Trigger/Action の組"のどちらかを設定します。が、こちらでは Trigger はなくなって TriggerBehavior って形になってますね。

読んでいくと、 最上位の name ってのが HTML の data-blend-behavior 属性の値と同じになってます。どうやら、この値を一致させて、同階層にある behaviors の内容を適用する模様。で、 behaviors の中は配列になっているので、同じ値で複数の behavior が設定できるのですね。

で、一番上の type が Blend.Behaviors.EventTriggerBehavior になってるので、「イベントを受けて何かを動かす振る舞い」ってことでしょう。 event の値は click になってます。なので、 「click を受けて何かを動かす振る舞い」ってことですね。

「何かを動かす振る舞い」の部分はその下の、triggerdActions の部分ですね。ここも配列なので複数の振る舞いを設定できるようですが、一つ目に画面で指定した Blend.Actions.ToggleClassActionが出てきます。ってことで、ToggleClassActionはそのままでクラスをトグルしてくれるので「イベントを受けて、クラスをトグルする振る舞い」って感じで指定されています。

さて、ここまで見てきてあれなのですが、指定の中に「何ていう」クラスをトグルするかは出てきてません。謎です。っていうか、どう考えても必ず指定が必要なのでどうにかして描けるはずです。で、HTML なのでどこかにソースがあるはずと思ってもう一回プロジェクトをよく見たらありました。"Blend.Runtime.1.0.js" ってのが、 js フォルダの直下に。再び「コレジャナイ」感が満載なわけですが、そこもめげずに見ていきましょう。

とりあえず、開いて「ToggleClassAction」を検索してみると、ありました。className ってプロパティを指定しろっぽいことが書かれてます。ほかには targetSelector ってのもありますね。

{
  // Property Meta-data (for JSON parsing)
  className: { type: String },
  targetSelector: { type: String }
}

っていうことで、className を追加してみました。値は "behavior-on" ってしてます。

"triggeredActions": [
  {
    "type": "Blend.Actions.ToggleClassAction",
    "className": "behavior-on"
  }
]

上記で指定した "behavior-on" って値が、ボタンをクリックするたびについたり消えたりするはずです。とりあえず、css で .behavior-on ってクラス名には背景章を赤で指定しておきました。

.behavior-on{
  background-color: red;
}

で、実行してボタンを押してみます。

f:id:k_maru:20130630011624p:plain

無事にクリックしたら、赤になって、もう一回クリックしたらもとに戻りました。

さて、ここまでで使い方は何となくわかりました。「コレジャナイ」感がもう絶頂なんですが、なんやかんや言うても作れもしないのに文句は言っちゃダメと思い、とりあえず作ってみました。もちろん作り方はソースをよみます(長くなっているので作り方はまた別途っていうことで)

作ったコードは以下のような感じ。ダイアログを上げるだけですね。

WinJS.Namespace.define("App.Actions", {
  AlertAction: WinJS.Class.derive(Blend.Actions.ActionBase,
    function () {
    }, {
      message: "",
      execute: function () {
        Windows.UI.Popups.MessageDialog(this.message).showAsync();
      }
    }, {
      message: { type: String }
    })
});

上記で作ったものを定義に追加すると以下のような感じになります。

"triggeredActions": [
  {
    "type": "Blend.Actions.ToggleClassAction",
    "className": "behavior-on"
  }, {
    "type": "App.Actions.AlertAction",
    "message": "Hello WinJS-Blend Behavior!"
  }
]

動かすと無事にダイアログが表示されました。

f:id:k_maru:20130630012637p:plain

まぁ、最後ちょっと飛ばしまくりでした、そのうちキレイにまとめます。


まぁ、何回も書いている通り「コレジャナイ」感でいっぱいなので、使うか使わないかは今後のなりゆき次第かなと。しかし、 XAML のほうの Behavior はどないのっとるのか・・・。