コントロールの作成の概要

Windows Presentation Foundation (WPF) コントロール モデルの機能拡張により、新しいコントロールを作成する必要性が大幅に削減されます。ただし、場合によっては、カスタム コントロールを作成する必要があります。ここでは、カスタム コントロールを作成する必要性を最小限に抑える機能と、Windows Presentation Foundation (WPF) のさまざまなコントロール作成モデルについて説明します。また、新しいコントロールを作成する方法も示します。

このトピックは、次のセクションで構成されています。

  • 新しいコントロールを記述する場合
  • コントロール作成モデル
  • UserControl の作成
  • コントロールの作成の基本
  • カスタム コントロールの作成
  • 関連トピック

新しいコントロールを記述する場合

従来、カスタマイズの内容を既存のコントロールから取得する場合は、背景色、境界線の幅、フォントのサイズなど、コントロールの標準プロパティの変更は制限されていました。これらの定義済みのパラメータを超えてコントロールの外観や動作を拡張する場合は、新しいコントロールを作成する必要がありました。<

WPF のコントロールは、リッチ コンテンツ、StyleTrigger、およびテンプレートをサポートします。多くの場合、これらの機能は、新しいコントロールを作成しないで、カスタム操作および一貫した操作を作成できるようにします。

  • リッチ コンテンツ。標準の WPF コントロールの多くは、リッチ コンテンツをサポートします。これらのコントロールは、WPF のビジュアル要素と任意のデータも含む任意のコンテンツの表示をサポートしており、新しいコントロールを作成したり、複雑な視覚化をサポートするために既存のコントロールを変更する必要性を軽減します。詳細については、「コンテンツ モデル」を参照してください。

  • スタイルStyle は、コントロールのプロパティを表す値のコレクションです。スタイルを使用して、新しいコントロールを作成することなく、目的のコントロールの外観や動作の再利用可能な表現を作成できます。

  • トリガTrigger を使用して、コントロールの外観や動作を宣言的な方法で変更するプロパティ値を適用できます。この場合も新しいコントロールを作成する必要はありません。

  • テンプレート。WPF 内の多くのコントロールはテンプレートをサポートしています。テンプレートを使用すると、アプリケーションの作成者はコードを作成することなく、コントロールのビジュアル表現を拡張できます。テンプレートは、コントロールを構成する、単なるビジュアル要素の表現です。

スタイル、トリガ、およびテンプレートの詳細については、「 スタイルとテンプレート」を参照してください。

これらの機能により、新しいコントロールを作成する必要性を最小限に抑えることができます。ただし、新しいコントロールを作成する必要がある場合は、WPF の各種コントロール作成モデルを理解することが重要です。これらのモデルについては次のセクションで説明します。

コントロール作成モデル

WPF には、コントロールを作成するための一般的なモデルが 3 つあり、各モデルがそれぞれ異なる機能と柔軟性レベルを提供します。新しいコントロールの作成を開始する前に、これらのモデルを理解することが重要です。

UserControl からの派生

WPF でコントロールを作成する場合は、UserControl のサブクラスを作成するのが最も簡単な方法です。UserControl の開発モデルは、WPF でのアプリケーション開発に使用されるモデルに非常によく似ています。ビジュアル要素を作成し、それらの要素に名前を付け、Extensible Application Markup Language (XAML) でイベント ハンドラを参照します。これで、名前の付いた要素をコード内で参照できます。

UserControl は、正しく構築されていれば、リッチ コンテンツ、スタイル、およびトリガの利点を活用できます。ただし、テンプレートをサポートするためには、Control から派生してカスタム Control を作成する必要があります。

UserControl からの派生の利点

次に該当する場合は、UserControl から派生することをお勧めします。

  • アプリケーションの構築と同じ方法でコントロールを構築する必要がある場合。

  • コントロールが既存のコンポーネントだけで構成されている場合。

  • 複雑なカスタマイズをサポートする必要がない場合。

Control からの派生

