外観をカスタマイズできるコントロールの作成Creating a Control That Has a Customizable Appearance

Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF) を使用すると、外観をカスタマイズできるコントロールを作成できます。Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF) gives you the ability to create a control whose appearance can be customized. たとえば、新しい ControlTemplateを作成することによって設定されるプロパティを超えて CheckBox の外観を変更できます。For example, you can change the appearance of a CheckBox beyond what setting properties will do by creating a new ControlTemplate. 次の図は、既定の ControlTemplate とカスタム ControlTemplateを使用する CheckBox を使用する CheckBox を示しています。The following illustration shows a CheckBox that uses a default ControlTemplate and a CheckBox that uses a custom ControlTemplate.

既定のコントロール テンプレートを使用するチェックボックス。A checkbox with the default control template. 既定のコントロール テンプレートを使用する CheckBoxA CheckBox that uses the default control template

カスタム コントロール テンプレートを使用するチェックボックス。A checkbox with a custom control template. カスタム コントロール テンプレートを使用する CheckBoxA CheckBox that uses a custom control template

コントロールを作成するときに parts と states のモデルに従うと、コントロールの外観がカスタマイズ可能になります。If you follow the parts and states model when you create a control, your control's appearance will be customizable. Blend for Visual Studio などのデザイナーツールでは、パーツと状態モデルがサポートされているため、このモデルに従うと、そのような種類のアプリケーションでコントロールをカスタマイズできます。Designer tools such as Blend for Visual Studio support the parts and states model, so when you follow this model your control will be customizable in those types of applications. このトピックでは、独自のコントロールを作成するときに、パーツと状態のモデルとその実行方法について説明します。This topic discusses the parts and states model and how to follow it when you create your own control. このトピックでは、このモデルの理念を示すために、カスタムコントロール NumericUpDownの例を使用します。This topic uses an example of a custom control, NumericUpDown, to illustrate the philosophy of this model. NumericUpDown コントロールには数値が表示されます。この値は、コントロールのボタンをクリックすることによって増減できます。The NumericUpDown control displays a numeric value, which a user can increase or decrease by clicking on the control's buttons. 次の図は、このトピックで説明する NumericUpDown コントロールを示しています。The following illustration shows the NumericUpDown control that is discussed in this topic.

NumericUpDown カスタムコントロール。NumericUpDown custom control. カスタム NumericUpDown コントロールA custom NumericUpDown control

このトピックには、次のセクションが含まれます。This topic contains the following sections:

前提条件Prerequisites

このトピックでは、既存のコントロールの新しい ControlTemplate を作成する方法、コントロールコントラクトの要素について理解し、「コントロールのテンプレートを作成する」で説明されている概念を理解していることを前提としています。This topic assumes that you know how to create a new ControlTemplate for an existing control, are familiar with what the elements on a control contract are, and understand the concepts discussed in Create a template for a control.

注意

外観をカスタマイズできるコントロールを作成するには、Control クラスまたは UserControl以外のサブクラスの1つを継承するコントロールを作成する必要があります。To create a control that can have its appearance customized, you must create a control that inherits from the Control class or one of its subclasses other than UserControl. UserControl から継承するコントロールは、すばやく作成できるコントロールですが、ControlTemplate を使用せず、外観をカスタマイズすることもできません。A control that inherits from UserControl is a control that can be quickly created, but it does not use a ControlTemplate and you cannot customize its appearance.

部品と状態モデルParts and States Model

Parts and states モデルでは、コントロールの視覚的な構造と視覚的な動作を定義する方法を指定します。The parts and states model specifies how to define the visual structure and visual behavior of a control. パートと状態モデルに従うには、次の手順を実行する必要があります。To follow the parts and states model, you should do the following:

  • コントロールの ControlTemplate での視覚的な構造と視覚的な動作を定義します。Define the visual structure and visual behavior in the ControlTemplate of a control.

  • コントロールのロジックがコントロールテンプレートの一部と対話する場合は、特定のベストプラクティスに従ってください。Follow certain best practices when your control's logic interacts with parts of the control template.

  • ControlTemplateに含める内容を指定するコントロールコントラクトを提供します。Provide a control contract to specify what should be included in the ControlTemplate.

コントロールの ControlTemplate で視覚的な構造と視覚的な動作を定義すると、アプリケーションの作成者は、コードを記述する代わりに新しい ControlTemplate を作成することによって、コントロールの視覚的な構造と視覚的な動作を変更できます。When you define the visual structure and visual behavior in the ControlTemplate of a control, application authors can change the visual structure and visual behavior of your control by creating a new ControlTemplate instead of writing code. ControlTemplateで定義するオブジェクトと状態 FrameworkElement をアプリケーションの作成者に通知するコントロールコントラクトを指定する必要があります。You must provide a control contract that tells application authors which FrameworkElement objects and states should be defined in the ControlTemplate. ControlTemplate 内のパーツを操作するときは、いくつかのベストプラクティスに従う必要があります。これにより、コントロールが不完全な ControlTemplateを正しく処理できるようになります。You should follow some best practices when you interact with the parts in the ControlTemplate so that your control properly handles an incomplete ControlTemplate. これら3つの原則に従うと、アプリケーションの作成者は、WPFWPFに付属するコントロールの場合と同様に、コントロールの ControlTemplate を簡単に作成できるようになります。If you follow these three principles, application authors will be able to create a ControlTemplate for your control just as easily as they can for the controls that ship with WPFWPF. 次のセクションでは、これらの推奨事項について詳しく説明します。The following section explains each of these recommendations in detail.

ControlTemplate でのコントロールの視覚的な構造と視覚的な動作の定義Defining the Visual Structure and Visual Behavior of a Control in a ControlTemplate

Parts および states モデルを使用してカスタムコントロールを作成する場合は、コントロールの視覚的な構造と視覚的な動作をロジックではなく ControlTemplate で定義します。When you create your custom control by using the parts and states model, you define the control's visual structure and visual behavior in its ControlTemplate instead of in its logic. コントロールの視覚的な構造は、コントロールを構成する FrameworkElement オブジェクトの複合です。The visual structure of a control is the composite of FrameworkElement objects that make up the control. 視覚的な動作とは、コントロールが特定の状態にあるときのコントロールの外観です。The visual behavior is the way the control appears when it is in a certain state. コントロールの視覚的な構造と視覚動作を指定する ControlTemplate の作成の詳細については、「コントロールのテンプレートを作成する」を参照してください。For more information about creating a ControlTemplate that specifies the visual structure and visual behavior of a control, see Create a template for a control.

NumericUpDown コントロールの例では、ビジュアル構造に2つの RepeatButton コントロールと1つの TextBlockが含まれています。In the example of the NumericUpDown control, the visual structure includes two RepeatButton controls and a TextBlock. これらのコントロールを NumericUpDown コントロールのコードに追加した場合、たとえば、そのコンストラクターでは、これらのコントロールの位置は unalterable になります。If you add these controls in the code of the NumericUpDown control--in its constructor, for example--the positions of those controls would be unalterable. コントロールのビジュアル構造とビジュアル動作をコード内で定義するのではなく、ControlTemplateで定義する必要があります。Instead of defining the control's visual structure and visual behavior in its code, you should define it in the ControlTemplate. 次に、ボタンと TextBlock の位置をカスタマイズし、ControlTemplate を置き換えることができるので Value が負の場合に発生する動作を指定します。Then an application developer to customize the position of the buttons and TextBlock and specify what behavior occurs when Value is negative because the ControlTemplate can be replaced.

