Übersicht über das Erstellen von Steuerelementen

Dank der Erweiterbarkeit des Windows Presentation Foundation (WPF)-Steuerelementmodells ist es nur selten erforderlich, ein neues Steuerelement zu erstellen. In bestimmten Fällen kann es jedoch auch weiterhin erforderlich sein, ein benutzerdefiniertes Steuerelement zu erstellen. In diesem Thema werden die Features erläutert, dank denen ein benutzerdefiniertes Steuerelement nur noch selten erstellt werden muss, und es werden die verschiedenen Modelle für das Erstellen von Steuerelementen in Windows Presentation Foundation (WPF) beschrieben. Dieses Thema veranschaulicht auch, wie ein neues Steuerelement erstellt wird.

Dieses Thema enthält folgende Abschnitte.

  • Alternativen zum Schreiben eines neuen Steuerelements
  • Modelle für das Erstellen von Steuerelementen
  • Grundlagen des Erstellens von Steuerelementen
  • Verwandte Abschnitte

Alternativen zum Schreiben eines neuen Steuerelements

Wenn Sie in der Vergangenheit ein vorhandenes Steuerelement an eigene Zwecke anpassen wollten, waren Sie darauf beschränkt, die Standardeigenschaften des Steuerelements zu ändern, also Hintergrundfarbe, Rahmenbreite und Schriftgröße. Wenn Sie das Aussehen oder Verhalten eines Steuerelements über diese vordefinierten Parameter hinaus erweitern möchten, müssen Sie ein neues Steuerelement erstellen. Normalerweise verwenden Sie dazu die Vererbung von einem vorhandenen Steuerelement und das Überschreiben der Methode, die für das Zeichnen des Steuerelements zuständig ist. Dies ist auch weiterhin möglich, aber Sie können vorhandene Steuerelemente mit WPF anpassen, indem Sie das umfangreiche Inhaltsmodell sowie die Stile, Vorlagen und Trigger verwenden. Die folgende Liste enthält Beispiele dazu, wie Sie mit diesen Features benutzerdefinierte und einheitliche Abläufe erzielen, ohne ein neues Steuerelement erstellen zu müssen.

  • Umfangreicher Inhalt Viele der WPF-Standardsteuerelemente unterstützen umfangreichen Inhalt. Die Inhaltseigenschaft eines Button-Elements hat z. B. den Typ Object, sodass auf einem Button-Element theoretisch alles angezeigt werden kann. Damit eine Schaltfläche ein Bild und Text anzeigt, können Sie ein Bild und einen TextBlock einem StackPanel hinzufügen und das StackPanel der Content-Eigenschaft zuweisen. Da diese Steuerelemente visuelle WPF-Elemente und beliebige Daten anzeigen können, ist das Erstellen neuer Steuerelemente oder das Ändern vorhandener Steuerelemente zur Unterstützung einer komplexen visuellen Darstellung nur noch selten notwendig. Weitere Informationen zum Inhaltsmodell für Button sowie anderen Inhaltsmodellen in WPF finden Sie unter WPF-Inhaltsmodell.

  • Stile Ein Style ist eine Auflistung von Werten, die Eigenschaften für ein Steuerelement darstellen. Durch die Verwendung von Stilen können Sie eine wiederverwendbare Darstellung des gewünschten Aussehens und Verhaltens eines Steuerelements ohne Schreiben eines neuen Steuerelements erstellen. Beispiel: Sie möchten alle TextBlock-Steuerelemente rot mit der Schriftart Arial und einem Schriftgrad von 14 formatieren. Sie können ein Format als Ressource erstellen und die jeweiligen Eigenschaften entsprechend festlegen. So verfügt jeder TextBlock, den Sie der Anwendung hinzufügen, über die gleiche Darstellung.

  • Datenvorlagen Mithilfe einer DataTemplate können Sie anpassen, wie Daten auf einem Steuerelement angezeigt werden. Zum Beispiel können Sie eine DataTemplate verwenden, um anzugeben, wie Daten in einem ListBox angezeigt werden. Ein Beispiel hierfür finden Sie unter Übersicht über Datenvorlagen. Zusätzlich zum Anpassen der Datendarstellung kann eine DataTemplate Benutzeroberflächenelemente enthalten. Auf diese Weise können Sie beim Erstellen benutzerdefinierter Oberflächen flexibel vorgehen. Mithilfe einer DataTemplate können Sie z. B. ein ComboBox-Element erstellen, in dem jedes Element ein Kontrollkästchen enthält.

  • Steuerelementvorlagen. Viele Steuerelemente in WPF verwenden eine ControlTemplate, um die Struktur und Darstellung eines Steuerelements zu definieren, wodurch die Darstellung eines Steuerelements von seiner Funktionalität getrennt wird. Sie können die Darstellung eines Steuerelements stark verändern, indem Sie seine ControlTemplate neu definieren. Angenommen, Sie möchten ein Steuerelement erstellen, das wie eine Ampel aussieht. Dieses Steuerelement verfügt über eine einfache Benutzeroberfläche und Funktion. Das Steuerelement besteht aus drei Kreisen, von denen nur jeweils einer leuchten kann. Nach einiger Überlegung stellen Sie fest, dass ein RadioButton-Element über die Funktion verfügt, bei der jeweils eine Komponente ausgewählt ist. Das standardmäßige Aussehen des RadioButton-Elements entspricht jedoch überhaupt nicht einer Ampel. Da das RadioButton-Element zum Definieren seines Aussehens eine Steuerelementvorlage verwendet, können Sie die ControlTemplate auf einfache Weise neu definieren, um die Anforderungen des Steuerelements zu erfüllen und die Ampel mithilfe von Optionsfeldern zu erstellen.

    HinweisHinweis

    Obwohl ein RadioButton-Element eine DataTemplate verwenden kann, ist eine DataTemplate in diesem Beispiel nicht ausreichend.Die DataTemplate definiert die Darstellung des Inhalts eines Steuerelements.Bei einem RadioButton-Element ist der Inhalt das, was rechts von dem Kreis angezeigt wird, der angibt, ob das RadioButton-Element aktiviert ist.Für das Beispiel mit der Ampel muss das Optionsfeld nur ein Kreis sein, der leuchten kann. Da die Darstellungsanforderung für die Ampel sich so stark von der Standarddarstellung des RadioButton-Elements unterscheidet, müssen Sie die ControlTemplate neu definieren.Normalerweise wird eine DataTemplate verwendet, um den Inhalt (bzw. die Daten) eines Steuerelements zu definieren, und eine ControlTemplate wird verwendet, um die Strukturierung eines Steuerelements zu definieren.

  • Trigger Mit einem Trigger können Sie das Aussehen und Verhalten eines Steuerelements dynamisch ändern, ohne ein neues Steuerelement erstellen zu müssen. Angenommen, Sie verwenden in der Anwendung mehrere ListBox-Steuerelemente und möchten, dass die Elemente in allen ListBox-Objekten fett und rot angezeigt werden, wenn sie markiert werden. Sie neigen ggf. zuerst dazu, eine Klasse zu erstellen, die von ListBox erbt, und die OnSelectionChanged-Methode zu überschreiben, um das Aussehen des markierten Elements zu ändern. Es ist jedoch besser, einem Stil eines ListBoxItem-Objekts einen Trigger hinzuzufügen, der das Aussehen des markierten Elements ändert. Mit einem Trigger können Sie Eigenschaftswerte ändern oder basierend auf dem Wert einer Eigenschaft Aktionen ausführen. Mit einem EventTrigger können Sie Aktionen ausführen, wenn ein Ereignis eintritt.

