Expression Blend 4 でカスタムコントロールとそのテンプレートを作ってみる

前回はWPFのスタイルやテンプレートについて書いたので、今回は実際に Expression Blend 4 でカスタムコントロールの作成とテンプレートの編集を行なってみます。

Expression Blend でプロジェクトを作成

Expression Blend を起動して、プロジェクトを作成します。


カスタムコントロールのクラスファイルを追加

コントロールに状態や属性を追加するにはカスタムコントロールを作ることになります。
今回の例ではフェードイン/フェードアウトするバッジがついた BadgedButton を作ってみます。

  • プロジェクトパネルでプロジェクトを選んで右クリック、新しいアイテムの追加
  • クラスファイルを選んでファイル名を指定。今回の例では BadgedButton.cs です


コードはこんな感じです。

  • IsBadged プロパティ、 BadgeImageSource プロパティ を追加
  • TemplateVisualState で BadgeStates グループの BadgeOn、BadgeOff 状態を定義
  • テンプレート適用時と IsBadged 変化時に VisualStateManager.GoToState を呼び出す
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace WindowsPhoneApplication1
{
	[TemplateVisualState(Name = "BadgeOn",GroupName = "BadgeStates")]
	[TemplateVisualState(Name = "BadgeOff",GroupName = "BadgeStates")]
	public class BadgedButton : Button { 
		static readonly Type my_type = typeof(BadgedButton);		

		// バッジのON/OFF
		public static readonly DependencyProperty IsBadgedProperty = 
			DependencyProperty.Register("IsBadged", typeof(bool),my_type , null); 
		public bool IsBadged { 
			get { return (bool)base.GetValue(IsBadgedProperty); } 
			set { 
				base.SetValue(IsBadgedProperty, value); 
				updateBadgeState(true);
			} 
		} 
		
		// バッジの画像
		public static readonly DependencyProperty BadgeImageSourceProperty = 
			DependencyProperty.Register("BadgeImageSource", typeof(ImageSource),my_type , null); 
		public ImageSource BadgeImageSource { 
			get { return (ImageSource)base.GetValue(BadgeImageSourceProperty); } 
			set { 
				base.SetValue(BadgeImageSourceProperty, value); 
			} 
		} 
		
		// テンプレート適用時に呼ばれる
		public override void OnApplyTemplate(){
			base.OnApplyTemplate();
			updateBadgeState(false);
		}

		// バッジの状態を反映させる	
		private void updateBadgeState(bool useTransitions){
			//  Go to states in WeatherStates state group
			if (IsBadged ){
				VisualStateManager.GoToState(this, "BadgeOn", useTransitions);
			}else{
				VisualStateManager.GoToState(this, "BadgeOff", useTransitions);
			}
		}
	}
}

コードを追加したら、

  • メニュー/プロジェクト/プロジェクトのビルド
  • メニュー/ウィンドウ/結果

で「ビルドは正常に完了しました」と表示されるか確認しておきます。

バッジ画像を用意

バッジの画像 と、おまけにロゴ を用意して、プロジェクトのフォルダにコピーします。
Expression Blend のプロジェクトパネルを右クリック、「既存の項目を追加」でプロジェクトに追加します。



コントロールをページに配置する

テンプレートを編集する間、テンプレートを適用する対象が実際に定義されていた方が表示の確認がしやすくて効率的です。 プロジェクトのメインページか、もしくは別途適当にページを作成してそこにコントロールを配置します。

MainPage.xml のアートボードを開きます。
アセットパネルでプロジェクトを選択すると先ほど作成したBadgedButtonがあるので、それを選択した状態でアートワークをドラッグすると、ドラッグの始点と終点を対角線とした矩形領域にコントロールが配置されます。

配置が終わったら左端のツールパネルで、選択内容もしくは個別選択を選んで、アートボードをクリックしても再度配置が行われないようにしておきます。

BadgedButton にプロパティを設定する

MainPage.xml のアートワークに配置したBadgedButtonを選択して、右端のプロパティパネルで内容を設定します。
その他- BadgeImageSource に badge.png を指定して、その他- IsBadged にチェックをつけます。
ただしこの2つはまだ表示に反映されません。

今回の例の目的からは少しはずれますが、おまけにボタン内部をテキストではなく画像にしてみます。
アセットパネルでメディアのlogo.png を選び、 デザインビューのBadgedButtonの中にドラッグ&ドロップします。
画面の右下にあるオブジェクトとタイムラインを見て、BadgedButtonの子要素に[Image]ができていることを確認します。
その[Image]を選択して、プロパティパネルでレイアウトのWidthとHeightを自動に、共通プロパティのStretchをUniformにすると画像がボタン内部にキレイに配置されます。