次の例は、NumericUpDown コントロールの視覚的な構造を示しています。これには Valueを増やすための RepeatButtonValueを減らすための RepeatButtonTextBlock を表示するための Valueが含まれています。The following example shows the visual structure of the NumericUpDown control, which includes a RepeatButton to increase Value, a RepeatButton to decrease Value, and a TextBlock to display Value.

<ControlTemplate TargetType="src:NumericUpDown">
  <Grid  Margin="3" 
         Background="{TemplateBinding Background}">
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
      </Grid.ColumnDefinitions>

      <Border BorderThickness="1" BorderBrush="Gray" 
              Margin="7,2,2,2" Grid.RowSpan="2" 
              Background="#E0FFFFFF"
              VerticalAlignment="Center" 
              HorizontalAlignment="Stretch">

        <!--Bind the TextBlock to the Value property-->
        <TextBlock Name="TextBlock"
                   Width="60" TextAlignment="Right" Padding="5"
                   Text="{Binding RelativeSource={RelativeSource FindAncestor, 
                     AncestorType={x:Type src:NumericUpDown}}, 
                     Path=Value}"/>
      </Border>

      <RepeatButton Content="Up" Margin="2,5,5,0"
        Name="UpButton"
        Grid.Column="1" Grid.Row="0"/>
      <RepeatButton Content="Down" Margin="2,0,5,5"
        Name="DownButton"
        Grid.Column="1" Grid.Row="1"/>

      <Rectangle Name="FocusVisual" Grid.ColumnSpan="2" Grid.RowSpan="2" 
        Stroke="Black" StrokeThickness="1"  
        Visibility="Collapsed"/>
    </Grid>

  </Grid>
</ControlTemplate>

NumericUpDown コントロールの視覚的な動作は、値が負の場合に赤いフォントで表示されることです。A visual behavior of the NumericUpDown control is that the value is in a red font if it is negative. Value が負の場合にコード内の TextBlockForeground を変更すると、NumericUpDown には常に赤色の負の値が表示されます。If you change the Foreground of the TextBlock in code when the Value is negative, the NumericUpDown will always show a red negative value. ControlTemplateVisualState オブジェクトを追加することによって、ControlTemplate でのコントロールの視覚的な動作を指定します。You specify the visual behavior of the control in the ControlTemplate by adding VisualState objects to the ControlTemplate. 次の例は、PositiveNegative の状態の VisualState オブジェクトを示しています。The following example shows the VisualState objects for the Positive and Negative states. PositiveNegative は相互に排他的です (コントロールは常に2つのうちの1つになります)。そのため、この例では、VisualState オブジェクトを1つの VisualStateGroupに配置します。Positive and Negative are mutually exclusive (the control is always in exactly one of the two), so the example puts the VisualState objects into a single VisualStateGroup. コントロールが Negative 状態になると、TextBlockForeground が赤に変わります。When the control goes into the Negative state, the Foreground of the TextBlock turns red. コントロールが Positive 状態の場合、Foreground は元の値に戻ります。When the control is in the Positive state, the Foreground returns to its original value. ControlTemplate での VisualState オブジェクトの定義については、「コントロールのテンプレートを作成する」で詳しく説明します。Defining VisualState objects in a ControlTemplate is further discussed in Create a template for a control.

注意

ControlTemplateのルート FrameworkElement には、VisualStateGroups 添付プロパティを必ず設定してください。Be sure to set the VisualStateGroups attached property on the root FrameworkElement of the ControlTemplate.

<ControlTemplate TargetType="local:NumericUpDown">
  <Grid  Margin="3" 
         Background="{TemplateBinding Background}">

    <VisualStateManager.VisualStateGroups>
      <VisualStateGroup Name="ValueStates">

        <!--Make the Value property red when it is negative.-->
        <VisualState Name="Negative">
          <Storyboard>
            <ColorAnimation To="Red"
              Storyboard.TargetName="TextBlock" 
              Storyboard.TargetProperty="(Foreground).(Color)"/>
          </Storyboard>

        </VisualState>

        <!--Return the TextBlock's Foreground to its 
            original color.-->
        <VisualState Name="Positive"/>
      </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
  </Grid>
</ControlTemplate>

コードでの ControlTemplate の一部の使用Using Parts of the ControlTemplate in Code

ControlTemplate 作成者は、意図的にまたは誤って、FrameworkElement または VisualState オブジェクトを省略する場合がありますが、コントロールのロジックでは、これらの部分が正しく機能するために必要になる場合があります。A ControlTemplate author might omit FrameworkElement or VisualState objects, either purposefully or by mistake, but your control's logic might need those parts to function properly. Parts および states モデルは、FrameworkElement オブジェクトまたは VisualState オブジェクトが不足している ControlTemplate に対して、コントロールの回復性を備えていることを指定します。The parts and states model specifies that your control should be resilient to a ControlTemplate that is missing FrameworkElement or VisualState objects. FrameworkElementVisualState、または VisualStateGroupControlTemplateにない場合は、コントロールで例外をスローしたり、エラーを報告したりしないでください。Your control should not throw an exception or report an error if a FrameworkElement, VisualState, or VisualStateGroup is missing from the ControlTemplate. ここでは、FrameworkElement オブジェクトとの対話と状態の管理について推奨される運用方法について説明します。This section describes the recommended practices for interacting with FrameworkElement objects and managing states.

不足している FrameworkElement オブジェクトの予測Anticipate Missing FrameworkElement Objects

ControlTemplateFrameworkElement オブジェクトを定義する場合は、コントロールのロジックがその一部と対話する必要がある場合があります。When you define FrameworkElement objects in the ControlTemplate, your control's logic might need to interact with some of them. たとえば、NumericUpDown コントロールは、ボタンの Click イベントをサブスクライブして Value を増減し、TextBlockText プロパティを Valueに設定します。For example, the NumericUpDown control subscribes to the buttons' Click event to increase or decrease Value and sets the Text property of the TextBlock to Value. カスタム ControlTemplateTextBlock またはボタンが省略されている場合は、コントロールがその機能の一部を失うことは許容されますが、コントロールでエラーが発生しないことを確認する必要があります。If a custom ControlTemplate omits the TextBlock or buttons, it is acceptable that the control loses some of its functionality, but you should be sure that your control does not cause an error. たとえば、ControlTemplateValueを変更するボタンが含まれていない場合、NumericUpDown はその機能を失いますが、ControlTemplate を使用するアプリケーションは引き続き実行されます。For example, if a ControlTemplate does not contain the buttons to change Value, the NumericUpDown loses that functionality, but an application that uses the ControlTemplate will continue to run.