Control クラスからの派生は、既存の WPF コントロールの多くで使用されるモデルです。カスタム Control は、テンプレートを使用して操作ロジックを視覚的表現から分離するように設計されています。カスタム Control の構築は UserControl の構築ほど簡単ではありませんが、カスタム Control は優れた柔軟性を提供します。

Control からの派生の利点

次の 1 つ以上の項目に該当する場合は、UserControl クラスを使用するのではなく、Control から派生することをお勧めします。

  • コントロールの外観を、ControlTemplate を通じてカスタマイズ可能にする必要がある場合。

  • コントロールでさまざまなテーマをサポートする必要がある場合。

FrameworkElement からの派生

UserControlControl の両方から派生するコントロールは、それを構成する既存の要素に依存します。カスタム コンポーネントで必要とされる細かいコントロールが、単純な要素コンポジションによる機能を超える場合があります。このような状況では、FrameworkElement に基づいてコンポーネントを作成するのが適切な選択です。FrameworkElement に基づくコンポーネントを構築するには、ダイレクト レンダリングとカスタム要素コンポジションの 2 つの標準的な方法があります。ダイレクト レンダリングでは、FrameworkElementOnRender メソッドをオーバーライドし、コンポーネントのビジュアルを明示的に定義する DrawingContext 操作を提供します。これは、Image および Border で使用される方法です。カスタム要素コンポジションでは、Visual 型のオブジェクトをインスタンス化し、それらを使用してコンポーネントの外観を構成します。例については、「DrawingVisual オブジェクトの使用」を参照してください。たとえば、カスタム要素コンポジションを使用する WPF のコントロールとして、Track があります。また、同じコントロールでダイレクト レンダリングとカスタム要素コンポジションの両方を使用することもできます。

FrameworkElement からの派生の利点

次の 1 つ以上の項目に該当する場合は、FrameworkElement から派生することをお勧めします。

  • コントロールの外観に関して、単純な要素コンポジションで提供されるよりも細かい制御を必要とする場合。

  • 独自のレンダリング ロジックを定義してコントロールの外観を定義する必要がある場合。

  • UserControl および Control を使用する方法では実現できない新しい方法で既存の要素を構成する必要がある場合。

UserControl の作成

前述のように、WPF でコントロールを作成するには、UserControl のサブクラスを作成するのが最も簡単な方法です。NumericUpDown UserControl のユーザー インターフェイス (UI) を定義する XAML を次の例に示します。

<UserControl x:Class="MyUserControl.NumericUpDown"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid Margin="3">
    <Grid.RowDefinitions>
      <RowDefinition/>
      <RowDefinition/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition/>
      <ColumnDefinition/>
    </Grid.ColumnDefinitions>

    <Border BorderThickness="1" BorderBrush="Gray" Margin="2" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">
      <TextBlock Name="valueText" Width="60" TextAlignment="Right" Padding="5"/>
    </Border>
    <RepeatButton Name="upButton" Click="upButton_Click" Grid.Column="1" Grid.Row="0">Up</RepeatButton>
    <RepeatButton Name="downButton" Click="downButton_Click" Grid.Column="1" Grid.Row="1">Down</RepeatButton>

  </Grid>
</UserControl>

この UserControl のロジックを次の例に示します。

using System;
using System.Windows;
using System.Windows.Controls;

namespace MyUserControl
{
    public partial class NumericUpDown : System.Windows.Controls.UserControl
    {
        /// <summary>
        /// Initializes a new instance of the NumericUpDownControl.
        /// </summary>
        public NumericUpDown()
        {
            InitializeComponent();

            UpdateTextBlock();
        }

        /// <summary>
        /// Gets or sets the value assigned to the control.
        /// </summary>
        public decimal Value
        {
            get { return _value; }
            set
            {
                if (value != _value)
                {
                    if (MinValue <= value && value <= MaxValue)
                    {
                        _value = value;
                        UpdateTextBlock();
                        OnValueChanged(EventArgs.Empty);
                    }
                }
            }
        }


        private decimal _value = MinValue;


        /// <summary>
        /// Occurs when the Value property changes.
        /// </summary>
        public event EventHandler<EventArgs> ValueChanged;