Weitere Informationen zu Stilen, Vorlagen und Triggern finden Sie unter Erstellen von Formaten und Vorlagen.

Wenn ein Steuerelement über die Funktionalität eines vorhandenen Steuerelements verfügt, Sie jedoch ein anderes Aussehen benötigen, sollten Sie zuerst die Verwendung der Methoden erwägen, die in diesem Abschnitt erläutert werden, um das Aussehen eines vorhandenen Steuerelements zu ändern.

Modelle für das Erstellen von Steuerelementen

Das umfangreiche Inhaltsmodell sowie die Stile, Vorlagen und Trigger sorgen dafür, dass Sie in vielen Fällen kein neues Steuerelement erstellen müssen. Wenn Sie jedoch ein neues Steuerelement erstellen müssen, sollten Sie mit den verschiedenen Modellen zum Erstellen von Steuerelementen von WPF vertraut sein. WPF stellt drei allgemeine Modelle zum Erstellen eines Steuerelements bereit, die jeweils verschiedene Features und Flexibilitätsgrade bieten. Die Basisklassen für die drei Modelle sind UserControl, Control und FrameworkElement.

Ableiten von UserControl

Die einfachste Möglichkeit zum Erstellen eines Steuerelements in WPF ist die Ableitung von UserControl. Wenn Sie ein Steuerelement erstellen, das von UserControl erbt, fügen Sie dem UserControl-Element vorhandene Komponenten hinzu und verweisen mithilfe von Extensible Application Markup Language (XAML) auf Ereignishandler. Sie können dann auf die benannten Elemente verweisen und die Ereignishandler im Code definieren. Dieses Entwicklungsmodell ähnelt dem Modell, das in WPF für die Anwendungsentwicklung verwendet wird.

Ein UserControl kann, wenn es ordnungsgemäß erstellt wurde, die Vorteile von umfangreichem Inhalt, Stilen und Triggern nutzen. Wenn das Steuerelement jedoch von UserControl erbt, können Personen, die das Steuerelement verwenden, keine DataTemplate oder ControlTemplate verwenden, um dessen Aussehen anzupassen. Es ist eine Ableitung von der Control-Klasse oder einer ihrer abgeleiteten Klassen (mit Ausnahme von UserControl) erforderlich, um ein benutzerdefiniertes Steuerelement zu erstellen, das Vorlagen unterstützt.

Vorteile der Ableitung von UserControl

