Erstellen eines Steuerelements mit einer anpassbaren Darstellung

Windows Presentation Foundation (WPF) ermöglicht es Ihnen, ein Steuerelement zu erstellen, dessen Darstellung angepasst werden kann. Beispielsweise können Sie die Darstellung einer CheckBox weitgehender ändern, als es durch das Festlegen von Eigenschaften möglich ist, indem Sie eine neue ControlTemplate erstellen. Die folgende Abbildung zeigt ein CheckBox an, das eine Standard-ControlTemplate verwendet, und ein CheckBox, das eine benutzerdefinierte ControlTemplate verwenden.

Ein Kontrollkästchen, das die Standardsteuerelementvorlage verwendet

Kontrollkästchen mit der Standardsteuerelementvorlage.

Ein Kontrollkästchen, das eine benutzerdefinierte Steuerelementvorlage verwendet

Kontrollkästchen mit einer benutzerdefinierten Steuerelementvorlage.

Wenn Sie beim Erstellen eines Steuerelements das Modell für Teile und Zustände berücksichtigen, kann die Darstellung des Steuerelements angepasst werden. Designertools wie Microsoft Expression Blend unterstützen das Modell für Teile und Zustände. Wenn Sie also diesem Modell folgen, ist das Steuerelement in den entsprechenden Anwendungstypen vom Benutzer anpassbar. In diesem Thema wird das Modell für Teile und Zustände erläutert und wie Sie dieses beim Erstellen eines eigenen Steuerelements berücksichtigen. Die Philosophie dieses Modells wird in diesem Thema anhand des Beispiels eines benutzerdefinierten Steuerelements, NumericUpDown, veranschaulicht. Das NumericUpDown-Steuerelement zeigt einen numerischen Wert an, den ein Benutzer erhöhen oder verringern kann, indem er auf die Schaltflächen des Steuerelements klickt. Die folgende Abbildung zeigt das NumericUpDown-Steuerelement, das in diesem Thema erläutert wird.

Ein benutzerdefiniertes NumericUpDown-Steuerelement.

Benutzerdefiniertes NumericUpDown-Steuerelement.

Dieses Thema enthält folgende Abschnitte:

  • Voraussetzungen

  • Modell für Teile und Zustände

  • Definieren der visuellen Struktur und des visuellen Verhaltens eines Steuerelements in einer ControlTemplate

  • Verwenden von Teilen der ControlTemplate in Code

  • Bereitstellen des Steuerelementvertrags

  • Vollständiges Beispiel

Voraussetzungen

In diesem Thema wird davon ausgegangen, dass Sie wissen, wie eine neue ControlTemplate für ein vorhandenes Steuerelement erstellt wird, und mit den Elementen eines Steuerelementvertrags und den in Anpassen der Darstellung eines vorhandenen Steuerelements durch Erstellen einer ControlTemplate erläuterten Begriffen vertraut sind.

HinweisHinweis

Um ein Steuerelement zu erstellen, dessen Darstellung angepasst werden kann, müssen Sie ein Steuerelement erstellen, das von der Control-Klasse oder einer ihrer Unterklassen (außer UserControl) erbt.Ein Steuerelement, das von UserControl erbt, kann zwar schnell erstellt werden, verwendet aber keine ControlTemplate, und Sie können seine Darstellung nicht anpassen.

Modell für Teile und Zustände

Das Modell für Teile und Zustände gibt an, wie die visuelle Struktur und das visuelle Verhalten eines Steuerelements definiert werden. Um dem Modell für Teile und Zustände zu folgen, gehen Sie wie folgt vor:

  • Definieren Sie die visuelle Struktur und das visuelle Verhalten in der ControlTemplate eines Steuerelements.

  • Verwenden Sie bestimmte bewährte Methoden, um zu steuern, wie die Logik des Steuerelements mit Teilen der Steuerelementvorlage interagiert.

  • Stellen Sie einen Steuerelementvertrag bereit, um anzugeben, was die ControlTemplate enthalten soll.

