2014年6月5日木曜日

wpf : xamlで描いたアイコンの使いまわし(ボツ案)

xamlでcanvasを使ってアイコンを描きました。 それを、例えば次のようにして使いまわそうとしても失敗します。 2つ目のボタンにしかアイコンが表示されません。

理由は簡単で、icon(Canvas)が「StaticResourceにインスタンスが1つだけしかないUIElement」だからです。 UIElementはレイアウトの計算をしてどのように表示するか決めます。 親が複数あると、どの親用にレイアウトを計算すればいいのか分かりません。 それだけじゃなくて、親からのバインドやイベントの登録などもあるかもしれません。 とにかく複数の親があるのは都合が悪いのです。 そこで、UIElementは親が切り替わると古い親のビジュアルツリーから削除され、複数の親を持たないようになっています。 この場合は最後に設定された2つ目のボタンにのみiconが登録されます。

そんなわけで、そのままではアイコンの使い回しができません。 なんとかインスタンスをコピーしてアイコンを使いまわす方法について考えました。 で、できてからふとDrawingBrushとかVisualBrushとかでアイコンを描いてブラシを使いまわすのが正しいやり方だと思いつきました。 Canvas上でShapeを使うのはアイコンを描くには重すぎですしね。 まぁ、他の用途で役に立つかもしれないので一応Canvas使い回しのやり方を投稿しておきます。

やり方を簡単に説明すると、Window.ResourcesにResourceDictionaryを継承した独自ResourceDictionaryを載せて、使いまわしをしたいアイコンにアクセスされたらコピーを返すようにします。 継承するのはResourceDictionary.OnGettingValueメソッドを使うためです。 これをオーバーライドするとxamlやその他からリソースにアクセスされたとき、返す値を入れ替えることができます。

コードを載せましょう。 ResourceDictionaryを継承したAppResourceDictionaryクラスです。

AppResourceDictionaryを使う側のxamlです。

Window.Resourcesに使いまわしたいアイコン(icon1)が書かれています。 その下の文字列(icon2)はicon1のコピー先の目印です。 icon2へのアクセスがあったらAppResourceDictionary.OnGettingValueメソッドは文字列の内容(ref_icon1)を見ます。 そこに「icon1を参照」と書かれています。 それを元にicon1を探してきて、XamlWriter&XamlReaderで同じ内容のインスタンスを作成して返します。 何度も作るのは効率的ではないのでicon2の文字列はコピー後のインスタンスで置き換えておきます。 それだけです。

注意点は次の通り。

  • XamlWriter&XamlReaderでインスタンスをコピーすると、微妙に違うものになる可能性がある。 見た目は要目視確認。 イベントとかバインドとかは試して無いけど、コピー不可?
  • コピーするといってもインスタンスの数はResourcesに登録した分しかない。 DataTemplateなどには使えない。 そんなときはDrawingBrushで。
  • Visual Studioのデザイナにはコピー後のアイコンは表示されない。