Erwägen Sie die Ableitung von UserControl, wenn alle folgenden Bedingungen gelten:

  • Sie möchten das Steuerelement in ähnlicher Weise wie eine Anwendung erstellen.

  • Das Steuerelement besteht nur aus bereits vorhandenen Komponenten.

  • Sie müssen keine komplexe Anpassung unterstützen.

Ableiten von Control

Eine Ableitung von der Control-Klasse ist das Modell, das die meisten der vorhandenen WPF-Steuerelemente verwenden. Wenn Sie ein Steuerelement erstellen, das von der Control-Klasse erbt, definieren Sie das Aussehen mithilfe von Vorlagen. Dabei trennen Sie die Funktionslogik von der visuellen Darstellung. Sie können die Entkopplung der Benutzeroberfläche und der Logik auch sicherstellen, indem Sie anstelle von Ereignissen Befehle und Bindungen verwenden und Verweise auf Elemente in der ControlTemplate nach Möglichkeit vermeiden. Wenn die Benutzeroberfläche und die Logik des Steuerelements korrekt entkoppelt sind, können die Benutzer des Steuerelements die ControlTemplate des Steuerelements neu definieren, um seine Darstellung anzupassen. Auch wenn das Erstellen eines benutzerdefinierten Control nicht so einfach ist wie das Erstellen eines UserControl, bietet ein benutzerdefiniertes Control doch die größte Flexibilität.

Vorteile der Ableitung von Control

Wenn einer oder mehrere der folgenden Umstände zutreffen, sollten Sie die Ableitung von Control statt der Verwendung der UserControl-Klasse in Betracht ziehen:

  • Sie möchten, dass die Darstellung des Steuerelements über die ControlTemplate anpassbar ist.

  • Sie möchten, dass das Steuerelement verschiedene Designs unterstützt.

Ableiten von FrameworkElement

Steuerelemente, die von UserControl oder Control abgeleitet werden, basieren auf dem Zusammensetzen vorhandener Elemente. In vielen Fällen ist dies akzeptabel, da sich Objekte, die von FrameworkElement erben, in einer ControlTemplate befinden können. Es kann jedoch vorkommen, dass die Darstellung eines Steuerelements mehr als die Funktionalität einer einfachen Elementzusammensetzung erfordert. Für solche Szenarien sollte eine Komponente auf der Basis von FrameworkElement erstellt werden.

Es gibt zwei Standardmethoden zum Erstellen von auf FrameworkElement basierenden Komponenten: direktes Rendering und benutzerdefinierte Elementzusammensetzung. Das direkte Rendering beinhaltet das Überschreiben der OnRender-Methode von FrameworkElement und das Bereitstellen von DrawingContext-Operationen, die die visuelle Struktur der Komponente explizit definieren. Diese Methode wird von Image und Border verwendet. Die benutzerdefinierte Elementzusammensetzung beinhaltet das Verwenden von Objekten des Typs Visual, um die Darstellung der Komponente zusammenzusetzen. Ein Beispiel finden Sie unter Verwenden von DrawingVisual-Objekten. Track ist ein Beispiel für ein Steuerelement in WPF, das die benutzerdefinierte Elementzusammensetzung verwendet. Es ist auch möglich, direktes Rendering und benutzerdefinierte Elementzusammensetzung im gleichen Steuerelement zu kombinieren.

Vorteile der Ableitung von FrameworkElement

Erwägen Sie die Ableitung von FrameworkElement, wenn eine oder mehrere der folgenden Bedingungen gelten:

  • Sie benötigen eine genaue Steuerung der Darstellung des Steuerelements, die über das hinausgeht, was die einfache Elementzusammensetzung bietet.

  • Sie möchten die Darstellung des Steuerelements definieren, indem Sie eine eigene Renderinglogik definieren.

  • Sie möchten vorhandene Elemente in einer Weise neu zusammensetzen, die über das hinausgeht, was mit UserControl und Control möglich ist.

Grundlagen des Erstellens von Steuerelementen

Wie bereits erwähnt, ist eine der leistungsfähigsten Funktionen von WPF die Möglichkeit, über die Festlegung der grundlegenden Eigenschaften eines Steuerelements zum Ändern der Darstellung und des Verhaltens hinausgehen zu können, ohne ein benutzerdefiniertes Steuerelement erstellen zu müssen. Die Funktionen für Stil, Datenbindung und Trigger werden durch das WPF-Eigenschaftensystem und das WPF-Ereignissystem ermöglicht. In den folgenden Abschnitten werden einige Verfahren beschrieben, die Sie unabhängig von dem zum Erstellen des benutzerdefinierten Steuerelements verwendeten Modell befolgen sollten, damit Benutzer des benutzerdefinierten Steuerelements diese Funktionen wie bei einem in WPF enthaltenen Steuerelement verwenden können. 

Verwenden von Abhängigkeitseigenschaften

Wenn es sich bei einer Eigenschaft um eine Abhängigkeitseigenschaft handelt, können Sie wie folgt vorgehen:

  • Legen Sie die Eigenschaft in einem Stil fest.

  • Binden Sie die Eigenschaft an eine Datenquelle.

  • Verwenden Sie eine dynamische Ressource als Wert der Eigenschaft.

  • Animieren Sie die Eigenschaft.