Wenn Sie die visuelle Struktur und das visuelle Verhalten in der ControlTemplate eines Steuerelements definieren, können Anwendungsentwickler die visuelle Struktur und das visuelle Verhalten des Steuerelements ändern, indem sie eine neue ControlTemplate erstellen, statt Code zu schreiben. Sie müssen einen Steuerelementvertrag bereitstellen, um die Anwendungsentwickler darüber zu informieren, welche FrameworkElement-Objekte und Zustände in der ControlTemplate definiert werden sollen. Verwenden Sie für die Interaktion mit den Teilen in der ControlTemplate einige bewährte Methoden, sodass das Steuerelement eine unvollständige ControlTemplate ordnungsgemäß behandelt. Wenn Sie sich an diese drei Prinzipien halten, können Anwendungsentwickler eine ControlTemplate für Ihr Steuerelement ebenso leicht wie für die in WPF enthaltenen Steuerelemente erstellen. Im folgenden Abschnitt werden diese Empfehlungen ausführlich erläutert.

Definieren der visuellen Struktur und des visuellen Verhaltens eines Steuerelements in einer ControlTemplate

Wenn Sie das benutzerdefinierte Steuerelement anhand des Modells für Teile und Zustände erstellen, definieren Sie die visuelle Struktur und das visuelle Verhalten des Steuerelements nicht in der zugehörigen Logik, sondern in der ControlTemplate. Die visuelle Struktur eines Steuerelements ist die Zusammensetzung der FrameworkElement-Objekte, aus denen das Steuerelement besteht. Das visuelle Verhalten ist die Art, wie das Steuerelement in bestimmten Zuständen dargestellt wird. Weitere Informationen über das Erstellen einer ControlTemplate, die die visuelle Struktur und das visuelle Verhalten eines Steuerelements angibt, finden Sie unter Anpassen der Darstellung eines vorhandenen Steuerelements durch Erstellen einer ControlTemplate.

Im Beispiel für das NumericUpDown-Steuerelement schließt die visuelle Struktur zwei RepeatButton-Steuerelemente und einen TextBlock ein. Wenn Sie diese Steuerelemente im Code des NumericUpDown-Steuerelements hinzufügen, z. B. im Konstruktor, sind die Positionen der betreffenden Steuerelemente unveränderlich. Sie sollten die visuelle Struktur und das visuelle Verhalten des Steuerelements nicht im Code, sondern in der ControlTemplate definieren. Dann kann ein Anwendungsentwickler die Position der Schaltflächen und von TextBlock anpassen sowie das Verhalten für den Fall angeben, dass Value negativ ist, weil die ControlTemplate ersetzt werden kann.

Im folgenden Beispiel wird die visuelle Struktur des NumericUpDown-Steuerelements veranschaulicht, die einen RepeatButton zum Erhöhen von Value, einen RepeatButton zum Verringern von Value und einen TextBlock zum Anzeigen von Value enthält.

<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>

Ein visuelles Verhalten des NumericUpDown-Steuerelements besteht darin, dass ein negativer Wert in roter Schrift angezeigt wird. Wenn Sie den Foreground des TextBlock im Code ändern, wenn der Value negativ ist, zeigt NumericUpDown immer einen roten negativen Wert an. Sie geben das visuelle Verhalten des Steuerelements in der ControlTemplate an, indem Sie der ControlTemplate VisualState-Objekte hinzufügen. Das folgende Beispiel zeigt die VisualState-Objekte für die Zustände Positive und Negative. Positive und Negative schließen sich gegenseitig aus (das Steuerelement weist immer nur einen der beiden Zustände auf). Daher befinden sich die VisualState-Objekte im Beispiel in einer einzelnen VisualStateGroup. Wenn das Steuerelement in den Negative-Zustand wechselt, wird der Foreground des TextBlock rot. Wenn das Steuerelement den Positive-Zustand aufweist, wechselt der Foreground wieder zum ursprünglichen Wert zurück. Das Definieren von VisualState-Objekten in einer ControlTemplate wird in Anpassen der Darstellung eines vorhandenen Steuerelements durch Erstellen einer ControlTemplate weiter erläutert.

HinweisHinweis

Die angefügte VisualStateManager.VisualStateGroups-Eigenschaft muss für das Stamm-FrameworkElement der ControlTemplate festgelegt werden.

<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>

Verwenden von Teilen der ControlTemplate in Code