        /// <summary>
        /// Raises the ValueChanged event.
        /// </summary>
        /// <param name="args">An EventArgs that contains the event data.</param>
        protected virtual void OnValueChanged(EventArgs args)
        {
            EventHandler<EventArgs> handler = ValueChanged;
            if (handler != null)
            {
                handler(this, args);
            }
        }

        private void upButton_Click(object sender, EventArgs e)
        {
            if (Value < MaxValue)
            {
                Value++;
            }
        }

        private void downButton_Click(object sender, EventArgs e)
        {
            if (Value > MinValue)
            {
                Value--;
            }
        }

        private void UpdateTextBlock()
        {
            valueText.Text = Value.ToString();
        }

        private const decimal MinValue = 0, MaxValue = 100;
    }
}

この例に示すように、カスタム UserControl の開発モデルは、アプリケーションの開発に使用されるモデルと非常によく似ています。

コントロールの作成の基本

前のセクションの UserControl の例で定義されているプロパティとイベントは、標準の共通言語ランタイム (CLR) プロパティとイベントであり、プラットフォームの機能は利用しません。ここでは、コントロール作成モデルに関係なく、WPF コントロールの一般的なガイドラインについて説明します。

DependencyProperty を使用したプロパティの補足

一般的に、新しいコントロールのすべてのプロパティを DependencyProperty を使用して補足することをお勧めします。詳細については、「カスタム依存関係プロパティ」を参照してください。

RoutedEvent の使用

CLR プロパティの概念を追加の機能で拡張する依存関係プロパティと同様に、ルーティング イベントは、標準の CLR イベントの概念を拡張します。新しい WPF コントロールを作成する場合は、イベントを RoutedEvent として実装する方法もお勧めします。詳細については、「ルーティング イベントの概要」および「方法 : カスタム ルーティング イベントを作成する」を参照してください。

DependencyProperty と RoutedEvent を使用するための UserControl の変更

次の例に示すように、前の UserControl の例を変更して、DependencyProperty および RoutedEvent, を使用できます。

using System;
using System.Windows;
using System.Windows.Controls;

namespace MyUserControl
{
    public partial class NumericUpDown : System.Windows.Controls.UserControl
    {
        /// <summary>
        /// Initializes a new instance of the NumericUpDownControl.
        /// </summary>
        public NumericUpDown()
        {
            InitializeComponent();

            UpdateTextBlock();
        }

        /// <summary>
        /// Gets or sets the value assigned to the control.
        /// </summary>
        public decimal Value
        {          
            get { return (decimal)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        /// <summary>
        /// Identifies the Value dependency property.
        /// </summary>
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register(
                "Value", typeof(decimal), typeof(NumericUpDown), 
                new FrameworkPropertyMetadata(MinValue,new PropertyChangedCallback(OnValueChanged)));

        private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            NumericUpDown control = (NumericUpDown)obj;         
            control.UpdateTextBlock();

            RoutedPropertyChangedEventArgs<decimal> e = new RoutedPropertyChangedEventArgs<decimal>(
                (decimal)args.OldValue, (decimal)args.NewValue, ValueChangedEvent);
            control.OnValueChanged(e);
        }

        /// <summary>
        /// Identifies the ValueChanged routed event.
        /// </summary>
        public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
            "ValueChanged", RoutingStrategy.Bubble, 
            typeof(RoutedPropertyChangedEventHandler<decimal>), typeof(NumericUpDown));

        /// <summary>
        /// Occurs when the Value property changes.
        /// </summary>
        public event RoutedPropertyChangedEventHandler<decimal> ValueChanged
        {
            add { AddHandler(ValueChangedEvent, value); }
            remove { RemoveHandler(ValueChangedEvent, value); }
        }

        /// <summary>
        /// Raises the ValueChanged event.
        /// </summary>
        /// <param name="args">Arguments associated with the ValueChanged event.</param>
        protected virtual void OnValueChanged(RoutedPropertyChangedEventArgs<decimal> args)
        {
            RaiseEvent(args);
        }