Wenn Sie möchten, dass eine Eigenschaft des Steuerelements diese Funktionalität unterstützt, sollten Sie sie als Abhängigkeitseigenschaft implementieren. Im folgenden Beispiel wird eine Abhängigkeitseigenschaft mit dem Namen Value definiert, indem wie folgt vorgegangen wird:

  • Ein DependencyProperty-Bezeichner mit dem Namen ValueProperty wird als public static readonly-Feld definiert.

  • Der Eigenschaftenname wird beim Eigenschaftensystem registriert, indem DependencyProperty.Register aufgerufen wird, um Folgendes anzugeben:

    • Name der Eigenschaft.

    • Typ der Eigenschaft.

    • Typ, der die Eigenschaft besitzt.

    • Die Metadaten für die Eigenschaft. Die Metadaten enthalten den Standardwert der Eigenschaft, einen CoerceValueCallback und einen PropertyChangedCallback.

  • Eine CLR-Wrappereigenschaft mit dem Namen Value wird definiert. Dies ist derselbe Name, der zum Registrieren der Abhängigkeitseigenschaft verwendet wird, indem die Accessoren get und set der Eigenschaft implementiert werden. Beachten Sie, dass die Accessoren get und set nur GetValue bzw. SetValue aufrufen. Es ist zu empfehlen, dass die Accessoren von Abhängigkeitseigenschaften keine zusätzliche Logik enthalten, da Clients und WPF die Accessoren umgehen und GetValue und SetValue direkt aufrufen können. Wenn eine Eigenschaft z. B. an eine Datenquelle gebunden ist, wird der set-Accessor der Eigenschaft nicht aufgerufen. Anstatt den Accessoren get und set zusätzliche Logik hinzuzufügen, sollten Sie die Delegaten ValidateValueCallback, CoerceValueCallback und PropertyChangedCallback verwenden, um auf den Wert zu reagieren bzw. den Wert zu prüfen, wenn dieser sich ändert. Weitere Informationen zu diesen Rückrufen finden Sie unter Rückrufe und Validierung von Abhängigkeitseigenschaften.

  • Definieren Sie eine Methode für den CoerceValueCallback mit dem Namen CoerceValue. CoerceValue stellt sicher, dass Value größer oder gleich MinValue und kleiner oder gleich MaxValue ist.

  • Definieren Sie eine Methode für den PropertyChangedCallback mit dem Namen OnValueChanged. OnValueChanged erstellt ein RoutedPropertyChangedEventArgs<T>-Objekt und bereitet das Auslösen des ValueChanged-Routingereignisses vor. Routingereignisse werden im nächsten Abschnitt erläutert.

        ''' <summary>
        ''' Identifies the Value dependency property.
        ''' </summary>
        Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Decimal), GetType(NumericUpDown), New FrameworkPropertyMetadata(MinValue, New PropertyChangedCallback(AddressOf OnValueChanged), New CoerceValueCallback(AddressOf CoerceValue)))

        ''' <summary>
        ''' Gets or sets the value assigned to the control.
        ''' </summary>
        Public Property Value() As Decimal
            Get
                Return CDec(GetValue(ValueProperty))
            End Get
            Set(ByVal value As Decimal)
                SetValue(ValueProperty, value)
            End Set
        End Property

        Private Shared Overloads Function CoerceValue(ByVal element As DependencyObject, ByVal value As Object) As Object
            Dim newValue As Decimal = CDec(value)
            Dim control As NumericUpDown = CType(element, NumericUpDown)

            newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue))

            Return newValue
        End Function

        Private Shared Sub OnValueChanged(ByVal obj As DependencyObject, ByVal args As DependencyPropertyChangedEventArgs)
            Dim control As NumericUpDown = CType(obj, NumericUpDown)

            Dim e As New RoutedPropertyChangedEventArgs(Of Decimal)(CDec(args.OldValue), CDec(args.NewValue), ValueChangedEvent)
            control.OnValueChanged(e)
        End Sub
/// <summary>
/// Identifies the Value dependency property.
/// </summary>
public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register(
        "Value", typeof(decimal), typeof(NumericUpDown),
        new FrameworkPropertyMetadata(MinValue, new PropertyChangedCallback(OnValueChanged),
                                      new CoerceValueCallback(CoerceValue)));

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

private static object CoerceValue(DependencyObject element, object value)
{
    decimal newValue = (decimal)value;
    NumericUpDown control = (NumericUpDown)element;

    newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue));

    return newValue;
}

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

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

Weitere Informationen finden Sie unter Benutzerdefinierte Abhängigkeitseigenschaften.

Verwenden von Routingereignissen

