コントロールの作成
WPFでは、コントロールの見た目を少し変えたいといった要求は、StyleやDataTemplate、ControlTemplateを使って簡単に実現できます。そのため、Windows Formのころに行っていた、見た目を変えるためのカスタムコントロールの作成は、ほとんど不要になっています。複数のコントロールを組み合わせたコントロールの作成や、独自の動作をするコントロールなど既存のコントロールのカスタマイズで対応できないような要件のみに限られています。
UserControl
UserControlは、複数のコントロールを組み合わせたコントロールを作成するのに向いています。UserControlは、Visual Studioのアイテムテンプレートからユーザーコントロール(WPF)を選択することで作成できます。これまでのWindowをベースに開発していたのと同じ要領で、デザイナを使って開発が出来る点が大きな特徴です。
以下にUserControlをデザイナで開いている画面を示します。Windowの開発と変わりがないことが確認できます。
UserControlの例として、NumericUpDownコントロールを作成する手順について示します。新規作成からユーザーコントロール(WPF)を選択し、NumericUpDownという名前で作成します。作成したら、以下のように2行2列のGridを作り数字を表示するためのTextBlockと、数字を増やしたり減らしたりするためのRepeatButtonを置きます。
<UserControlxmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:local="clr-namespace:CreateControlSample01"x:Class="CreateControlSample01.NumericUpDown"mc:Ignorable="d"d:DesignHeight="100"d:DesignWidth="287"><Grid><Grid.RowDefinitions><RowDefinition/><RowDefinition/></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="Auto"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions><RepeatButton Content="Up"Grid.Column="1"Margin="2.5"Click="UpButton_Click"/><RepeatButton Content="Down"Grid.Column="1"Grid.Row="1"Margin="2.5"Click="DownButton_Click"/><TextBlock x:Name="textBlockValue"Grid.RowSpan="2"TextWrapping="Wrap"HorizontalAlignment="Right"VerticalAlignment="Center"Margin="5"Foreground="Black"/></Grid></UserControl>
次に、NumericUpDownの値を保持するためのValue依存関係プロパティをNumericUpDownコントロールに作成します。
publicstaticreadonly DependencyProperty ValueProperty = DependencyProperty.Register( "Value", typeof(int), typeof(NumericUpDown), new PropertyMetadata(0)); publicint Value { get { return (int)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } }
画面のTextBlockのTextプロパティとValueプロパティをバインドします。今回は、BindingのRelativeSourceというものを使ってBindingの元になるオブジェクトを、コントロールのツリーを親へ親へ辿っていってNumericUpDownコントロールに行きあたるまで探索するように指定しています(FindAncestor)。
Text="{Binding Value, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:NumericUpDown}}}"
そして、RepeatButtonのクリックイベントで、Valueプロパティの値を操作します。
privatevoid UpButton_Click(object sender, RoutedEventArgs e) { this.Value++; } privatevoid DownButton_Click(object sender, RoutedEventArgs e) { this.Value--; }
これで、NumeridUpDownコントロールは完成です。UserControlを作成すると、デザイナのツールボックスで自動的に表示されるので、通常のコントロールと同じ要領で画面に配置することが出来ます。
VisualStateManager
UserControlでNumericUpDownコントロールを作成したので、ここで、コントロールの状態に応じてアニメーションを行うVisualStateManagerという機能について紹介します。VisualStateManagerは、見た目の状態を管理する機能です。StyleのTriggerなどでIsMouseOverがTrueの時にアニメーションを実行するといったことが可能でしたが、VisualStateManagerは、状態に名前を付けて管理することが出来る点が異なります。状態の遷移はプログラムから行うので、Triggerに比べてより複雑な条件を指定することが出来ます。
VisualStateManagerは、VisualStateManagerクラスのVisualStateGroups添付プロパティでコントロールに対して設定します。VisualStateGroups添付プロパティには、x:Nameで名前を付けたVisualStateGroupを設定します。VisualStateGroupの中には、x:Nameで名前をつけたVisualStateが定義できます。このVisualStateの中にStoryboardを設定してアニメーションを定義します。VisualStateGroupの役割ですが、同一のVisualStateGroup内のVisualStateは同時に1つしかアクティブになれないという制約があります。逆にいうと、異なる意味を持つVisualStateを別のVisualStateGroupに置くことで、同時に複数のVisualStateを有効にするといったことが可能になっています。
ここでは、Valueの値がマイナスのときだけValueの値を赤色にするVisualStateを定義したいと思います。VisualStateGroupの名前をNegativePositiveにして、その中にNegativeとPositiveというVisualStateを定義します。
<UserControl ...省略...d:DesignHeight="100"d:DesignWidth="287"><Grid><VisualStateManager.VisualStateGroups><VisualStateGroup x:Name="PositiveNegative"><VisualState x:Name="Positive" /><VisualState x:Name="Negative"><Storyboard><ColorAnimationStoryboard.TargetName="textBlockValue"Storyboard.TargetProperty="Foreground.Color"To="Red" /></Storyboard></VisualState></VisualStateGroup></VisualStateManager.VisualStateGroups> ...省略... </Grid></UserControl>
NegativeのVisualState内には、TextBlockの値を赤色にするアニメーションを定義しています。VisualStateの定義が終わったのでコードビハインドで、VisualStateの切り替え処理を書きます。VisualStateの切り替えは、VisualStateManager.GoToStateメソッドを使います。GoToStateメソッドは、VisualStateを切り替えるコントロールと、VisualState名と、VisualStateが切り替わるときのアニメーション効果を使用するかどうかを設定します。
Valueプロパティの値が書き換わったタイミングでVisualStateを切り替えればいいので以下のようなコードになります。
publicstaticreadonly DependencyProperty ValueProperty = DependencyProperty.Register( "Value", typeof(int), typeof(NumericUpDown), new PropertyMetadata(0, ValueChanged)); privatestaticvoid ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((NumericUpDown)d).UpdateState(true); } ...省略... public NumericUpDown() { InitializeComponent(); this.UpdateState(false); } ...省略... privatevoid UpdateState(bool useTransition) { if (this.Value >= 0) { VisualStateManager.GoToState(this, "Positive", useTransition); } else { VisualStateManager.GoToState(this, "Negative", useTransition); } }
UpdateStateというVisualStateを切り替える処理を作り、Valueプロパティの変更時と、NumericUpDownコントロールの初期化時に呼び出しています。初期化のときはアニメーション効果は不要なため切り替え効果はfalseを指定しています。Valueプロパティの値が変わった時は切り替え効果を有効にするためtrueを設定しています。
NumeridUpDownコントロールを実行すると以下のようにマイナスのときは赤色になることが確認できます。
VisualStateManagerは、フォーカスの状態に応じた見た目の管理など組み込みのコントロールの様々な個所で使用されています。
BlendでのVisualStateManagerの設定方法
VisualStateManagerは、複雑になると手書きするのが大変になってくるためBlendを使って作成するのが一般的です。Blendの状態タブを開くとベースという状態がデフォルトで選択されていて、その下にVisualStateGroupとVisualStateを定義できるようになっています。VisualStateを選択すると、通常のアニメーション作成と同じ要領で切り替え時の動作を設定できます。
以下の画面例は、NumericUpDownコントロールでNegativeのVisualStateを選択したときの表示です。
過去記事
- WPF4.5入門 その1 「はじめに」
- WPF4.5入門 その2 「WPFとは」
- WPF4.5入門 その3 「Hello world」
- WPF4.5入門 その4 「Mainメソッドはどこにいった?」
- WPF4.5入門 その5 「全てC#でHello world」
- WPF4.5入門 その6 「WPFを構成するものを考えてみる」
- WPF4.5入門 その7 「XAMLのオブジェクト要素と名前空間」
- WPF4.5入門 その8 「オブジェクト要素のプロパティ」
- WPF4.5入門 その9 「コレクション構文」
- WPF4.5入門 その10 「コンテンツ構文」
- WPF4.5入門 その11 「マークアップ拡張」
- WPF4.5入門 その12 「その他のXAMLの機能」
- WPF4.5入門 その13 「簡単なレイアウトを行うコントロール」
- WPF4.5入門 その14 「レイアウトコントロールのCanvasとStackPanel」
- WPF4.5入門 その15 「レイアウトコントロールのDockPanelとWrapPanel」
- WPF4.5入門 その16 「ViewBoxコントロール」
- WPF4.5入門 その17 「ScrollViewerコントロール」
- WPF4.5入門 その18 「Gridコントロール part 1」
- WPF4.5入門 その19 「Gridコントロール part 2」
- WPF4.5入門 その20 「レイアウトに影響を与えるプロパティ」
- WPF4.5入門 その21 「WPFのコンセプトと重要な機能つまみ食い」
- WPF4.5入門 その22 「Buttonコントロール」
- WPF4.5入門 その23 「DataGridコントロール その1」
- WPF4.5入門 その24 「DataGridコントロール その2」
- WPF4.5入門 その25 「TreeViewコントロール その1」
- WPF4.5入門 その26 「TreeViewコントロール その2」
- WPF4.5入門 その28 「Calendarコントロール」 - かずきのBlog@hatena
- WPF4.5入門 その29 「ContextMenuコントロール」 - かずきのBlog@hatena
- WPF4.5入門 その30「Menuコントロール」 - かずきのBlog@hatena
- WPF4.5入門 その31 「ToolBarコントロール」 - かずきのBlog@hatena
- WPF4.5入門 その32 「CheckBoxコントロール」 - かずきのBlog@hatena
- WPF4.5入門 その33 「ComboBoxコントロール」 - かずきのBlog@hatena
- WPF4.5入門 その34 「ListBoxコントロール」 - かずきのBlog@hatena
- WPF4.5入門 その35 「RadioButtonコントロール」 - かずきのBlog@hatena
- WPF4.5入門 その36 「Sliderコントロール」 - かずきのBlog@hatena
- WPF4.5入門 その37 「TabControl」 - かずきのBlog@hatena
- WPF4.5入門 その38 「ファイルダイアログ」 - かずきのBlog@hatena
- WPF4.5入門 その39 「情報を表示するコントロール」 - かずきのBlog@hatena
- WPF4.5入門 その40 「Popup、ToolTip、TextBox、Image、MediaElementコントロール」 - かずきのBlog@hatena
- WPF4.5入門 その41 「DispatcherObject」 - かずきのBlog@hatena
- WPF4.5入門 その42 「WPFのプロパティシステム」 - かずきのBlog@hatena
- WPF4.5入門 その43 「読み取り専用の依存関係プロパティ」 - かずきのBlog@hatena
- 拡張されたプロパティメタデータ - かずきのBlog@hatena
- WPF4.5入門 その45 「添付プロパティ」 - かずきのBlog@hatena
- WPF4.5入門 その46 「WPFのイベントシステム」 - かずきのBlog@hatena
- WPF4.5入門 その47 「コンテンツモデル」 - かずきのBlog@hatena
- WPF4.5入門 その48 「WPFのアニメーション その1」 - かずきのBlog@hatena
- WPF4.5入門 その49 「WPFのアニメーション その2」 - かずきのBlog@hatena
- WPF4.5入門 その50 「Style」 - かずきのBlog@hatena
- WPF4.5入門 その51 「リソース」 - かずきのBlog@hatena
- WPF入門 その52 「コントロールテンプレート」 - かずきのBlog@hatena