        private void upButton_Click(object sender, EventArgs e)
        {
            if (Value < MaxValue)
            {
                Value++;
            }
        }

        private void downButton_Click(object sender, EventArgs e)
        {
            if (Value > MinValue)
            {
                Value--;
            }
        }

        private void UpdateTextBlock()
        {
            valueText.Text = Value.ToString();
        }

        private const decimal MinValue = 0, MaxValue = 100;
    }
}

サンプル全体については、「DependencyProperty と RoutedEvent を持つ NumericUpDown UserControl のサンプル」を参照してください。

カスタム コントロールの作成

テンプレートをサポートするためのコントロールの構築

WPF で再利用可能な機能を構築するには、UserControl を使用する方法が最も簡単ですが、テンプレートを利用したり、さまざまなテーマをサポートするためには、Control から派生させる方法が標準的です。ここでは、前のセクションの UserControl の例をカスタム Control に変換します。

基本クラスの変更

最初に、UserControl 基本クラスを Control で置き換えます。

テンプレートへの移動

基本クラスを更新したら、コントロールのコンテンツをテンプレートに移動する必要があります。テンプレートはスタイルで定義され、アプリケーション内の多くの場所に置くことができます。この例では、アプリケーション リソースに置きます。

UserControl の例では、TextBlockRepeatButton に名前が割り当てられていました。RepeatButton は、コードで定義されているイベント ハンドラも参照していました。このカスタム Control では、それらを削除できます。代わりに、バインディングとコマンドを使用して同じ動作を、より疎結合の方法で取得します。

<Application x:Class="MyCustomControl.MyApp"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="Window1.xaml"
    xmlns:local="clr-namespace:MyCustomControl">
  <Application.Resources>

    <Style TargetType="{x:Type local:NumericUpDown}">
      <Setter Property="HorizontalAlignment" Value="Center"/>
      <Setter Property="VerticalAlignment" Value="Center"/>
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type local:NumericUpDown}">
            <Grid Margin="3">
              <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
              </Grid.RowDefinitions>
              <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
              </Grid.ColumnDefinitions>

              <Border BorderThickness="1" BorderBrush="Gray" Margin="2" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">
                <TextBlock Width="60" TextAlignment="Right" Padding="5"/>
              </Border>
              <RepeatButton Grid.Column="1" Grid.Row="0">Up</RepeatButton>
              <RepeatButton Grid.Column="1" Grid.Row="1">Down</RepeatButton>
            </Grid>

          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>

  </Application.Resources>
</Application>

Value プロパティの変更

UserControl の例の UpdateTextBlock メソッドは、Value プロパティが変更されると、UI 内の TextBlock のコンテキストを変更します。このメソッドは削除できます。代わりに、TextBlock をコントロールの Value に直接バインドできます。この分離により、コントロールの実装に影響を与えることなくテンプレートを変更できます。Binding 宣言を使用する TextBlock を次の例に示します。

<TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value}"  Width="60" TextAlignment="Right" Padding="5"/>

データ バインディングの詳細については、「データ バインディングの概要」を参照してください。

入力の処理

UserControl の例では、RepeatButton が、コードで定義されているイベント ハンドラを直接参照していました。カスタム Control では、コマンドを使用して同じ動作をより柔軟な方法で実現できます。コントロールは、次の例に示すようにコマンドを定義できます。

public static RoutedCommand IncreaseCommand
{
    get
    {
        return _increaseCommand;
    }
}
public static RoutedCommand DecreaseCommand
{
    get
    {
        return _decreaseCommand;
    }
}

private static void InitializeCommands()
{
    _increaseCommand = new RoutedCommand("IncreaseCommand", typeof(NumericUpDown));
    CommandManager.RegisterClassCommandBinding(typeof(NumericUpDown), new CommandBinding(_increaseCommand, OnIncreaseCommand));
    CommandManager.RegisterClassInputBinding(typeof(NumericUpDown), new InputBinding(_increaseCommand, new KeyGesture(Key.Up)));

    _decreaseCommand = new RoutedCommand("DecreaseCommand", typeof(NumericUpDown));
    CommandManager.RegisterClassCommandBinding(typeof(NumericUpDown), new CommandBinding(_decreaseCommand, OnDecreaseCommand));
    CommandManager.RegisterClassInputBinding(typeof(NumericUpDown), new InputBinding(_decreaseCommand, new KeyGesture(Key.Down)));
}