Ähnlich wie Abhängigkeitseigenschaften, die das Konzept der CLR-Eigenschaften um zusätzliche Funktionen erweitern, erweitern Routingereignisse das Konzept der CLR-Standardereignisse. Beim Erstellen eines neuen WPF-Steuerelements ist es außerdem ratsam, das Ereignis als Routingereignis zu implementieren, da ein Routingereignis das folgende Verhalten unterstützt:

  • Ereignisse können über ein übergeordnetes Element mehrerer Steuerelemente behandelt werden. Wenn es sich bei einem Ereignis um ein Bubbling-Ereignis handelt, kann ein einzelnes übergeordnetes Element in der Elementstruktur das Ereignis abonnieren. Dann können Anwendungsentwickler einen Handler verwenden, um auf das Ereignis mehrerer Steuerelemente zu reagieren. Wenn das Steuerelement z. B. jeweils Teil der Elemente in einem ListBox-Objekt ist (weil es in einer DataTemplate enthalten ist), kann der Anwendungsentwickler den Ereignishandler für das Ereignis des betreffenden Steuerelements unter ListBox definieren. Der Ereignishandler wird jeweils aufgerufen, wenn das Ereignis für eines der Steuerelemente auftritt.

  • Sie können Routingereignisse in einem EventSetter verwenden, damit Anwendungsentwickler den Handler eines Ereignisses innerhalb eines Stils angeben können.

  • Sie können Routingereignisse in einem EventTrigger verwenden. Dies ist hilfreich beim Animieren von Eigenschaften mit XAML. Weitere Informationen finden Sie unter Übersicht über Animationen.

Im folgenden Beispiel wird ein Routingereignis definiert, indem wie folgt vorgegangen wird:

  • Definieren Sie einen RoutedEvent-Bezeichner mit dem Namen ValueChangedEvent als public static readonly-Feld.

  • Das Routingereignis wird registriert, indem die EventManager.RegisterRoutedEvent-Methode aufgerufen wird. Im Beispiel werden beim Aufrufen von RegisterRoutedEvent die folgenden Informationen angegeben:

    • Der Name des Ereignisses lautet ValueChanged.

    • Die Routingstrategie ist Bubble. Dies bedeutet, dass zuerst ein Ereignishandler auf der Quelle (Objekt, das das Ereignis auslöst) aufgerufen wird. Danach werden nacheinander die Ereignishandler der übergeordneten Elemente der Quelle aufgerufen, wobei mit dem Ereignishandler des jeweils nächsten übergeordneten Elements begonnen wird.

    • Der Typ des Ereignishandlers ist RoutedPropertyChangedEventHandler<T>, der mit einem Decimal-Typ erstellt wird.

    • Der besitzende Typ des Ereignisses ist NumericUpDown.

  • Ein öffentliches Ereignis mit dem Namen ValueChanged wird deklariert, wobei Ereignisaccessordeklarationen eingeschlossen werden. Im Beispiel wird AddHandler in der add-Accessordeklaration und RemoveHandler in der remove-Accessordeklaration aufgerufen, um die WPF-Ereignisdienste zu verwenden.

  • Eine geschützte virtuelle Methode mit dem Namen OnValueChanged wird erstellt, die das ValueChanged-Ereignis auslöst.

        ''' <summary>
        ''' Identifies the ValueChanged routed event.
        ''' </summary>
        Public Shared ReadOnly ValueChangedEvent As RoutedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, GetType(RoutedPropertyChangedEventHandler(Of Decimal)), GetType(NumericUpDown))

        ''' <summary>
        ''' Occurs when the Value property changes.
        ''' </summary>
        Public Custom Event ValueChanged As RoutedPropertyChangedEventHandler(Of Decimal)
            AddHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
                MyBase.AddHandler(ValueChangedEvent, value)
            End AddHandler
            RemoveHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
                MyBase.RemoveHandler(ValueChangedEvent, value)
            End RemoveHandler
            RaiseEvent(ByVal sender As System.Object, ByVal e As RoutedPropertyChangedEventArgs(Of Decimal))
            End RaiseEvent
        End Event

        ''' <summary>
        ''' Raises the ValueChanged event.
        ''' </summary>
        ''' <param name="args">Arguments associated with the ValueChanged event.</param>
        Protected Overridable Sub OnValueChanged(ByVal args As RoutedPropertyChangedEventArgs(Of Decimal))
            MyBase.RaiseEvent(args)
        End Sub
/// <summary>
/// Identifies the ValueChanged routed event.
/// </summary>
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
    "ValueChanged", RoutingStrategy.Bubble, 
    typeof(RoutedPropertyChangedEventHandler<decimal>), typeof(NumericUpDown));

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

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

Weitere Informationen finden Sie unter Übersicht über Routingereignisse und unter Gewusst wie: Erstellen eines benutzerdefinierten Routingereignisses.

Verwenden der Bindung

Um die Benutzeroberfläche des Steuerelements von seiner Logik zu entkoppeln, können Sie erwägen, die Datenbindung zu verwenden. Dies ist besonders wichtig, wenn Sie die Darstellung des Steuerelements mithilfe einer ControlTemplate definieren. Wenn Sie die Datenbindung verwenden, ist es ggf. nicht mehr erforderlich, auf bestimmte Teile der Benutzeroberfläche zu verweisen. Vermeiden Sie Verweise auf Elemente in der ControlTemplate. Wenn der Code auf Elemente in der ControlTemplate verweist und die ControlTemplate geändert wird, muss das Element, auf das verwiesen wird, in die neue ControlTemplate aufgenommen werden.