次の方法を使用すると、コントロールが不足している FrameworkElement オブジェクトに正しく応答するようになります。The following practices will ensure that your control responds properly to missing FrameworkElement objects:

  1. コードで参照する必要のある各 FrameworkElementx:Name 属性を設定します。Set the x:Name attribute for each FrameworkElement that you need to reference in code.

  2. 操作する必要がある各 FrameworkElement のプライベートプロパティを定義します。Define private properties for each FrameworkElement that you need to interact with.

  3. FrameworkElement プロパティの set アクセサーで、コントロールが処理するすべてのイベントをサブスクライブおよびサブスクライブ解除します。Subscribe to and unsubscribe from any events that your control handles in the FrameworkElement property's set accessor.

  4. OnApplyTemplate メソッドで、手順 2. で定義した FrameworkElement のプロパティを設定します。Set the FrameworkElement properties that you defined in step 2 in the OnApplyTemplate method. これは、ControlTemplate 内の FrameworkElement をコントロールで使用できる最も早い段階です。This is the earliest that the FrameworkElement in the ControlTemplate is available to the control. ControlTemplateから取得するには、FrameworkElementx:Name を使用します。Use the x:Name of the FrameworkElement to get it from the ControlTemplate.

  5. メンバーにアクセスする前に、FrameworkElementnull ていないことを確認してください。Check that the FrameworkElement is not null before accessing its members. null場合は、エラーを報告しないでください。If it is null, do not report an error.

次の例は、前の一覧の推奨事項に従って、NumericUpDown コントロールが FrameworkElement オブジェクトとどのように対話するかを示しています。The following examples show how the NumericUpDown control interacts with FrameworkElement objects in accordance with the recommendations in the preceding list.

ControlTemplate内の NumericUpDown コントロールのビジュアル構造を定義する例では、Value を向上させる RepeatButtonx:Name 属性が UpButtonに設定されています。In the example that defines the visual structure of the NumericUpDown control in the ControlTemplate, the RepeatButton that increases Value has its x:Name attribute set to UpButton. 次の例では、ControlTemplateで宣言されている RepeatButton を表す UpButtonElement という名前のプロパティを宣言しています。The following example declares a property called UpButtonElement that represents the RepeatButton that is declared in the ControlTemplate. set アクセサーは、最初にボタンの Click イベントにアンサブスクライブします。 UpDownElementnullされていない場合は、プロパティが設定され、次に Click イベントがサブスクライブされます。The set accessor first unsubscribes to the button's Click event if UpDownElement is not null, then it sets the property, and then it subscribes to the Click event. また、プロパティも定義されていますが、ここには示されていませんが、DownButtonElementと呼ばれる他の RepeatButtonについても説明しています。There is also a property defined, but not shown here, for the other RepeatButton, called DownButtonElement.

private RepeatButton upButtonElement;

private RepeatButton UpButtonElement
{
    get
    {
        return upButtonElement;
    }

    set
    {
        if (upButtonElement != null)
        {
            upButtonElement.Click -=
                new RoutedEventHandler(upButtonElement_Click);
        }
        upButtonElement = value;

        if (upButtonElement != null)
        {
            upButtonElement.Click +=
                new RoutedEventHandler(upButtonElement_Click);
        }
    }
}
Private m_upButtonElement As RepeatButton

Private Property UpButtonElement() As RepeatButton
    Get
        Return m_upButtonElement
    End Get

    Set(ByVal value As RepeatButton)
        If m_upButtonElement IsNot Nothing Then
            RemoveHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
        End If
        m_upButtonElement = value

        If m_upButtonElement IsNot Nothing Then
            AddHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
        End If
    End Set
End Property

次の例は、NumericUpDown コントロールの OnApplyTemplate を示しています。The following example shows the OnApplyTemplate for the NumericUpDown control. この例では、GetTemplateChild メソッドを使用して、ControlTemplateから FrameworkElement オブジェクトを取得します。The example uses the GetTemplateChild method to get the FrameworkElement objects from the ControlTemplate. この例では、指定した名前の FrameworkElement が予期される型ではない場合に、GetTemplateChild が検出されることに注意してください。Notice that the example guards against cases where GetTemplateChild finds a FrameworkElement with the specified name that is not of the expected type. また、指定した x:Name を持つが、型が間違っている要素は無視することをお勧めします。It is also a best practice to ignore elements that have the specified x:Name but are of the wrong type.

public override void OnApplyTemplate()
{
    UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
    DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
    //TextElement = GetTemplateChild("TextBlock") as TextBlock;

    UpdateStates(false);
}
Public Overloads Overrides Sub OnApplyTemplate()

    UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
    DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)

    UpdateStates(False)
End Sub

前の例で示した手順に従うことで、ControlTemplateFrameworkElementがない場合に、コントロールが引き続き実行されるようにすることができます。By following the practices that are shown in the previous examples, you ensure that your control will continue to run when the ControlTemplate is missing a FrameworkElement.

VisualStateManager を使用して状態を管理するUse the VisualStateManager to Manage States

VisualStateManager は、コントロールの状態を追跡し、状態間の遷移に必要なロジックを実行します。The VisualStateManager keeps track of the states of a control and performs the logic necessary to transition between states. ControlTemplateVisualState オブジェクトを追加する場合は、それらを VisualStateGroup に追加し、VisualStateGroups にアクセスできるように VisualStateManager 添付プロパティに VisualStateGroup を追加します。When you add VisualState objects to the ControlTemplate, you add them to a VisualStateGroup and add the VisualStateGroup to the VisualStateGroups attached property so that the VisualStateManager has access to them.

次の例では、前の例を繰り返して、コントロールの PositiveNegative の状態に対応する VisualState オブジェクトを示します。The following example repeats the previous example that shows the VisualState objects that correspond to the Positive and Negative states of the control. NegativeVisualStateStoryboard により、TextBlock 赤の Foreground が変わります。The Storyboard in the NegativeVisualState turns the Foreground of the TextBlock red. NumericUpDown コントロールが Negative 状態になると、Negative 状態のストーリーボードが開始されます。When the NumericUpDown control is in the Negative state, the storyboard in the Negative state begins. その後、コントロールが Positive 状態に戻ったときに、Negative の状態の Storyboard が停止します。Then the Storyboard in the Negative state stops when the control returns to the Positive state. PositiveVisualState には Storyboard を含める必要はありません。これは、NegativeStoryboard が停止したときに、Foreground が元の色に戻るためです。The PositiveVisualState does not need to contain a Storyboard because when the Storyboard for the Negative stops, the Foreground returns to its original color.

<ControlTemplate TargetType="local:NumericUpDown">
  <Grid  Margin="3" 
         Background="{TemplateBinding Background}">

    <VisualStateManager.VisualStateGroups>
      <VisualStateGroup Name="ValueStates">

        <!--Make the Value property red when it is negative.-->
        <VisualState Name="Negative">
          <Storyboard>
            <ColorAnimation To="Red"
              Storyboard.TargetName="TextBlock" 
              Storyboard.TargetProperty="(Foreground).(Color)"/>
          </Storyboard>

        </VisualState>

        <!--Return the TextBlock's Foreground to its 
            original color.-->
        <VisualState Name="Positive"/>
      </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
  </Grid>
</ControlTemplate>

