ずっと思ってた。WPFをやり始めたころからずっと。
カスタムコントロールコントロールを作りたい!!!ということでコツコツDependencyPropertyとかCommandとかやってきたのが実を結んで、ついにカスタムコントロール作成を入門してみるよ。
プロジェクトの作成
WpfMyControlという名前でプロジェクトを作成した。あえて見出しをつけるまでもないけど、はじめを大事にね。
コントロールの作成
右クリックメニューからさくっと追加。カスタムコントロール(WPF)っていうのを選ぼう。
WPFついてないのを選ぶとWindowsFormのになっちゃうので要注意。
名前はGreetControlにしてみた。コントロールを作成すると、Themes\Generic.xamlとGreetControl.csというファイルが作られる。
Generic.xamlは、見た目を定義するのに使います。GreetControlに、CommandやDependencyPropertyを定義する。
今回の目標
最初に書いておけって感じがしなくもないけど、今回の目標を書いておく。といあえず、テキストボックスとボタンがあって、ボタンを押すと、テキストボックスの中身がメッセージボックスで表示されるものを目指す。
見た目の作成
見た目を作っていく。見た目はGeneric.xamlにあるStyleに書いていく。最初の状態だとGeneric.xamlには、下のようなStyleが定義されている。
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfMyControl">
<Style TargetType="{x:Type local:GreetControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:GreetControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
GreetControlをTargetTypeにして、Templateを設定するStyleが定義されているのがわかる。
ここにStackPanelを置いて、TextBoxとButtonをとりあえず置いてみた。
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfMyControl">
<Style TargetType="{x:Type local:GreetControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:GreetControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel>
<TextBox />
<Button Content="Greet!!" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
ちょっと見た目を弄ったので、Window1.xamlに置いてみる。namespaceを定義してWindowにぽちっと置いてみた。
<Window x:Class="WpfMyControl.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfMyControl"
Title="Window1" Height="300" Width="300">
<StackPanel>
<local:GreetControl />
</StackPanel>
</Window>
実行してみると、確かに見た目は出来てる!
次はプロパティを作ってみよう。とりあえず、TextBoxに入力された値を保持するためのプロパティが必要になりそう。ということでプロパティをつくってみようと思う。
プロパティの定義
プロパティは、普通に依存プロパティで作ることになる。string型のValueプロパティなので、こんな感じでいける。
using System;
using System.Collections.Generic;
using System.Linq;
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.Navigation;
using System.Windows.Shapes;
namespace WpfMyControl
{
public class GreetControl : Control
{
#region Value 依存プロパティ
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// GreetControlのstring型のValueプロパティで、デフォルト値が空文字
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value",
typeof(string),
typeof(GreetControl),
new UIPropertyMetadata(""));
#endregion
static GreetControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(GreetControl), new FrameworkPropertyMetadata(typeof(GreetControl)));
}
}
}
usingとかが長いけど、追加したのはregion~endregionまでの間になる。これで、Window1.xamlでValueプロパティに値を設定できるようになる。
<Window x:Class="WpfMyControl.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfMyControl"
Title="Window1" Height="300" Width="300">
<StackPanel>
<local:GreetControl Value="こんにちは" />
</StackPanel>
</Window>
ビルドは通るけど、ぜんぜん動きとしては変わらない。プロパティの値を見た目に反映させるには、Generic.xamlをいじくることになる。
プロパティの値をバインドして画面に出すよ
ということでGeneric.xamlに戻って編集を再開。ValueプロパティをTextBoxにバインドして出してみようと思う。
ということでTemplateBindingでさくっとバインドをする。
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfMyControl">
<Style TargetType="{x:Type local:GreetControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:GreetControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel>
<!-- Binding!! -->
<TextBox Text="{TemplateBinding Value}"/>
<Button Content="Greet!!" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
これで実行すると、画面にWindow1.xamlで設定した「こんにちは」が表示されるようになる。
動きをつけよう
最後に動きをつけて完成かな。このコントロールの動きは、ボタンを押したときにポローンとメッセージボックスが出るってだけ。単純にイベントを登録するのではなく、Commandを使ってボタンのクリックを補足します。
とりあえずCommandの定義をGreetControl.csに追加。
using System;
using System.Collections.Generic;
using System.Linq;
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.Navigation;
using System.Windows.Shapes;
namespace WpfMyControl
{
public class GreetControl : Control
{
#region Value 依存プロパティ
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// GreetControlのstring型のValueプロパティで、デフォルト値が空文字
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value",
typeof(string),
typeof(GreetControl),
new UIPropertyMetadata(""));
#endregion
#region GreetCommand
public static ICommand GreetCommand = new RoutedCommand(
"GreetCommand", typeof(GreetControl));
#endregion
static GreetControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(GreetControl), new FrameworkPropertyMetadata(typeof(GreetControl)));
}
}
}
Commandが定義できたらStaticコンストラクタにCommandBindingを追加する。Staticメソッド経由でインスタンスメソッドを呼び出してる。そこでMessageBoxの表示処理を書くって寸法。
using System;
using System.Collections.Generic;
using System.Linq;
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.Navigation;
using System.Windows.Shapes;
namespace WpfMyControl
{
public class GreetControl : Control
{
#region Value 依存プロパティ
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// GreetControlのstring型のValueプロパティで、デフォルト値が空文字
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value",
typeof(string),
typeof(GreetControl),
new UIPropertyMetadata(""));
#endregion
#region GreetCommand
public static ICommand GreetCommand = new RoutedCommand(
"GreetCommand", typeof(GreetControl));
#endregion
static GreetControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(GreetControl), new FrameworkPropertyMetadata(typeof(GreetControl)));
// CommandBingingを登録
var greetCommandBinding = new CommandBinding(
GreetCommand, OnGreetCommand);
CommandManager.RegisterClassCommandBinding(
typeof(GreetControl), greetCommandBinding);
}
#region Command
private static void OnGreetCommand(object sender, ExecutedRoutedEventArgs e)
{
// senderからコントロールを取得して、インスタンスメソッドに処理を丸投げ
var control = (GreetControl)sender;
control.OnGreetCommand();
}
/// <summary>
/// GreetCommandの処理の実体
/// </summary>
public void OnGreetCommand()
{
MessageBox.Show(this.Value);
}
#endregion
}
}
そして、Generic.xamlで、ButtonとCommandを関連付ける。
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfMyControl">
<Style TargetType="{x:Type local:GreetControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:GreetControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel>
<!-- Binding!! -->
<TextBox Text="{TemplateBinding Value}"/>
<!-- Command!! -->
<Button Content="Greet!!" Command="{x:Static local:GreetControl.GreetCommand}" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
動作確認
ということで、実行して動作を確認してみる。実行してボタンをぽちっとすると…
でた~!ということで始めての、作り方は正統派のコントロールの作り方でした。