Ein Entwickler einer ControlTemplate lässt u. U. FrameworkElement-Objekte oder VisualState-Objekte absichtlich oder irrtümlich weg. Möglicherweise benötigt die Logik Ihres Steuerelements diese Teile aber, um ordnungsgemäß zu funktionieren. Das Modell für Teile und Zustände gibt an, dass das Steuerelement flexibel auf eine ControlTemplate reagiert, in der FrameworkElement-Objekte oder VisualState-Objekte fehlen. Das Steuerelement sollte keine Ausnahme auslösen oder einen Fehler melden, wenn ein FrameworkElement, ein VisualState oder eine VisualStateGroup in der ControlTemplate fehlt. In diesem Abschnitt werden die empfohlenen Vorgehensweisen zum Interagieren mit FrameworkElement-Objekten und zum Verwalten von Zuständen beschrieben.

Vorhersehen von fehlenden FrameworkElement-Objekten

Wenn Sie FrameworkElement-Objekte in der ControlTemplate definieren, muss die Logik Ihres Steuerelements möglicherweise mit einigen dieser Objekte interagieren. Beispielsweise abonniert das NumericUpDown-Steuerelement das Click-Ereignis der Schaltflächen zum Erhöhen oder Verringern von Value und legt die Text-Eigenschaft des TextBlock auf Value fest. Wenn in einer benutzerdefinierten ControlTemplate der TextBlock oder die Schaltflächen fehlen, ist ein gewisser Funktionsverlust beim Steuerelement akzeptabel. Sie sollten sich aber vergewissern, dass das Steuerelement keine Fehler auslöst. Wenn eine ControlTemplate z. B. nicht die Schaltflächen zum Ändern von Value enthält, geht diese Funktion bei NumericUpDown verloren. Dennoch wird eine Anwendung, die die ControlTemplate verwendet, weiterhin ausgeführt.

Mit den folgenden Methoden wird sichergestellt, dass das Steuerelement angemessen auf das Fehlen von FrameworkElement-Objekten reagiert:

  1. Legen Sie das x:Name-Attribut für jedes FrameworkElement fest, auf das Sie im Code verweisen müssen.

  2. Definieren Sie private Eigenschaften für jedes FrameworkElement, mit dem interagiert werden muss.

  3. Abonnieren und kündigen Sie alle Ereignisse, die das Steuerelement im set-Accessor der FrameworkElement-Eigenschaft behandelt.

  4. Legen Sie die FrameworkElement-Eigenschaften fest, die Sie in Schritt 2 der OnApplyTemplate-Methode definiert haben. Dies ist der früheste Zeitpunkt, zu dem das FrameworkElement in der ControlTemplate für das Steuerelement verfügbar ist. Verwenden Sie den x:Name des FrameworkElement, um es von der ControlTemplate abzurufen.

  5. Stellen Sie sicher, dass das FrameworkElement nicht null ist, bevor auf die entsprechenden Member zugegriffen wird. Wenn der Wert null ist, melden Sie keinen Fehler.

In den folgenden Beispielen wird gezeigt, wie das NumericUpDown-Steuerelement in Übereinstimmung mit den Empfehlungen in der vorangehenden Liste mit FrameworkElement-Objekten interagiert.

In dem Beispiel, in dem die visuelle Struktur des NumericUpDown-Steuerelements in der ControlTemplate definiert ist, ist das x:Name-Attribut des RepeatButton, mit dem Value erhöht wird, auf UpButton festgelegt. Im folgenden Beispiel wird eine Eigenschaft mit dem Namen UpButtonElement deklariert, die den RepeatButton darstellt, der in der ControlTemplate deklariert wird. Der set-Accessor kündig zunächst das Click-Ereignis der Schaltfläche, wenn UpDownElement nicht null ist, legt dann die Eigenschaft fest und abonniert anschließend das Click-Ereignis. Auch für die andere RepeatButton mit der Bezeichnung DownButtonElement wird eine Eigenschaft definiert, die aber nicht hier gezeigt wird.

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 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);
        }
    }
}