TextBlock には名前が指定されていますが、コントロールのロジックが TextBlockを参照しないため、TextBlockNumericUpDown の制御コントラクトに含まれていません。Note that the TextBlock is given a name, but the TextBlock is not in the control contract for NumericUpDown because the control's logic never references the TextBlock. ControlTemplate で参照される要素には名前がありますが、コントロールの新しい ControlTemplate でその要素を参照する必要がない場合があるため、コントロールコントラクトに含める必要はありません。Elements that are referenced in the ControlTemplate have names, but do not need to be part of the control contract because a new ControlTemplate for the control might not need to reference that element. たとえば、NumericUpDown の新しい ControlTemplate を作成するユーザーは、Foregroundを変更することによって Value が負であることを示すことがない場合があります。For example, someone who creates a new ControlTemplate for NumericUpDown might decide to not indicate that Value is negative by changing the Foreground. この場合、コードと ControlTemplate はどちらも名前で TextBlock を参照しません。In that case, neither the code nor the ControlTemplate references the TextBlock by name.

コントロールのロジックは、コントロールの状態を変更する役割を担います。The control's logic is responsible for changing the control's state. 次の例では、NumericUpDown コントロールが GoToState メソッドを呼び出して、Value が0以上の場合に Positive 状態に移行し、Negative が0未満の場合は Value 状態になることを示します。The following example shows that the NumericUpDown control calls the GoToState method to go into the Positive state when Value is 0 or greater, and the Negative state when Value is less than 0.

if (Value >= 0)
{
    VisualStateManager.GoToState(this, "Positive", useTransitions);
}
else
{
    VisualStateManager.GoToState(this, "Negative", useTransitions);
}
If Value >= 0 Then
    VisualStateManager.GoToState(Me, "Positive", useTransitions)
Else
    VisualStateManager.GoToState(Me, "Negative", useTransitions)
End If

GoToState メソッドは、ストーリーボードを適切に開始および停止するために必要なロジックを実行します。The GoToState method performs the logic necessary to start and stop the storyboards appropriately. コントロールが GoToState を呼び出してその状態を変更すると、VisualStateManager は次の操作を実行します。When a control calls GoToState to change its state, the VisualStateManager does the following:

  • コントロールが移動する VisualStateStoryboardがある場合は、ストーリーボードが開始されます。If the VisualState that the control is going to has a Storyboard, the storyboard begins. 次に、コントロールの送信元の VisualStateStoryboardがある場合は、ストーリーボードが終了します。Then, if the VisualState that the control is coming from has a Storyboard, the storyboard ends.

  • コントロールが既に指定されている状態にある場合、GoToState は何も処理を行わず、trueを返します。If the control is already in the state that is specified, GoToState takes no action and returns true.

  • 指定された状態が controlControlTemplate に存在しない場合、GoToState はアクションを実行せず、falseを返します。If state that is specified doesn't exist in the ControlTemplate of control, GoToState takes no action and returns false.

VisualStateManager を使用するためのベストプラクティスBest Practices for Working with the VisualStateManager

コントロールの状態を維持するには、次の操作を行うことをお勧めします。It is recommended that you do the following to maintain your control's states:

  • プロパティを使用して、状態を追跡します。Use properties to track its state.

  • 状態を切り替えるためのヘルパーメソッドを作成します。Create a helper method to transition between states.

NumericUpDown コントロールでは、Value プロパティを使用して、Positive または Negative 状態であるかどうかを追跡します。The NumericUpDown control uses its Value property to track whether it is in the Positive or Negative state. また、NumericUpDown コントロールは、IsFocused プロパティを追跡する FocusedUnFocused の状態も定義します。The NumericUpDown control also defines the Focused and UnFocused states, which tracks the IsFocused property. 自然にコントロールのプロパティに対応していない状態を使用する場合は、プライベートプロパティを定義して状態を追跡できます。If you use states that do not naturally correspond to a property of the control, you can define a private property to track the state.

すべての状態を更新する1つのメソッドは、VisualStateManager の呼び出しを一元化し、コードを管理しやすくします。A single method that updates all the states centralizes calls to the VisualStateManager and keeps your code manageable. 次の例は、NumericUpDown コントロールのヘルパーメソッドである UpdateStatesを示しています。The following example shows the NumericUpDown control's helper method, UpdateStates. Value が0以上の場合、ControlPositive 状態になります。When Value is greater than or equal to 0, the Control is in the Positive state. Value が0未満の場合、コントロールは Negative 状態になります。When Value is less than 0, the control is in the Negative state. IsFocusedtrue場合、コントロールは Focused の状態になります。それ以外の場合は、Unfocused 状態になります。When IsFocused is true, the control is in the Focused state; otherwise, it is in the Unfocused state. コントロールは、状態の変化に関係なく、状態を変更する必要があるときに UpdateStates を呼び出すことができます。The control can call UpdateStates whenever it needs to change its state, regardless of what state changes.

private void UpdateStates(bool useTransitions)
{
    if (Value >= 0)
    {
        VisualStateManager.GoToState(this, "Positive", useTransitions);
    }
    else
    {
        VisualStateManager.GoToState(this, "Negative", useTransitions);
    }

    if (IsFocused)
    {
        VisualStateManager.GoToState(this, "Focused", useTransitions);
    }
    else
    {
        VisualStateManager.GoToState(this, "Unfocused", useTransitions);
    }
}
Private Sub UpdateStates(ByVal useTransitions As Boolean)

    If Value >= 0 Then
        VisualStateManager.GoToState(Me, "Positive", useTransitions)
    Else
        VisualStateManager.GoToState(Me, "Negative", useTransitions)
    End If

    If IsFocused Then
        VisualStateManager.GoToState(Me, "Focused", useTransitions)
    Else
        VisualStateManager.GoToState(Me, "Unfocused", useTransitions)

    End If
End Sub

コントロールが既にその状態にあるときに状態名を GoToState に渡すと、GoToState は何も行われないため、コントロールの現在の状態を確認する必要はありません。If you pass a state name to GoToState when the control is already in that state, GoToState does nothing, so you don't need to check for the control's current state. たとえば、負の数値から別の負の数値に変更し Value と、Negative 状態のストーリーボードは中断されず、ユーザーはコントロールの変更を確認できません。For example, if Value changes from one negative number to another negative number, the storyboard for the Negative state is not interrupted and the user will not see a change in the control.