Im folgenden Beispiel wird der TextBlock des NumericUpDown-Steuerelements aktualisiert, indem ein Name zugewiesen und im Code auf das Textfeld verwiesen wird.

<Border BorderThickness="1" BorderBrush="Gray" Margin="2" 
        Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">
  <TextBlock Name="valueText" Width="60" TextAlignment="Right" Padding="5"/>
</Border>
        Private Sub UpdateTextBlock()
            valueText.Text = Value.ToString()
        End Sub
private void UpdateTextBlock()
{
    valueText.Text = Value.ToString();
}

Im folgenden Beispiel wird die Bindung verwendet, um dasselbe Ziel zu erreichen.

<Border BorderThickness="1" BorderBrush="Gray" Margin="2" 
        Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">

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

</Border>

Weitere Informationen über Datenbindung finden Sie unter Übersicht über Datenbindung.

Entwerfen für Designer

Führen Sie die folgenden Schritte aus, um eine Unterstützung für benutzerdefinierte WPF-Steuerelemente in WPF Designer für Visual Studio zu erhalten (beispielsweise das Bearbeiten von Eigenschaften mit dem Eigenschaftenfenster). Weitere Informationen zur WPF-Designer-Entwicklung finden Sie unter WPF-Designer.

Abhängigkeitseigenschaften

Stellen Sie sicher, dass Sie die CLR-Accessoren get und set implementieren. Dies ist weiter oben unter "Verwenden von Abhängigkeitseigenschaften" beschrieben. Designer können den Wrapper verwenden, um das Vorhandensein einer Abhängigkeitseigenschaft zu erkennen. Genau wie WPF und Clients des Steuerelements auch, müssen diese beim Abrufen oder Festlegen der Eigenschaft jedoch nicht die Accessoren aufrufen.

Angefügte Eigenschaften

Sie sollten angefügte Eigenschaften für benutzerdefinierte Steuerelemente anhand der folgenden Richtlinien implementieren:

  • Verwenden Sie eine public static readonly-DependencyProperty der Form EigenschaftennameProperty, die mithilfe der RegisterAttached-Methode erstellt wurde. Der Eigenschaftenname, der an RegisterAttached übergeben wird, muss mit dem PropertyName übereinstimmen.

  • Implementieren Sie ein publicstatic-CLR-Methodenpaar mit den Namen SetPropertyName und GetPropertyName. Beide Methoden sollten eine von DependencyProperty abgeleitete Klasse als erstes Argument akzeptieren. Die SetPropertyName-Methode akzeptiert auch ein Argument, dessen Typ zum registrierten Datentyp für die Eigenschaft passt. Die GetPropertyName-Methode sollte einen Wert des gleichen Typs zurückgeben. Wenn die SetEigenschaftenname-Methode fehlt, wird die Eigenschaft als schreibgeschützt gekennzeichnet.

  • SetEigenschaftsname und GetEigenschaftsname müssen direkt an die GetValue-Methode bzw. die SetValue-Methode im Zielabhängigkeitsobjekt weiterleiten. Designer können auf die angefügte Eigenschaft zugreifen, indem sie einen Aufruf über den Methodenwrapper durchführen oder indem sie einen direkten Aufruf an das Zielabhängigkeitsobjekt ausführen.

Weitere Informationen zu angefügten Eigenschaften finden Sie unter Übersicht über angefügte Eigenschaften.

Definieren und Verwenden von freigegebenen Ressourcen

Sie können das Steuerelement in die Assembly der Anwendung oder in eine separate Assembly einbinden, die in mehreren Anwendungen verwendet werden kann. Der Großteil der in diesem Thema erläuterten Informationen gelten unabhängig von der Methode, die Sie verwenden. Sie müssen jedoch einen Unterschied beachten. Wenn Sie ein Steuerelement in die Assembly einer Anwendung einbinden, können Sie der Datei App.xaml globale Ressourcen hinzufügen. Einer Assembly, die nur Steuerelemente enthält, ist jedoch kein Application-Objekt zugeordnet, sodass keine App.xaml-Datei verfügbar ist.

Wenn eine Anwendung nach einer Ressource sucht, erfolgt die Suche auf drei Ebenen in der folgenden Reihenfolge:

  1. Die Elementebene.

    Das System beginnt mit dem Element, das auf die Ressource verweist, darauf folgen die Ressourcen des logischen übergeordneten Elements usw., bis das Stammelement erreicht ist.

  2. Die Anwendungsebene.

    Mit dem Application-Objekt definierte Ressourcen.

  3. Die Designebene.

    Wörterbücher auf Designebene werden im Unterordner Themes gespeichert. Die Dateien im Ordner Themes entsprechen den Designs. Beispiel: Aero.NormalColor.xaml, Luna.NormalColor.xaml, Royale.NormalColor.xaml usw. Der Ordner kann auch die Datei generic.xaml enthalten. Die Suche nach einer Ressource auf Designebene erfolgt zunächst in der designspezifischen Datei und dann in der Datei generic.xaml.