Das folgende Beispiel zeigt die OnApplyTemplate für das NumericUpDown-Steuerelement. Im Beispiel wird die GetTemplateChild-Methode verwendet, um die FrameworkElement-Objekte aus der ControlTemplate abzurufen. Beachten Sie, dass das Beispiel Vorkehrungen für den Fall aufweist, dass GetTemplateChild ein FrameworkElement mit dem angegebenen Namen findet, das nicht vom erwarteten Typ ist. Es ist auch eine bewährte Methode, Elemente zu ignorieren, die zwar den angegebenen x:Name haben, aber vom falschen Typ sind.

Public Overloads Overrides Sub OnApplyTemplate()

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

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

    UpdateStates(false);
}

Wenn Sie sich an die in den vorangegangenen Beispielen gezeigten Methoden halten, stellen Sie sicher, dass das Steuerelement weiterhin ausgeführt wird, wenn in der ControlTemplate ein FrameworkElement fehlt.

Verwenden von VisualStateManager zum Verwalten von Zuständen

Der VisualStateManager verfolgt die Zustände eines Steuerelements und führt die zum Wechseln zwischen Zuständen erforderliche Logik aus. Beim Hinzufügen von VisualState-Objekten zur ControlTemplate fügen Sie die Objekte einer VisualStateGroup hinzu und fügen der angefügten VisualStateGroups-Eigenschaft die System.Windows.VisualStateGroup-Eigenschaft hinzu, sodass der VisualStateManager auf diese zugreifen kann.

Das folgende Beispiel ist eine Wiederholung des vorherigen Beispiels, in dem die VisualState-Objekte veranschaulicht werden, die dem Positive-Zustand und dem Negative-Zustand des Steuerelements entsprechen. Das Storyboard im Negative VisualState zeigt den Foreground des TextBlock rot an. Wenn das NumericUpDown-Steuerelement den Zustand Negative aufweist, beginnt das Storyboard im Zustand Negative. Das Storyboard im Zustand Negative wird beendet, wenn das Steuerelement wieder in den Zustand Positive wechselt. Der VisualState Positive muss kein Storyboard enthalten. Wenn das Storyboard für Negative beendet wird, wird der Foreground wieder in der ursprünglichen Farbe angezeigt.

<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>

Beachten Sie, dass dem TextBlock ein Name gegeben wird, sich der TextBlock aber nicht im Steuerelementvertrag für NumericUpDown befindet, da die Logik des Steuerelements nie auf den TextBlock verweist. Elemente, auf die in der ControlTemplate verwiesen wird, verfügen über Namen, müssen aber nicht Bestandteil des Steuerelementvertrags sein, da eine neue ControlTemplate für das Steuerelement unter Umständen nicht auf dieses Element verweisen muss. Jemand, der eine neue ControlTemplate für NumericUpDown erstellt, könnte z. B. entscheiden nicht anzugeben, dass Value negativ ist, indem er den Foreground ändert. In diesem Fall verweist weder der Code noch die ControlTemplate mit Namen auf den TextBlock.

Die Logik des Steuerelements ist verantwortlich dafür, den Zustand des Steuerelements zu ändern. Das folgende Beispiel zeigt, dass das NumericUpDown-Steuerelement die GoToState-Methode aufruft, um in den Zustand Positive zu wechseln, wenn Value gleich oder größer 0 ist, und in den Zustand Negative, wenn Value kleiner 0 ist.

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

Die GoToState-Methode führt die Logik aus, die jeweils zum Starten bzw. Beenden des Storyboards erforderlich ist. Wenn ein Steuerelement GoToState aufruft, um seinen Zustand zu ändern, geht der VisualStateManager wie folgt vor:

  • Wenn der VisualState, in den das Steuerelement wechselt, ein Storyboard aufweist, beginnt das Storyboard. Wenn dann der VisualState, aus dem das Steuerelement stammt, über ein Storyboard verfügt, wird das Storyboard beendet.

  • Wenn das Steuerelement bereits im angegebenen Zustand ist, führt GoToState keine Aktion aus und gibt true zurück.

  • Wenn der angegebene Zustand in der ControlTemplate von control nicht vorhanden ist, führt GoToState keine Aktion aus und gibt false zurück.

Empfohlene Vorgehensweise für die Verwendung von VisualStateManager