VisualStateManagerVisualStateGroup オブジェクトを使用して、GoToStateを呼び出すときに終了する状態を判断します。The VisualStateManager uses VisualStateGroup objects to determine which state to exit when you call GoToState. コントロールは、ControlTemplate で定義されている VisualStateGroup ごとに常に1つの状態になり、同じ VisualStateGroupから別の状態になったときにのみ状態が保持されます。The control is always in one state for each VisualStateGroup that is defined in its ControlTemplate and only leaves a state when it goes into another state from the same VisualStateGroup. たとえば、NumericUpDown コントロールの ControlTemplate では、PositiveNegativeVisualState オブジェクトを1つの VisualStateGroup に定義し、Focused オブジェクトと Unfocusedオブジェクトを別の VisualState にします。For example, the ControlTemplate of the NumericUpDown control defines the Positive and NegativeVisualState objects in one VisualStateGroup and the Focused and UnfocusedVisualState objects in another. (Focused および UnfocusedVisualState は、このトピックの「完全な例」セクションで定義されています。コントロールが Positive 状態から Negative 状態になったとき、またはその逆の場合、コントロールは Focused 状態または Unfocused 状態のままになります。(You can see the Focused and UnfocusedVisualState defined in the Complete Example section in this topic When the control goes from the Positive state to the Negative state, or vice versa, the control remains in either the Focused or Unfocused state.

コントロールの状態が変わる可能性がある一般的な場所には、次の3つがあります。There are three typical places where the state of a control might change:

  • ControlTemplateControlに適用される場合。When the ControlTemplate is applied to the Control.

  • プロパティが変更したとき。When a property changes.

  • イベントが発生したとき。When an event occurs.

次の例は、このような場合に NumericUpDown コントロールの状態を更新する方法を示しています。The following examples demonstrate updating the state of the NumericUpDown control in these cases.

ControlTemplate が適用されたときにコントロールが正しい状態で表示されるように、OnApplyTemplate メソッドでコントロールの状態を更新する必要があります。You should update the state of the control in the OnApplyTemplate method so that the control appears in the correct state when the ControlTemplate is applied. 次の例では、OnApplyTemplateUpdateStates を呼び出して、コントロールが適切な状態であることを確認します。The following example calls UpdateStates in OnApplyTemplate to ensure that the control is in the appropriate states. たとえば、NumericUpDown コントロールを作成し、その Foreground を緑色に、Value を-5 に設定したとします。For example, suppose that you create a NumericUpDown control, and then set its Foreground to green and Value to -5. ControlTemplateNumericUpDown コントロールに適用されているときに UpdateStates を呼び出さない場合、コントロールは Negative 状態ではなく、値は赤ではなく緑色になります。If you do not call UpdateStates when the ControlTemplate is applied to the NumericUpDown control, the control is not in the Negative state and the value is green instead of red. コントロールを Negative 状態にするには、UpdateStates を呼び出す必要があります。You must call UpdateStates to put the control in the Negative state.

public override void OnApplyTemplate()
{
    UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
    DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
    //TextElement = GetTemplateChild("TextBlock") as TextBlock;

    UpdateStates(false);
}
Public Overloads Overrides Sub OnApplyTemplate()

    UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
    DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)

    UpdateStates(False)
End Sub

プロパティが変更されたときに、多くの場合、コントロールの状態を更新する必要があります。You often need to update the states of a control when a property changes. 次の例は ValueChangedCallback メソッド全体を示しています。The following example shows the entire ValueChangedCallback method. Value が変更されると ValueChangedCallback が呼び出されるため、メソッドは Value を正の値から負の値に変更した場合、またはその逆の場合に UpdateStates を呼び出します。Because ValueChangedCallback is called when Value changes, the method calls UpdateStates in case Value changed from positive to negative or vice versa. Value 変更されても、正または負のままである場合は、UpdateStates を呼び出すことができます。その場合、コントロールは状態を変更しないためです。It is acceptable to call UpdateStates when Value changes but remains positive or negative because in that case, the control will not change states.

private static void ValueChangedCallback(DependencyObject obj, 
    DependencyPropertyChangedEventArgs args)
{
    NumericUpDown ctl = (NumericUpDown)obj;
    int newValue = (int)args.NewValue;

    // Call UpdateStates because the Value might have caused the
    // control to change ValueStates.
    ctl.UpdateStates(true);

    // Call OnValueChanged to raise the ValueChanged event.
    ctl.OnValueChanged(
        new ValueChangedEventArgs(NumericUpDown.ValueChangedEvent, 
            newValue));
}
Private Shared Sub ValueChangedCallback(ByVal obj As DependencyObject,
                                        ByVal args As DependencyPropertyChangedEventArgs)

    Dim ctl As NumericUpDown = DirectCast(obj, NumericUpDown)
    Dim newValue As Integer = CInt(args.NewValue)

    ' Call UpdateStates because the Value might have caused the
    ' control to change ValueStates.
    ctl.UpdateStates(True)

    ' Call OnValueChanged to raise the ValueChanged event.
    ctl.OnValueChanged(New ValueChangedEventArgs(NumericUpDown.ValueChangedEvent, newValue))
End Sub

また、イベントが発生したときに状態を更新することが必要になる場合もあります。You might also need to update states when an event occurs. 次の例は、NumericUpDownGotFocus イベントを処理するために ControlUpdateStates を呼び出すことを示しています。The following example shows that the NumericUpDown calls UpdateStates on the Control to handle the GotFocus event.

protected override void OnGotFocus(RoutedEventArgs e)
{
    base.OnGotFocus(e);
    UpdateStates(true);
}
Protected Overloads Overrides Sub OnGotFocus(ByVal e As RoutedEventArgs)
    MyBase.OnGotFocus(e)
    UpdateStates(True)
End Sub

VisualStateManager は、コントロールの状態を管理するのに役立ちます。The VisualStateManager helps you manage your control's states. VisualStateManagerを使用すると、コントロールが状態間で正しく遷移するようになります。By using the VisualStateManager, you ensure that your control correctly transitions between states. このセクションに記載されている推奨事項に従って VisualStateManagerを操作すると、コントロールのコードは読みやすく、保守も容易になります。If you follow the recommendations described in this section for working with the VisualStateManager, your control's code will remain readable and maintainable.

コントロールコントラクトの提供Providing the Control Contract

テンプレートに配置する内容を ControlTemplate の作成者が認識できるように、コントロールコントラクトを提供します。You provide a control contract so that ControlTemplate authors will know what to put in the template. コントロール コントラクトには、次の 3 つの要素があります。A control contract has three elements:

  • コントロールのロジックが使用する視覚的要素。The visual elements that the control's logic uses.

  • コントロールの状態および各状態が所属するグループ。The states of the control and the group each state belongs to.

  • コントロールに対して視覚的に作用するパブリック プロパティ。The public properties that visually affect the control.

新しい ControlTemplate を作成するユーザーは、コントロールのロジックで使用される FrameworkElement オブジェクト、各オブジェクトの種類、およびその名前を知る必要があります。Someone that creates a new ControlTemplate needs to know what FrameworkElement objects the control's logic uses, what type each object is, and what its name is. また、ControlTemplate 作成者は、コントロールが持つことができる各状態の名前と、状態の VisualStateGroup を把握している必要があります。A ControlTemplate author also needs to know the name of each possible state the control can be in, and which VisualStateGroup the state is in.

NumericUpDown の例に戻るために、コントロールは、ControlTemplate に次の FrameworkElement オブジェクトがあることを想定しています。Returning to the NumericUpDown example, the control expects the ControlTemplate to have the following FrameworkElement objects:

コントロールは、次の状態になります。The control can be in the following states:

コントロールが必要とする FrameworkElement オブジェクトを指定するには、予期される要素の名前と型を指定する TemplatePartAttributeを使用します。To specify what FrameworkElement objects the control expects, you use the TemplatePartAttribute, which specifies the name and type of the expected elements. コントロールの状態を指定するには、TemplateVisualStateAttributeを使用します。これは、状態の名前と、それが属する VisualStateGroup を指定します。To specify the possible states of a control, you use the TemplateVisualStateAttribute, which specifies the state's name and which VisualStateGroup it belongs to. TemplatePartAttributeTemplateVisualStateAttribute をコントロールのクラス定義に配置します。Put the TemplatePartAttribute and TemplateVisualStateAttribute on the class definition of the control.

コントロールの外観に影響するすべてのパブリックプロパティは、コントロールコントラクトの一部でもあります。Any public property that affects the appearance of your control is also a part of the control contract.

次の例では、NumericUpDown コントロールの FrameworkElement オブジェクトと状態を指定しています。The following example specifies the FrameworkElement object and states for the NumericUpDown control.

[TemplatePart(Name = "UpButtonElement", Type = typeof(RepeatButton))]
[TemplatePart(Name = "DownButtonElement", Type = typeof(RepeatButton))]
[TemplateVisualState(Name = "Positive", GroupName = "ValueStates")]
[TemplateVisualState(Name = "Negative", GroupName = "ValueStates")]
[TemplateVisualState(Name = "Focused", GroupName = "FocusedStates")]
[TemplateVisualState(Name = "Unfocused", GroupName = "FocusedStates")]
public class NumericUpDown : Control
{
    public static readonly DependencyProperty BackgroundProperty;
    public static readonly DependencyProperty BorderBrushProperty;
    public static readonly DependencyProperty BorderThicknessProperty;
    public static readonly DependencyProperty FontFamilyProperty;
    public static readonly DependencyProperty FontSizeProperty;
    public static readonly DependencyProperty FontStretchProperty;
    public static readonly DependencyProperty FontStyleProperty;
    public static readonly DependencyProperty FontWeightProperty;
    public static readonly DependencyProperty ForegroundProperty;
    public static readonly DependencyProperty HorizontalContentAlignmentProperty;
    public static readonly DependencyProperty PaddingProperty;
    public static readonly DependencyProperty TextAlignmentProperty;
    public static readonly DependencyProperty TextDecorationsProperty;
    public static readonly DependencyProperty TextWrappingProperty;
    public static readonly DependencyProperty VerticalContentAlignmentProperty;

    public Brush Background { get; set; }
    public Brush BorderBrush { get; set; }
    public Thickness BorderThickness { get; set; }
    public FontFamily FontFamily { get; set; }
    public double FontSize { get; set; }
    public FontStretch FontStretch { get; set; }
    public FontStyle FontStyle { get; set; }
    public FontWeight FontWeight { get; set; }
    public Brush Foreground { get; set; }
    public HorizontalAlignment HorizontalContentAlignment { get; set; }
    public Thickness Padding { get; set; }
    public TextAlignment TextAlignment { get; set; }
    public TextDecorationCollection TextDecorations { get; set; }
    public TextWrapping TextWrapping { get; set; }
    public VerticalAlignment VerticalContentAlignment { get; set; }
}
<TemplatePart(Name:="UpButtonElement", Type:=GetType(RepeatButton))> _
<TemplatePart(Name:="DownButtonElement", Type:=GetType(RepeatButton))> _
<TemplateVisualState(Name:="Positive", GroupName:="ValueStates")> _
<TemplateVisualState(Name:="Negative", GroupName:="ValueStates")> _
<TemplateVisualState(Name:="Focused", GroupName:="FocusedStates")> _
<TemplateVisualState(Name:="Unfocused", GroupName:="FocusedStates")> _
Public Class NumericUpDown
    Inherits Control
    Public Shared ReadOnly BackgroundProperty As DependencyProperty
    Public Shared ReadOnly BorderBrushProperty As DependencyProperty
    Public Shared ReadOnly BorderThicknessProperty As DependencyProperty
    Public Shared ReadOnly FontFamilyProperty As DependencyProperty
    Public Shared ReadOnly FontSizeProperty As DependencyProperty
    Public Shared ReadOnly FontStretchProperty As DependencyProperty
    Public Shared ReadOnly FontStyleProperty As DependencyProperty
    Public Shared ReadOnly FontWeightProperty As DependencyProperty
    Public Shared ReadOnly ForegroundProperty As DependencyProperty
    Public Shared ReadOnly HorizontalContentAlignmentProperty As DependencyProperty
    Public Shared ReadOnly PaddingProperty As DependencyProperty
    Public Shared ReadOnly TextAlignmentProperty As DependencyProperty
    Public Shared ReadOnly TextDecorationsProperty As DependencyProperty
    Public Shared ReadOnly TextWrappingProperty As DependencyProperty
    Public Shared ReadOnly VerticalContentAlignmentProperty As DependencyProperty


    Private _Background As Brush
    Public Property Background() As Brush
        Get
            Return _Background
        End Get
        Set(ByVal value As Brush)
            _Background = value
        End Set
    End Property

    Private _BorderBrush As Brush
    Public Property BorderBrush() As Brush
        Get
            Return _BorderBrush
        End Get
        Set(ByVal value As Brush)
            _BorderBrush = value
        End Set
    End Property

    Private _BorderThickness As Thickness
    Public Property BorderThickness() As Thickness
        Get
            Return _BorderThickness
        End Get
        Set(ByVal value As Thickness)
            _BorderThickness = value
        End Set
    End Property

    Private _FontFamily As FontFamily
    Public Property FontFamily() As FontFamily
        Get
            Return _FontFamily
        End Get
        Set(ByVal value As FontFamily)
            _FontFamily = value
        End Set
    End Property

    Private _FontSize As Double
    Public Property FontSize() As Double
        Get
            Return _FontSize
        End Get
        Set(ByVal value As Double)
            _FontSize = value
        End Set
    End Property

    Private _FontStretch As FontStretch
    Public Property FontStretch() As FontStretch
        Get
            Return _FontStretch
        End Get
        Set(ByVal value As FontStretch)
            _FontStretch = value
        End Set
    End Property

    Private _FontStyle As FontStyle
    Public Property FontStyle() As FontStyle
        Get
            Return _FontStyle
        End Get
        Set(ByVal value As FontStyle)
            _FontStyle = value
        End Set
    End Property

    Private _FontWeight As FontWeight
    Public Property FontWeight() As FontWeight
        Get
            Return _FontWeight
        End Get
        Set(ByVal value As FontWeight)
            _FontWeight = value
        End Set
    End Property

    Private _Foreground As Brush
    Public Property Foreground() As Brush
        Get
            Return _Foreground
        End Get
        Set(ByVal value As Brush)
            _Foreground = value
        End Set
    End Property

    Private _HorizontalContentAlignment As HorizontalAlignment
    Public Property HorizontalContentAlignment() As HorizontalAlignment
        Get
            Return _HorizontalContentAlignment
        End Get
        Set(ByVal value As HorizontalAlignment)
            _HorizontalContentAlignment = value
        End Set
    End Property

    Private _Padding As Thickness
    Public Property Padding() As Thickness
        Get
            Return _Padding
        End Get
        Set(ByVal value As Thickness)
            _Padding = value
        End Set
    End Property

    Private _TextAlignment As TextAlignment
    Public Property TextAlignment() As TextAlignment
        Get
            Return _TextAlignment
        End Get
        Set(ByVal value As TextAlignment)
            _TextAlignment = value
        End Set
    End Property

    Private _TextDecorations As TextDecorationCollection
    Public Property TextDecorations() As TextDecorationCollection
        Get
            Return _TextDecorations
        End Get
        Set(ByVal value As TextDecorationCollection)
            _TextDecorations = value
        End Set
    End Property

    Private _TextWrapping As TextWrapping
    Public Property TextWrapping() As TextWrapping
        Get
            Return _TextWrapping
        End Get
        Set(ByVal value As TextWrapping)
            _TextWrapping = value
        End Set
    End Property

    Private _VerticalContentAlignment As VerticalAlignment
    Public Property VerticalContentAlignment() As VerticalAlignment
        Get
            Return _VerticalContentAlignment
        End Get
        Set(ByVal value As VerticalAlignment)
            _VerticalContentAlignment = value
        End Set
    End Property
End Class

コード例全体Complete Example

次の例は、NumericUpDown コントロールの ControlTemplate 全体を示しています。The following example is the entire ControlTemplate for the NumericUpDown control.

<!--This is the contents of the themes/generic.xaml file.-->
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:VSMCustomControl">


  <Style TargetType="{x:Type local:NumericUpDown}">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="local:NumericUpDown">
          <Grid  Margin="3" 
                Background="{TemplateBinding Background}">


            <VisualStateManager.VisualStateGroups>

              <VisualStateGroup Name="ValueStates">

                <!--Make the Value property red when it is negative.-->
                <VisualState Name="Negative">
                  <Storyboard>
                    <ColorAnimation To="Red"
                      Storyboard.TargetName="TextBlock" 
                      Storyboard.TargetProperty="(Foreground).(Color)"/>
                  </Storyboard>

                </VisualState>

                <!--Return the control to its initial state by
                    return the TextBlock's Foreground to its 
                    original color.-->
                <VisualState Name="Positive"/>
              </VisualStateGroup>

              <VisualStateGroup Name="FocusStates">

                <!--Add a focus rectangle to highlight the entire control
                    when it has focus.-->
                <VisualState Name="Focused">
                  <Storyboard>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusVisual" 
                                                   Storyboard.TargetProperty="Visibility" Duration="0">
                      <DiscreteObjectKeyFrame KeyTime="0">
                        <DiscreteObjectKeyFrame.Value>
                          <Visibility>Visible</Visibility>
                        </DiscreteObjectKeyFrame.Value>
                      </DiscreteObjectKeyFrame>
                    </ObjectAnimationUsingKeyFrames>
                  </Storyboard>
                </VisualState>

                <!--Return the control to its initial state by
                    hiding the focus rectangle.-->
                <VisualState Name="Unfocused"/>
              </VisualStateGroup>

            </VisualStateManager.VisualStateGroups>

            <Grid>
              <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
              </Grid.RowDefinitions>
              <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
              </Grid.ColumnDefinitions>

              <Border BorderThickness="1" BorderBrush="Gray" 
                Margin="7,2,2,2" Grid.RowSpan="2" 
                Background="#E0FFFFFF"
                VerticalAlignment="Center" 
                HorizontalAlignment="Stretch">
                <!--Bind the TextBlock to the Value property-->
                <TextBlock Name="TextBlock"
                  Width="60" TextAlignment="Right" Padding="5"
                  Text="{Binding RelativeSource={RelativeSource FindAncestor, 
                                 AncestorType={x:Type local:NumericUpDown}}, 
                                 Path=Value}"/>
              </Border>

              <RepeatButton Content="Up" Margin="2,5,5,0"
                Name="UpButton"
                Grid.Column="1" Grid.Row="0"/>
              <RepeatButton Content="Down" Margin="2,0,5,5"
                Name="DownButton"
                Grid.Column="1" Grid.Row="1"/>

              <Rectangle Name="FocusVisual" Grid.ColumnSpan="2" Grid.RowSpan="2" 
                Stroke="Black" StrokeThickness="1"  
                Visibility="Collapsed"/>
            </Grid>

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