Wenn sich das Steuerelement in einer separaten Assembly befindet, speichern Sie globale Ressourcen auf Element- oder Designebene. Beide Methoden haben ihre Vorteile.

Definieren von Ressourcen auf Elementebene

Sie können freigegebene Ressourcen auf Elementebene definieren, indem Sie ein benutzerdefiniertes Ressourcenwörterbuch erstellen und mit dem Ressourcenwörterbuch des Steuerelements zusammenführen. Bei dieser Methode können Sie der Ressourcendatei einen beliebigen Namen zuweisen und sie im gleichen Ordner speichern wie die Steuerelemente. Für Ressourcen auf Elementebene können auch einfache Zeichenfolgen als Schlüssel verwendet werden. Im folgenden Beispiel wird eine LinearGradientBrush-Ressourcendatei mit dem Namen Dictionary1.xaml erstellt.

<ResourceDictionary 
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">
  <LinearGradientBrush 
    x:Key="myBrush"  
    StartPoint="0,0" EndPoint="1,1">
    <GradientStop Color="Red" Offset="0.25" />
    <GradientStop Color="Blue" Offset="0.75" />
  </LinearGradientBrush>

</ResourceDictionary>

Nachdem Sie das Wörterbuch definiert haben, müssen Sie es mit dem Ressourcenwörterbuch des Steuerelements zusammenführen. Hierzu können Sie XAML oder Code verwenden.

Im folgenden Beispiel wird ein Ressourcenwörterbuch mit XAML zusammengeführt.

<UserControl.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Dictionary1.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</UserControl.Resources>

Der Nachteil bei dieser Vorgehensweise besteht darin, dass jedes Mal, wenn Sie darauf verweisen, ein ResourceDictionary-Objekt erstellt wird. Beispiel: Wenn Ihre Bibliothek zehn benutzerdefinierte Steuerelemente enthält und Sie die freigegebenen Ressourcenwörterbücher für jedes Steuerelement mit XAML zusammenführen, werden zehn identische ResourceDictionary-Objekte erstellt. Sie können dies verhindern, indem Sie eine statische Klasse erstellen, die die Ressourcen in Code zusammenführt und das ResourceDictionary ausgibt.

Im folgenden Beispiel wird eine Klasse erstellt, die ein freigegebenes ResourceDictionary zurückgibt.

internal static class SharedDictionaryManager
{
    internal static ResourceDictionary SharedDictionary
    {
        get
        {
            if (_sharedDictionary == null)
            {
                System.Uri resourceLocater =
                    new System.Uri("/ElementResourcesCustomControlLibrary;component/Dictionary1.xaml", 
                                    System.UriKind.Relative);

                _sharedDictionary = 
                    (ResourceDictionary)Application.LoadComponent(resourceLocater);
            }

            return _sharedDictionary;
        }
    }

    private static ResourceDictionary _sharedDictionary;
}

Im folgenden Beispiel wird die freigegebene Ressource mit den Ressourcen eines benutzerdefinierten Steuerelements im Konstruktur des Steuerelements zusammengeführt, bevor InitilizeComponent aufgerufen wird. Da das SharedDictionaryManager.SharedDictionary eine statische Eigenschaft ist, wird das ResourceDictionary nur einmal erstellt. Da das Ressourcenwörterbuch vor dem Aufruf von InitializeComponent zusammengeführt wurde, sind die Ressourcen für das Steuerelement in der XAML-Datei zugänglich.

public NumericUpDown()
{
    this.Resources.MergedDictionaries.Add(SharedDictionaryManager.SharedDictionary);
    InitializeComponent();

}

Definieren von Ressourcen auf Designebene

WPF ermöglicht Ihnen, Ressourcen für andere Windows-Designs zu erstellen. Als Steuerelementautor können Sie eine Ressource für ein bestimmtes Design definieren, um die Darstellung des Steuerelements an das jeweils verwendete Design anzupassen. Beispiel: Die Button-Darstellung im Design Windows - klassisch (das Standardesign in Windows 2000) unterscheidet sich von der Button-Darstellung im Windows XP-Design, da für Button für jedes Design eine andere ControlTemplate verwendet wird.

Designspezifische Ressourcen werden in einem Ressourcenwörterbuch mit einem entsprechenden Dateinamen gespeichert. Diese Dateien müssen sich im Verzeichnis Themes befinden, einem Unterverzeichnis des Ordners, der das Steuerelement enthält. In der folgenden Tabelle werden die Ressourcenwörterbuchdateien und die den einzelnen Dateien zugeordneten Designs aufgeführt:

Name der Ressourcenwörterbuchdatei

Windows-Design

Classic.xaml

Klassische Windows 9x/2000-Darstellung unter Windows XP

Luna.NormalColor.xaml

Blaues Design unter Windows XP (Standard)