private static void OnIncreaseCommand(object sender, ExecutedRoutedEventArgs e)
{
    NumericUpDown control = sender as NumericUpDown;
    if (control != null)
    {
        control.OnIncrease();
    }
}
private static void OnDecreaseCommand(object sender, ExecutedRoutedEventArgs e)
{
    NumericUpDown control = sender as NumericUpDown;
    if (control != null)
    {
        control.OnDecrease();
    }
}

protected virtual void OnIncrease()
{
    if (this.Value < MaxValue)
    {
        this.Value += 1;
    }
}
protected virtual void OnDecrease()
{
    if (this.Value > MinValue)
    {
        this.Value -= 1;
    }
}

private static RoutedCommand _increaseCommand;
private static RoutedCommand _decreaseCommand;

テンプレートの要素は、次の例に示すようにコマンドを参照できます。

<RepeatButton Command="{x:Static local:NumericUpDown.IncreaseCommand}"  Grid.Column="1" Grid.Row="0">Up</RepeatButton>
<RepeatButton Command="{x:Static local:NumericUpDown.DecreaseCommand}"  Grid.Column="1" Grid.Row="1">Down</RepeatButton>

コマンドの詳細については、「コマンド実行の概要」を参照してください。

テンプレートの定義とバインディングおよびコマンドの使用により、NumericUpDown コントロールを、固定された視覚化を持つ静的な UserControl から、柔軟でカスタマイズ可能なカスタム Control に変更しています。

サンプル全体については、「1 つのプロジェクトでの NumericUpDown カスタム コントロールのサンプル」を参照してください。

外部のコントロール ライブラリ

最後の手順は、NumericUpDown コントロールをその独自のアセンブリにパッケージ化して、簡単に再利用できるようにすることです。

テーマ ファイルの作成

NumericUpDown クラスをライブラリ アセンブリに移動したら、スタイル定義を移動する必要があります。最初に、すべてのテーマ ファイルを格納するための "themes" フォルダを作成する必要があります。次に、generic.xaml という名前のファイルを作成します。このファイルは、このアセンブリのすべてのリソース検索のフォールバックとして機能します。

generic.xamlResourceDictionary を定義し、ResourceDictionary 内に NumericUpDown コントロールのスタイルを置きます。

ThemeInfo 属性の定義

generic.xamlStyle が見つかるようにするためには、ホスト アプリケーションはアセンブリにコントロール固有のリソースが含まれていることを把握する必要があります。これを実行するためには、アセンブリ属性をクラスに追加します。アセンブリにはジェネリックなリソースだけが含まれているため、GenericDictionaryLocation プロパティの SourceAssembly を設定します。AssemblyInfo.cs ファイルのコンテンツを次に示します。

[assembly: ThemeInfo(
    ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
    //(used if a resource is not found in the page, 
    // or application resource dictionaries)
    ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
    //(used if a resource is not found in the page, 
    // app, or any theme specific resource dictionaries)
)]

サンプル全体については、「外部ライブラリでの NumericUpDown カスタム コントロールのサンプル」を参照してください。

Windows テーマのサポート

同じコントロールが、さまざまな WPF テーマによって異なる概観を持つことができます。複数のテーマをサポートするためには、正しいスタイル、テンプレート、およびコントロールに必要なその他のリソースを使用してテーマ ファイルを定義する必要があります。ThemeInfo 属性の ResourceDictionaryLocation パラメータを設定して、ソース アセンブリを参照する必要もあります。

[assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly, ResourceDictionaryLocation.SourceAssembly)]

サンプル全体については、「テーマおよび UI オートメーションがサポートされた NumericUpDown カスタム コントロールのサンプル」を参照してください。

参照

その他の技術情報

コントロールのカスタマイズ
コントロールのカスタマイズのサンプル