次の例は、NumericUpDownのロジックを示しています。The following example shows the logic for the NumericUpDown.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;

namespace VSMCustomControl
{
    [TemplatePart(Name = "UpButtonElement", Type = typeof(RepeatButton))]
    [TemplatePart(Name = "DownButtonElement", Type = typeof(RepeatButton))]
    [TemplateVisualState(Name = "Positive", GroupName = "ValueStates")]
    [TemplateVisualState(Name = "Negative", GroupName = "ValueStates")]
    [TemplateVisualState(Name = "Focused", GroupName = "FocusedStates")]
    [TemplateVisualState(Name = "Unfocused", GroupName = "FocusedStates")]
    public class NumericUpDown : Control
    {
        public NumericUpDown()
        {
            DefaultStyleKey = typeof(NumericUpDown);
            this.IsTabStop = true;
        }

        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register(
                "Value", typeof(int), typeof(NumericUpDown),
                new PropertyMetadata(
                    new PropertyChangedCallback(ValueChangedCallback)));

        public int Value
        {
            get
            {
                return (int)GetValue(ValueProperty);
            }

            set
            {
                SetValue(ValueProperty, value);
            }
        }

        private static void ValueChangedCallback(DependencyObject obj, 
            DependencyPropertyChangedEventArgs args)
        {
            NumericUpDown ctl = (NumericUpDown)obj;
            int newValue = (int)args.NewValue;

            // Call UpdateStates because the Value might have caused the
            // control to change ValueStates.
            ctl.UpdateStates(true);

            // Call OnValueChanged to raise the ValueChanged event.
            ctl.OnValueChanged(
                new ValueChangedEventArgs(NumericUpDown.ValueChangedEvent, 
                    newValue));
        }