Luna.Homestead.xaml

Olivgrünes Design unter Windows XP

Luna.Metallic.xaml

Silberdesign unter Windows XP

Royale.NormalColor.xaml

Standarddesign von Windows XP Media Center Edition

Aero.NormalColor.xaml

Standarddesign unter Windows Vista

Sie müssen nicht für jedes Design eine Ressource definieren. Wenn eine Ressource nicht für ein bestimmtes Design definiert ist, sucht das Steuerelement in Classic.xaml nach der Ressource. Ist die Ressource nicht in der Datei für das aktuelle Design oder Classic.xaml definiert, verwendet das Steuerelement die generische Ressource, die in einer Ressourcenwörterbuchdatei mit dem Namen generic.xaml enthalten ist. Die Datei generic.xaml befindet sich im gleichen Ordner wie die designspezifischen Ressourcenwörterbuchdateien. Obwohl generic.xaml keinem bestimmten Windows-Design entspricht, handelt es sich doch um ein Wörterbuch auf Designebene.

Beispiel für benutzerdefiniertes NumericUpDown-Steuerelement mit Unterstützung von Designs und Benutzeroberflächenautomatisierung enthält zwei Ressourcenwörterbücher für das NumericUpDown-Steuerelement: eines in der Datei "generic.xaml" und eines in der Datei "Luna.NormalColor.xaml". Führen Sie die Anwendung aus, und wechseln Sie zwischen dem Silberdesign für Windows XP und einem anderen Design, um den Unterschied zwischen den beiden Steuerelementvorlagen zu sehen. (Unter Windows Vista können Sie die Datei Luna.NormalColor.xaml in Aero.NormalColor.xaml umbenennen und zwischen zwei Designs wechseln, z. B. zwischen Windows - klassisch und dem Standarddesign für Windows Vista.)

Wenn Sie eine ControlTemplate in eine der designspezifischen Ressourcenwörterbuchdateien einfügen, müssen Sie einen statischen Konstruktor für das Steuerelement erstellen und die OverrideMetadata(Type, PropertyMetadata)-Methode für den DefaultStyleKey aufrufen, wie im folgenden Beispiel gezeigt.

        Shared Sub New()
            DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(GetType(NumericUpDown)))
        End Sub
static NumericUpDown()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown),
               new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}

Definieren und Angeben von Schlüsseln für Designressourcen

Wenn Sie eine Ressource auf Elementebene definieren, können Sie ihr eine Zeichenfolge als Schlüssel zuweisen und über diese Zeichenfolge auf die Ressource zugreifen. Wenn Sie auf Designebene eine Ressource definieren, müssen Sie einen ComponentResourceKey als Schlüssel verwenden. Im folgenden Beispiel wird eine Ressource in generic.xaml definiert.

<LinearGradientBrush 
     x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:Painter}, 
                                  ResourceId=MyEllipseBrush}"  
                                  StartPoint="0,0" EndPoint="1,0">
    <GradientStop Color="Blue" Offset="0" />
    <GradientStop Color="Red" Offset="0.5" />
    <GradientStop Color="Green" Offset="1"/>
</LinearGradientBrush>

Das folgende Beispiel verweist auf die Ressource, indem ComponentResourceKey als Schlüssel angegeben wird.

<RepeatButton 
    Grid.Column="1" Grid.Row="0"
    Background="{StaticResource {ComponentResourceKey 
                        TypeInTargetAssembly={x:Type local:NumericUpDown}, 
                        ResourceId=ButtonBrush}}">
    Up
</RepeatButton>
<RepeatButton 
    Grid.Column="1" Grid.Row="1"
    Background="{StaticResource {ComponentResourceKey 
                    TypeInTargetAssembly={x:Type local:NumericUpDown}, 
                    ResourceId=ButtonBrush}}">
    Down
 </RepeatButton>

Angeben des Speicherorts von Designressourcen

Um die Ressourcen für ein Steuerelement finden zu können, muss die Hostanwendung über die Information verfügen, dass die Assembly steuerelementspezifische Ressourcen enthält. Fügen Sie hierzu der Assembly, die das Steuerelement enthält, das ThemeInfoAttribute hinzu. Das ThemeInfoAttribute weist eine GenericDictionaryLocation-Eigenschaft zur Angabe des Speicherorts allgemeiner Ressourcen sowie eine ThemeDictionaryLocation-Eigenschaft zur Angabe des Speicherorts designspezifischer Ressourcen.

Im folgenden Beispiel werden die GenericDictionaryLocation-Eigenschaft und die ThemeDictionaryLocation-Eigenschaft auf SourceAssembly gesetzt, um anzugeben, dass sich die allgemeinen und die designspezifischen Ressourcen in derselben Assembly befinden wie das Steuerelement.

<Assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly, ResourceDictionaryLocation.SourceAssembly)>
[assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly, 
           ResourceDictionaryLocation.SourceAssembly)]

Siehe auch

Konzepte

Paket-URI in WPF

Weitere Ressourcen

WPF-Designer

Anpassung von Steuerelementen