Silverlightで再利用可能なカスタムコントロールを作る その2
テキストボックスの横幅は自動調整されるようになったけど、上下ボタンの縦高さも自動調整する必要があるのでこれもやる。
Controls/NumericUpDown.cs
private ButtonBase upButton; private ButtonBase downButton; private TextBox valueTextBox; private FrameworkElement buttonsPane; public override void OnApplyTemplate() { base.OnApplyTemplate(); upButton = (ButtonBase)this.GetTemplateChild("upButton"); downButton = (ButtonBase)this.GetTemplateChild("downButton"); valueTextBox = (TextBox)this.GetTemplateChild("valueTextBox"); buttonsPane = (FrameworkElement)this.GetTemplateChild("buttonsPane"); upButton.Click += upButton_Click; downButton.Click += downButton_Click; } protected override Size ArrangeOverride(Size finalSize) { var margin1 = upButton.Margin; var margin2 = downButton.Margin; upButton.Height = downButton.Height = finalSize.Height / 2 - ((margin1.Top + margin1.Bottom) + (margin2.Top + margin2.Bottom)); valueTextBox.Width = this.Width - buttonsPane.Width - 2; return base.ArrangeOverride(finalSize); } void upButton_Click(object sender, RoutedEventArgs e) { } void downButton_Click(object sender, RoutedEventArgs e) { }
TemplatePart属性の宣言も忘れずに。
[TemplatePart(Name = "upButton", Type = typeof(ButtonBase))] [TemplatePart(Name = "downButton", Type = typeof(ButtonBase))] [TemplatePart(Name = "valueTextBox", Type = typeof(TextBox))] [TemplatePart(Name = "buttonsPane", Type = typeof(FrameworkElement))] public class NumericUpDown : Control { }
これで外観はだいたいできたので、次は上下ボタンのテンプレートを外部から設定できるように変更してみよう。
やり方は、まずNumericUpDownクラスに「ButtonTemplate」という依存プロパティを「ControlTemplate」型で定義する。
Controls/NumericUpDown.cs
/// <summary> /// ButtonTemplate /// </summary> public static readonly DependencyProperty ButtonTemplateProperty = DependencyProperty.Register( "ButtonTemplate", typeof(ControlTemplate), typeof(NumericUpDown), new PropertyMetadata((d, e) => { }) ); /// <summary> /// 上下ボタンのテンプレートを取得、設定します。 /// </summary> public ControlTemplate ButtonTemplate { get { return (ControlTemplate)this.GetValue(ButtonTemplateProperty); } set { this.SetValue(ButtonTemplateProperty, value); } }
あとは「Generic.xaml」側で定義されている上下ボタンの「Template」プロパティにこの依存プロパティをバインディングすればいい。
Generic.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:my="clr-namespace:NumericUpDownDemo.Controls"> <Style TargetType="my:NumericUpDown"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="my:NumericUpDown"> <Grid Background="{TemplateBinding Background}"> <TextBox x:Name="valueTextBox" HorizontalAlignment="Left" Margin="1" /> <StackPanel x:Name="buttonsPane" Grid.Column="1" Width="{TemplateBinding ButtonWidth}" VerticalAlignment="Center" HorizontalAlignment="Right"> <Button x:Name="upButton" Margin="0,0,0,0.5" Cursor="Hand" Template="{TemplateBinding ButtonTemplate}" > <Polygon Points="0,3, 10,3, 5,0" Fill="Black" HorizontalAlignment="Center" VerticalAlignment="Center" /> </Button> <Button x:Name="downButton" Margin="0,0.5,0,0" Cursor="Hand" Template="{TemplateBinding ButtonTemplate}" > <Polygon Points="0,0, 10,0, 5,3" Fill="Black" HorizontalAlignment="Center" VerticalAlignment="Center" /> </Button> </StackPanel> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
これだけでOK。
では実際にテンプレートを変更してみよう。
Page.xaml
<UserControl x:Class="NumericUpDownDemo.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:my="clr-namespace:NumericUpDownDemo.Controls" Width="400" Height="300"> <Grid x:Name="LayoutRoot"> <my:NumericUpDown> <my:NumericUpDown.ButtonTemplate> <ControlTemplate> <Border BorderBrush="Black" BorderThickness="1" CornerRadius="4"> <Border.Background> <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1"> <GradientStop Offset="0" Color="Silver" /> <GradientStop Offset="0.7" Color="White" /> </LinearGradientBrush> </Border.Background> <ContentPresenter /> </Border> </ControlTemplate> </my:NumericUpDown.ButtonTemplate> </my:NumericUpDown> </Grid> </UserControl>
BorderとLinearGradientBrushを使って、ちょっとおしゃれなボタンにしている。
こんな感じで再利用可能なカスタムコントロールを作ることができる。思ったより簡単ですね!!
あと、コントロールとして必要な機能を一通り実装したバージョンは以下のようになる。
Controls/NumericUpDown.cs
using System; using System.Windows; using System.Windows.Media; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.ComponentModel; using System.Diagnostics; namespace NumericUpDownDemo.Controls { /// <summary> /// 値を増減できる入力コントロール /// </summary> [TemplatePart(Name = "upButton", Type = typeof(ButtonBase))] [TemplatePart(Name = "downButton", Type = typeof(ButtonBase))] [TemplatePart(Name = "valueTextBox", Type = typeof(TextBox))] [TemplatePart(Name = "buttonsPane", Type = typeof(FrameworkElement))] public class NumericUpDown : Control, INotifyPropertyChanged { /// <summary> /// デフォルトの背景色 /// </summary> private static readonly Brush DefaultBackground = new SolidColorBrush(Colors.LightGray); private ButtonBase upButton; private ButtonBase downButton; private TextBox valueTextBox; private FrameworkElement buttonsPane; /// <summary> /// ButtonWidth /// </summary> public static readonly DependencyProperty ButtonWidthProperty = DependencyProperty.Register( "ButtonWidth", typeof(double), typeof(NumericUpDown), new PropertyMetadata((d, e) => { }) ); /// <summary> /// ボタンの横幅を取得、設定します。 /// </summary> public double ButtonWidth { get { return (double)this.GetValue(ButtonWidthProperty); } set { this.SetValue(ButtonWidthProperty, value); } } /// <summary> /// ButtonTemplate /// </summary> public static readonly DependencyProperty ButtonTemplateProperty = DependencyProperty.Register( "ButtonTemplate", typeof(ControlTemplate), typeof(NumericUpDown), new PropertyMetadata((d, e) => { }) ); /// <summary> /// ボタンのテンプレートを取得、設定します。 /// </summary> public ControlTemplate ButtonTemplate { get { return (ControlTemplate)this.GetValue(ButtonTemplateProperty); } set { this.SetValue(ButtonTemplateProperty, value); } } /// <summary> /// Value /// </summary> public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( "Value", typeof(double), typeof(NumericUpDown), new PropertyMetadata((d, e) => ((NumericUpDown)d).OnValueChanged(e)) ); /// <summary> /// 値を取得、設定します。 /// </summary> public double Value { get { return (double)this.GetValue(ValueProperty); } set { this.SetValue(ValueProperty, value); } } /// <summary> /// Increment /// </summary> public static readonly DependencyProperty IncrementProperty = DependencyProperty.Register( "Increment", typeof(double), typeof(NumericUpDown), new PropertyMetadata((d, e) => { }) ); /// <summary> /// 増減値を取得、設定します。 /// </summary> public double Increment { get { return (double)this.GetValue(IncrementProperty); } set { this.SetValue(IncrementProperty, value); } } /// <summary> /// Maximum /// </summary> public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register( "Maximum", typeof(double), typeof(NumericUpDown), new PropertyMetadata((d, e) => { }) ); /// <summary> /// 最大値を取得、設定します。 /// </summary> public double Maximum { get { return (double)this.GetValue(MaximumProperty); } set { this.SetValue(MaximumProperty, value); } } /// <summary> /// Minimum /// </summary> public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register( "Minimum", typeof(double), typeof(NumericUpDown), new PropertyMetadata((d, e) => { }) ); /// <summary> /// 最小値を取得、設定します。 /// </summary> public double Minimum { get { return (double)this.GetValue(MinimumProperty); } set { this.SetValue(MinimumProperty, value); } } /// <summary> /// DoublePlaces /// </summary> public static readonly DependencyProperty DoublePlacesProperty = DependencyProperty.Register( "DecimalPlaces", typeof(int), typeof(NumericUpDown), new PropertyMetadata((d, e) => { }) ); /// <summary> /// 表示する少数桁数を取得、設定します。 /// </summary> public int DoublePlaces { get { return (int)this.GetValue(DoublePlacesProperty); } set { this.SetValue(DoublePlacesProperty, value); } } /// <summary> /// Valueプロパティの値が変更された時に呼び出されます。 /// </summary> public event RoutedEventHandler ValueChanged; /// <summary> /// ValueChangedイベントを呼び出します。 /// </summary> /// <param name="e"></param> protected virtual void OnValueChanged(DependencyPropertyChangedEventArgs e) { if(valueTextBox != null) { valueTextBox.Text = FormatValue((double)e.NewValue); } if(ValueChanged != null) { ValueChanged(this, new RoutedEventArgs { Source = this }); } OnPropertyChanged(new PropertyChangedEventArgs("Value")); } /// <summary> /// 標準的なコンストラクタ /// </summary> public NumericUpDown() { this.DefaultStyleKey = typeof(NumericUpDown); // デフォルト値 this.Width = 100; this.Height = 26; this.Background = DefaultBackground; this.ButtonWidth = 24; this.Value = 0; this.Increment = 1; this.Maximum = 100; this.Minimum = 0; this.DoublePlaces = 2; } public override void OnApplyTemplate() { base.OnApplyTemplate(); upButton = (ButtonBase)this.GetTemplateChild("upButton"); downButton = (ButtonBase)this.GetTemplateChild("downButton"); valueTextBox = (TextBox)this.GetTemplateChild("valueTextBox"); buttonsPane = (FrameworkElement)this.GetTemplateChild("buttonsPane"); upButton.Click += upButton_Click; downButton.Click += downButton_Click; valueTextBox.Text = FormatValue(this.Value); } protected override Size ArrangeOverride(Size finalSize) { var margin1 = upButton.Margin; var margin2 = downButton.Margin; upButton.Height = downButton.Height = finalSize.Height / 2 - ((margin1.Top + margin1.Bottom) + (margin2.Top + margin2.Bottom)); valueTextBox.Width = this.Width - buttonsPane.Width - 2; return base.ArrangeOverride(finalSize); } /// <summary> /// 指定した値を書式設定します。 /// </summary> /// <param name="value"></param> /// <returns></returns> [DebuggerStepThrough] string FormatValue(double value) { return value.ToString("f" + this.DoublePlaces.ToString()); } /// <summary> /// [▲]ボタンがクリックされた時 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void upButton_Click(object sender, RoutedEventArgs e) { if(this.Value + this.Increment > this.Maximum) return; this.Value += this.Increment; } /// <summary> /// [▼]ボタンがクリックされた時 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void downButton_Click(object sender, RoutedEventArgs e) { if(this.Value - this.Increment < this.Minimum) return; this.Value -= this.Increment; } /// <summary> /// <see cref="INotifyPropertyChanged.PropertyChanged"/> /// </summary> public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// PropertyChangedイベントを呼び出します。 /// </summary> /// <param name="e">イベント引数</param> protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { if(PropertyChanged != null) PropertyChanged(this, e); } } }
ソース
動作デモ