        public static readonly RoutedEvent ValueChangedEvent =
            EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Direct,
                          typeof(ValueChangedEventHandler), typeof(NumericUpDown));

        public event ValueChangedEventHandler ValueChanged
        {
            add { AddHandler(ValueChangedEvent, value); }
            remove { RemoveHandler(ValueChangedEvent, value); }
        }

        protected virtual void OnValueChanged(ValueChangedEventArgs e)
        {
            // Raise the ValueChanged event so applications can be alerted
            // when Value changes.
            RaiseEvent(e);
        }

        private void UpdateStates(bool useTransitions)
        {
            if (Value >= 0)
            {
                VisualStateManager.GoToState(this, "Positive", useTransitions);
            }
            else
            {
                VisualStateManager.GoToState(this, "Negative", useTransitions);
            }

            if (IsFocused)
            {
                VisualStateManager.GoToState(this, "Focused", useTransitions);
            }
            else
            {
                VisualStateManager.GoToState(this, "Unfocused", useTransitions);
            }
        }

        public override void OnApplyTemplate()
        {
            UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
            DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
            //TextElement = GetTemplateChild("TextBlock") as TextBlock;

            UpdateStates(false);
        }

        private RepeatButton downButtonElement;

        private RepeatButton DownButtonElement
        {
            get
            {
                return downButtonElement;
            }

            set
            {
                if (downButtonElement != null)
                {
                    downButtonElement.Click -=
                        new RoutedEventHandler(downButtonElement_Click);
                }
                downButtonElement = value;

                if (downButtonElement != null)
                {
                    downButtonElement.Click +=
                        new RoutedEventHandler(downButtonElement_Click);
                }
            }
        }