Es wird empfohlen, dass Sie zum Verwalten der Zustände des Steuerelements wie folgt vorgehen:

  • Verwenden Sie Eigenschaften, um den Zustand nachzuverfolgen.

  • Erstellen Sie eine Hilfsmethode für das Wechseln zwischen Zuständen.

Das NumericUpDown-Steuerelement verwendet seine Value-Eigenschaft, um zu ermitteln, ob es im Zustand Positive oder im Zustand Negative ist. Das NumericUpDown-Steuerelement definiert auch die Zustände Focused und UnFocused, die die IsFocused-Eigenschaft nachverfolgen. Wenn Sie Zustände verwenden, die nicht der Eigenschaft eines Steuerelements entsprechen, können Sie eine private Eigenschaft definieren, um den Zustand zu verfolgen.

Eine einzelne Methode, die alle Zustände aktualisiert, zentralisiert Aufrufe zum VisualStateManager und hält den Code überschaubar. Das folgende Beispiel zeigt die Hilfsmethode des NumericUpDown-Steuerelements, UpdateStates. Wenn Value größer oder gleich 0 ist, befindet sich das Control im Zustand Positive. Wenn Value kleiner als 0 ist, weist das Steuerelement den Zustand Negative auf. Wenn IsFocused true ist, weist das Steuerelement den Zustand Focused auf. Andernfalls befindet es sich im Zustand Unfocused. Das Steuerelement kann UpdateStates unabhängig davon, welcher Zustand sich ändert, immer dann aufrufen, wenn es seinen Zustand ändern muss.

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
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);
    }

}

Übergeben Sie einen Zustandsnamen an GoToState, wenn das Steuerelement bereits in diesem Zustand ist, hat GoToState keine Auswirkungen. Daher müssen Sie den aktuellen Zustand des Steuerelements nicht überprüfen. Wenn z. B. Value aus einer negativen Zahl in eine andere negative Zahl geändert wird, wird das Storyboard für den Zustand Negative nicht unterbrochen, und für den Benutzer sind keine Änderungen im Steuerelement sichtbar.

Der VisualStateManager verwendet VisualStateGroup-Objekte, um zu bestimmen, welcher Zustand beendet werden soll, wenn Sie GoToState aufrufen. Das Steuerelement befindet sich für jede VisualStateGroup, die in der zugehörigen ControlTemplate definiert ist, immer in einem Zustand und verlässt einen Zustand nur dann, wenn es in einen anderen Zustand aus derselben VisualStateGroup wechselt. Beispielsweise definiert die ControlTemplate des NumericUpDown-Steuerelements die VisualState-Objekte Positive und Negative in einer VisualStateGroup und die VisualState-Objekte Focused und Unfocused in einer anderen. (Sie können den im Abschnitt Vollständiges Beispiel in diesem Beispiel definierten VisualState Focused und Unfocused sehen. Wenn das Steuerelement aus dem Zustand Positive in den Zustand Negative wechselt oder umgekehrt, verbleibt das Steuerelement im Zustand Focused oder Unfocused.

Es gibt drei typische Situationen, in denen sich der Zustand eines Steuerelements ändern kann:

  • Wenn die ControlTemplate auf das Control angewendet wird.

  • Wenn sich eine Eigenschaft ändert.

  • Wenn ein Ereignis eintritt.

In den folgenden Beispielen wird das Aktualisieren des Zustands des NumericUpDown-Steuerelements in diesen Fällen veranschaulicht.

Sie sollten den Zustand des Steuerelements in der OnApplyTemplate-Methode aktualisieren, sodass das Steuerelement im richtigen Zustand angezeigt wird, wenn die ControlTemplate angewendet wird. Im folgenden Beispiel wird UpdateStates in OnApplyTemplate aufgerufen, um sicherzustellen, dass sich das Steuerelement im entsprechenden Zustand befindet. Angenommen, Sie erstellen ein NumericUpDown-Steuerelement und legen den Foreground auf Grün und Value auf -5 fest. Wenn Sie UpdateStates beim Anwenden der ControlTemplate auf das NumericUpDown-Steuerelement nicht aufrufen, weist das Steuerelement nicht den Zustand Negative auf, und der Wert ist Grün statt Rot. Sie müssen UpdateStates aufrufen, um das Steuerelement in den Zustand Negative zu versetzen.

Public Overloads Overrides Sub OnApplyTemplate()

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

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

    UpdateStates(false);
}