コントロール用のリソースディクショナリのXAMLを作成

カスタムコントロールのリソースをどのように管理するかは要件にもよりますが、この例では BadgedButton 用のリソースは 専用のファイルに格納します。

プロジェクトパネルを右クリックして新しいアイテムの追加を選び、リソースディクショナリの BadgedButton.xaml を作成します。

App.xamlからリンクを貼る

作成したリソースディクショナリはまだ他のどこからも参照されていないため、コントロールと関連付けることができません。
右側のリソースパネルでApp.xmlを右クリックしてリソースディクショナリへのリンクを選び、App.xaml から BadgedButton.xaml を参照するようにします。

テンプレートの作成

デザインビューまたはオブジェクトとタイムラインパネルでBadgedButtonを選んで右クリック、テンプレートの編集 - コピーして編集を選びます。

Styleリソースの作成ダイアログが開かれるので、
キーを「全てに適用」、定義先に「BadgedButton.xaml 」を指定します。

BadgedButton クラス用のスタイルが作成されました。

アートボードのデザインビューの上の方にあるパンくずリストで、スタイルの編集とControlTemplateの編集を切り替えることができます。

テンプレートの編集

テンプレートのデザインビューを開いて、次にアセットパネルで コントロール/すべて の Imageを選び、ボタンの上にドラッグします。
オブジェクトとタイムラインパネルでそのImageを選び、以下のようにプロパティを設定します

  • レイアウト/Width 自動
  • レイアウト/Height 自動
  • レイアウト/HorizontalAlignment Left
  • レイアウト/VerticalAlignment 中央
  • レイアウト/Margin 左20程度、右上下は0
  • 共通プロパティ/Source テンプレートのバインド BadgeImageSource
  • 共通プロパティ/Stretch None

ここまで設定したら一度MainPage.xaml のデザインビューを確認してみましょう。先ほど配置したボタンに設定したbadge.pngが表示されています。

状態変化をアニメーションさせる

BadgedButton.xaml のテンプレートのデザインビューを開きます。
左上の状態パネルの中にBadgeStates がありますね。
状態が変化した際のアニメーションを指定してみましょう。

  • まず状態パネルの各状態に赤いドットが表示されていないことを確認します。
  • テンプレート中の[Image]をタイムラインから選択して、プロパティの外観のOpacityを0にします。
  • 状態パネルのBadgeOnをクリックすると記録が始まります。赤いドットが表示されているはずです。
  • テンプレート中の[Image]をタイムラインから選択して、プロパティの外観のOpacityを100にします。
  • 状態パネルのBadgeOnの、赤いドットををクリックすると記録が完了します。

次はBadgeOffも同様に、今度は100から0への変化を記録します。

アニメーションにかかる時間を指定しましょう。BadgeStatesの既定の切り替え効果の、0s と書いてある箇所をクリックして、1に書き換えます

状態変化の指定が終わると、BadgedButton.xaml の中に以下のようなマークアップが生成されているはずです。

<VisualStateGroup x:Name="BadgeStates">
	<VisualStateGroup.Transitions>
		<VisualTransition GeneratedDuration="0:0:1"/>
	</VisualStateGroup.Transitions>
	<VisualState x:Name="BadgeOff">
		<Storyboard>
			<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="image" d:IsOptimized="True"/>
		</Storyboard>
	</VisualState>
	<VisualState x:Name="BadgeOn">
		<Storyboard>
			<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="image" d:IsOptimized="True"/>
		</Storyboard>
	</VisualState>
</VisualStateGroup>

ボタンをクリックするとバッジが変化するようにする

動作確認のため、ボタンをクリックしたらバッジの有無が変化するようにしてみましょう。

  • MainPage.xamlのデザインビューを開いて、ボタンを選択します。
  • プロパティパネルの上端でプロパティとイベントの表示を切り替えます。
  • Clickと書いてある部分の右の空白をダブルクリックします。
  • 空のクリックイベントハンドラが生成されてコードが表示されるので、以下のような内容に書き換えます。
private void BadgedButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
	var btn = sender as BadgedButton;
	btn.IsBadged = ! btn.IsBadged;
}

試しに動かしてみる

メニュー - プロジェクト - プロジェクトの実行 を選ぶと、Windows Phone のエミュレータが起動してアプリが開始されます。
ボタンを押す度にバッジがフェードアウト/フェードインするのを確認します。