        void downButtonElement_Click(object sender, RoutedEventArgs e)
        {
            Value--;
        }

        private RepeatButton upButtonElement;

        private RepeatButton UpButtonElement
        {
            get
            {
                return upButtonElement;
            }

            set
            {
                if (upButtonElement != null)
                {
                    upButtonElement.Click -=
                        new RoutedEventHandler(upButtonElement_Click);
                }
                upButtonElement = value;

                if (upButtonElement != null)
                {
                    upButtonElement.Click +=
                        new RoutedEventHandler(upButtonElement_Click);
                }
            }
        }

        void upButtonElement_Click(object sender, RoutedEventArgs e)
        {
            Value++;
        }

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);
            Focus();
        }


        protected override void OnGotFocus(RoutedEventArgs e)
        {
            base.OnGotFocus(e);
            UpdateStates(true);
        }

        protected override void OnLostFocus(RoutedEventArgs e)
        {
            base.OnLostFocus(e);
            UpdateStates(true);
        }
    }

    public delegate void ValueChangedEventHandler(object sender, ValueChangedEventArgs e);

    public class ValueChangedEventArgs : RoutedEventArgs
    {
        private int _value;

        public ValueChangedEventArgs(RoutedEvent id, int num)
        {
            _value = num;
            RoutedEvent = id;
        }

        public int Value
        {
            get { return _value; }
        }
    }
}
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Controls.Primitives
Imports System.Windows.Input
Imports System.Windows.Media

<TemplatePart(Name:="UpButtonElement", Type:=GetType(RepeatButton))> _
<TemplatePart(Name:="DownButtonElement", Type:=GetType(RepeatButton))> _
<TemplateVisualState(Name:="Positive", GroupName:="ValueStates")> _
<TemplateVisualState(Name:="Negative", GroupName:="ValueStates")> _
<TemplateVisualState(Name:="Focused", GroupName:="FocusedStates")> _
<TemplateVisualState(Name:="Unfocused", GroupName:="FocusedStates")> _
Public Class NumericUpDown
    Inherits Control

    Public Sub New()
        DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(GetType(NumericUpDown)))
        Me.IsTabStop = True
    End Sub

    Public Shared ReadOnly ValueProperty As DependencyProperty =
        DependencyProperty.Register("Value", GetType(Integer), GetType(NumericUpDown),
                          New PropertyMetadata(New PropertyChangedCallback(AddressOf ValueChangedCallback)))

    Public Property Value() As Integer

        Get
            Return CInt(GetValue(ValueProperty))
        End Get

        Set(ByVal value As Integer)

            SetValue(ValueProperty, value)
        End Set
    End Property

    Private Shared Sub ValueChangedCallback(ByVal obj As DependencyObject,
                                            ByVal args As DependencyPropertyChangedEventArgs)

        Dim ctl As NumericUpDown = DirectCast(obj, NumericUpDown)
        Dim newValue As Integer = CInt(args.NewValue)

        ' Call UpdateStates because the Value might have caused the
        ' control to change ValueStates.
        ctl.UpdateStates(True)

        ' Call OnValueChanged to raise the ValueChanged event.
        ctl.OnValueChanged(New ValueChangedEventArgs(NumericUpDown.ValueChangedEvent, newValue))
    End Sub

    Public Shared ReadOnly ValueChangedEvent As RoutedEvent =
        EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Direct,
                                         GetType(ValueChangedEventHandler), GetType(NumericUpDown))

    Public Custom Event ValueChanged As ValueChangedEventHandler

        AddHandler(ByVal value As ValueChangedEventHandler)
            Me.AddHandler(ValueChangedEvent, value)
        End AddHandler

        RemoveHandler(ByVal value As ValueChangedEventHandler)
            Me.RemoveHandler(ValueChangedEvent, value)
        End RemoveHandler

        RaiseEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Me.RaiseEvent(e)
        End RaiseEvent

    End Event


    Protected Overridable Sub OnValueChanged(ByVal e As ValueChangedEventArgs)
        ' Raise the ValueChanged event so applications can be alerted
        ' when Value changes.
        MyBase.RaiseEvent(e)
    End Sub


#Region "NUDCode"
    Private Sub UpdateStates(ByVal useTransitions As Boolean)

        If Value >= 0 Then
            VisualStateManager.GoToState(Me, "Positive", useTransitions)
        Else
            VisualStateManager.GoToState(Me, "Negative", useTransitions)
        End If

        If IsFocused Then
            VisualStateManager.GoToState(Me, "Focused", useTransitions)
        Else
            VisualStateManager.GoToState(Me, "Unfocused", useTransitions)

        End If
    End Sub

    Public Overloads Overrides Sub OnApplyTemplate()

        UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
        DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)

        UpdateStates(False)
    End Sub

    Private m_downButtonElement As RepeatButton

    Private Property DownButtonElement() As RepeatButton
        Get
            Return m_downButtonElement
        End Get

        Set(ByVal value As RepeatButton)

            If m_downButtonElement IsNot Nothing Then
                RemoveHandler m_downButtonElement.Click, AddressOf downButtonElement_Click
            End If
            m_downButtonElement = value

            If m_downButtonElement IsNot Nothing Then
                AddHandler m_downButtonElement.Click, AddressOf downButtonElement_Click
            End If
        End Set
    End Property

    Private Sub downButtonElement_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
        Value -= 1
    End Sub

    Private m_upButtonElement As RepeatButton

    Private Property UpButtonElement() As RepeatButton
        Get
            Return m_upButtonElement
        End Get

        Set(ByVal value As RepeatButton)
            If m_upButtonElement IsNot Nothing Then
                RemoveHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
            End If
            m_upButtonElement = value

            If m_upButtonElement IsNot Nothing Then
                AddHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
            End If
        End Set
    End Property

    Private Sub upButtonElement_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
        Value += 1
    End Sub

    Protected Overloads Overrides Sub OnMouseLeftButtonDown(ByVal e As MouseButtonEventArgs)
        MyBase.OnMouseLeftButtonDown(e)
        Focus()
    End Sub


    Protected Overloads Overrides Sub OnGotFocus(ByVal e As RoutedEventArgs)
        MyBase.OnGotFocus(e)
        UpdateStates(True)
    End Sub

    Protected Overloads Overrides Sub OnLostFocus(ByVal e As RoutedEventArgs)
        MyBase.OnLostFocus(e)
        UpdateStates(True)
    End Sub
#End Region
End Class


Public Delegate Sub ValueChangedEventHandler(ByVal sender As Object,
                                             ByVal e As ValueChangedEventArgs)

Public Class ValueChangedEventArgs
    Inherits RoutedEventArgs
    Private _value As Integer

    Public Sub New(ByVal id As RoutedEvent,
                   ByVal num As Integer)

        _value = num
        RoutedEvent = id
    End Sub

    Public ReadOnly Property Value() As Integer
        Get
            Return _value
        End Get
    End Property
End Class

参照See also