Oft muss der Zustand eines Steuerelements aktualisiert werden, wenn sich eine Eigenschaft ändert. Im folgenden Beispiel wird die gesamte ValueChangedCallback-Methode gezeigt. Da ValueChangedCallback aufgerufen wird, wenn sich Value ändert, ruft die Methode UpdateStates auf, falls Value aus einer positiven Zahl in eine negative geändert wurde oder umgekehrt. Es ist akzeptabel, UpdateStates aufzurufen, wenn sich Value zwar ändert, aber positiv bzw. negativ bleibt, da sich in diesem Fall der Zustand des Steuerelements nicht verändert.

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
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));
}

Möglicherweise müssen Sie den Zustand auch aktualisieren, wenn ein Ereignis eintritt. Das folgende Beispiel zeigt, dass NumericUpDown UpdateStates für das Control aufruft, um das GotFocus-Ereignis zu behandeln.

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

Der VisualStateManager unterstützt Sie bei der Verwaltung der Zustände Ihres Steuerelements. Durch Verwendung von VisualStateManager stellen Sie sicher, dass das Steuerelement ordnungsgemäß zwischen Zuständen wechselt. Wenn Sie den in diesem Abschnitt beschriebenen Empfehlungen zur Verwendung von VisualStateManager folgen, bleibt der Code Ihres Steuerelements lesbar und verwaltbar.

Bereitstellen des Steuerelementvertrags

Sie stellen einen Steuerelementvertrag bereit, damit Entwickler von ControlTemplate wissen, was in die Vorlage eingefügt werden soll. Ein Steuerelementvertrag besteht aus drei Elementen:

  • Den von der Logik des Steuerelements verwendeten visuellen Elementen

  • Den Zuständen des Steuerelements und den Gruppen, zu denen die einzelnen Zustände gehören

  • Die öffentlichen Eigenschaften, die sich auf das Steuerelement visuell auswirken

Ein Entwickler, der eine neue ControlTemplate erstellt, muss wissen, welche FrameworkElement-Objekte die Logik des Steuerelements verwendet und von welchem Typ die einzelnen Objekte sind. Außerdem muss der Name bekannt sein. Ein Entwickler von ControlTemplate muss darüber hinaus die Namen aller möglichen Zustände kennen, die das Steuerelement annehmen kann, und wissen, in welcher VisualStateGroup sich der Zustand befindet.

Hinsichtlich des Beispiels von NumericUpDown, erwartet das Steuerelement, dass die ControlTemplate über die folgenden FrameworkElement-Objekte verfügt:

Das Steuerelement kann sich in den folgenden Zuständen befinden:

Um anzugeben, welche FrameworkElement-Objekte das Steuerelement erwartet, verwenden Sie das TemplatePartAttribute, das den Namen und den Typ der erwarteten Elemente angibt. Um die möglichen Zustände eines Steuerelements anzugeben, verwenden Sie das TemplateVisualStateAttribute, das den Namen des Zustands sowie die VisualStateGroup angibt, zu der er gehört. Fügen Sie das TemplatePartAttribute und das TemplateVisualStateAttribute in die Klassendefinition des Steuerelements ein.

Jede öffentliche Eigenschaft, die sich auf die Darstellung des Steuerelements auswirkt, ist auch Teil des Steuerelementvertrags.

Im folgenden Beispiel werden das FrameworkElement-Objekt und die Zustände für das NumericUpDown-Steuerelement angegeben.

<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
[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; }
}

Vollständiges Beispiel

Das folgende Beispiels ist die vollständige ControlTemplate für das NumericUpDown-Steuerelement.

<!--This is the contents of the themes/generic.xaml file.-->
<ResourceDictionary
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://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>

Im folgenden Beispiel ist die Logik für das NumericUpDown-Steuerelement dargestellt.

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
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; }
        }
    }
}

Siehe auch

Konzepte

Anpassen der Darstellung eines vorhandenen Steuerelements durch Erstellen einer ControlTemplate

Weitere Ressourcen

Anpassung von Steuerelementen