Übersicht über das Erstellen von Steuerelementen

Die Erweiterbarkeit des Windows Presentation Foundation-Steuerelementmodells (WPF) reduziert die Notwendigkeit erheblich, ein neues Steuerelement erstellen zu müssen. In bestimmten Fällen lässt sich das Erstellen benutzerdefinierter Steuerelemente dennoch nicht vermeiden. In diesem Thema werden die Funktionen, dank deren Sie auf das Erstellen neuer Steuerelementen in den meisten Fällen verzichten können, sowie verschiedene Modelle zum Erstellen von Steuerelementen in Windows Presentation Foundation (WPF) behandelt. Außerdem wird in diesem Thema auch das Erstellen eines neuen Steuerelements veranschaulicht.

Alternativen zum Erstellen eines neuen Steuerelements

In der Vergangenheit waren Sie bei der Anpassung eines vorhandenen Steuerelements auf die Bearbeitung seiner Standardeigenschaften beschränkt. Sie konnten z.B. die Hintergrundfarbe, die Rahmenbreite sowie die Schriftgröße ändern. Falls Sie die Darstellung oder das Verhalten eines Steuerelements über diese vordefinierten Parameter hinaus erweitern wollten, waren Sie auf das Erstellen eines neuen Steuerelements angewiesen. Die übliche Vorgehensweise war die Vererbung von einem vorhandenen Steuerelement samt der Überschreibung der für das Zeichnen des Steuerelements zuständigen Methode. Diese Option steht Ihnen zwar weiterhin zur Verfügung, mit dem reichen Inhaltsmodel von WPF sowie seinen Stilen, Vorlagen und Triggern können Sie jedoch die vorhandenen Steuerelemente auf eine vielfältige Art und Weise anzupassen. In der folgenden Liste finden Sie Beispiele für die Verwendung dieser Funktionen zum Erstellen einer benutzerdefinierten und konsistenten Umgebung, ohne ein neues Steuerelement erstellen zu müssen.

  • Multimediainhalte. Viele WPF-Standardsteuerelemente unterstützen Multimediainhalte. Die Inhaltseigenschaft einer Button-Instanz ist z. B. vom Typ Object, sodass theoretisch alles auf einer Button-Instanz angezeigt werden kann. Damit eine Schaltfläche ein Bild und einen Text anzeigt, können Sie ein Bild und einen TextBlock zu einer StackPanel-Instanz hinzufügen und die StackPanel-Instanz zur Content-Eigenschaft zuweisen. Da die Steuerelemente visuelle WPF-Elemente sowie beliebige Daten anzeigen können, ist es zwecks der Unterstützung einer komplexen Visualisierung selten notwendig, neue Steuerelemente zu erstellen oder vorhandene Steuerelemente zu ändern. Weitere Informationen über das Inhaltsmodell für Button und andere Inhaltsmodelle in WPF finden Sie unter WPF-Inhaltsmodell.

  • Stile Ein Style ist eine Sammlung von Werten, die Eigenschaften für ein Steuerelement darstellen. Mit Stilen können Sie eine wiederverwendbare Darstellung der erwünschten Gestaltung und des erwünschten Verhaltens der Steuerelemente erstellen, ohne ein neues Steuerelement erstellen zu müssen. Nehmen wir z. B. an, Sie möchten, dass alle Ihre TextBlock-Steuerelemente in roter Arial-Schrift mit einer Schriftgröße von 14 dargestellt werden. Sie können einen Stil als eine Ressource anlegen und die jeweiligen Eigenschaften entsprechend festlegen. Dann verfügt jeder TextBlock, den Sie Ihrer Anwendung hinzufügen, über dieselbe Darstellung.

  • Datenvorlagen Eine DataTemplate ermöglicht es Ihnen, die Anzeige von Daten in einem Steuerelement anzupassen. Beispielsweise kann eine DataTemplate verwendet werden, um festzulegen, wie die Daten in einem ListBox angezeigt werden. Weitere Beispiele finden Sie unter Übersicht über Datenvorlagen. Zusätzlich zur Anpassung der Darstellung der Daten kann eine DataTemplate-Instanz Benutzeroberflächenelemente enthalten. Somit können Sie beim Erstellen benutzerdefinierter Oberflächen flexibel vorgehen. Verwenden Sie z. B. eine DataTemplate, um eine ComboBox-Instanz zu erstellen, bei der jedes Element ein Kontrollkästchen enthält.

  • Steuerelementvorlagen Viele Steuerelemente in WPF verwenden eine ControlTemplate, um die Struktur und die Darstellung des Steuerelements zu definieren, wodurch die Darstellung eines Steuerelements von der Funktionalität des Steuerelements getrennt wird. Sie können die Darstellung eines Steuerelements drastisch ändern, indem Sie seine ControlTemplate neu definieren. Nehmen wir z.B. an, dass Sie ein Steuerelement wie eine Ampel aussehen lassen möchten. Dieses Steuerelement hat eine einfache Benutzeroberfläche und Funktionalität. Das Steuerelement besteht aus drei Kreisen, von denen jeweils nur ein einziger leuchten kann. Nach einiger Überlegung werden Sie vielleicht feststellen, dass eine RadioButton die Funktionalität bietet, dass immer nur eine Option ausgewählt werden kann, aber die Standarddarstellung der RadioButton sieht nicht aus wie die Lichter einer Ampel. Da die RadioButton eine Steuerelementvorlage verwendet, um ihre Darstellung zu definieren, ist es einfach, die ControlTemplate neu zu definieren, um sie an die Anforderungen des Steuerelements anzupassen und Optionsfelder zu verwenden, um Ihre Ampel zu erstellen.

    Hinweis

    Obwohl eine RadioButton eine DataTemplate verwenden kann, ist eine DataTemplate in diesem Beispiel nicht ausreichend. Die DataTemplate definiert die Darstellung des Inhalts eines Steuerelements. Im Falle einer RadioButton ist der Inhalt das, was rechts neben dem Kreis angezeigt wird und angibt, ob die RadioButton ausgewählt ist. Im Beispiel der Ampel muss das Optionsfeld nur ein Kreis sein, der „leuchten“ kann. Da die Anforderungen an das Darstellung der Ampel so unterschiedlich sind, dass sie sich von der Standarddarstellung der RadioButton, unterscheiden, ist es notwendig, die ControlTemplate-Instanz neu zu definieren. Im Allgemeinen wird eine DataTemplate verwendet, um den Inhalt (oder die Daten) eines Steuerelements zu definieren, und eine ControlTemplate wird verwendet, um festzulegen, wie ein Steuerelement strukturiert ist.

  • Trigger Mit einem Trigger können Sie die Darstellung und das Verhalten eines Steuerelements dynamisch ändern, ohne ein neues Steuerelement zu erstellen. Nehmen wir z. B. an, Sie haben mehrere ListBox-Steuerelemente in Ihrer Anwendung und möchten, dass die Elemente in jedem ListBox fett und rot dargestellt werden, wenn sie ausgewählt werden. Ihr erster Instinkt könnte sein, eine Klasse zu erstellen, die von ListBox erbt, und die OnSelectionChanged-Methode außer Kraft setzen, um die Darstellung des ausgewählten Elements zu ändern. Ein besserer Ansatz ist jedoch, einen Trigger zu einem Stil einer ListBoxItem-Instanz hinzuzufügen, der die Darstellung des ausgewählten Elements ändert. Mit Triggern können Sie Eigenschaftswerte bearbeiten oder basierend auf den Eigenschaftswerten Aktionen ausführen. Ein EventTrigger ermöglicht es Ihnen, Aktionen durchzuführen, wenn ein Ereignis eintritt.

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

Im Allgemeinen gilt: wenn das benötigte Steuerelement die Funktionalität eines vorhandenen Steuerelements nachmacht, aber anders aussehen soll, lohnt es sich zuerst zu überlegen, ob Sie mit einem der in diesem Abschnitt beschriebenen Verfahren die vorhandene Darstellung des Steuerelements ändern können.

Modelle für das Erstellen von Steuerelementen

Dank dem umfangreichen Inhaltsmodell sowie Stilen, Vorlagen und Triggern können Sie in den meisten Fällen auf das Erstellen neuer Steuerelemente verzichten. Falls Sie jedoch ein neues Steuerelement erstellen müssen, ist es wichtig Modelle für das Erstellen von Steuerelementen in WPF zu verstehen. WPF bietet drei allgemeine Modelle zum Erstellen der Steuerelemente, die jeweils verschiedene Funktionen und Flexibilitätsgrade bieten. Die Basisklassen für die drei Modelle sind UserControl, Control und FrameworkElement.

Ableiten von UserControl

Der einfachste Weg, ein Steuerelement in WPF zu erstellen, ist die Ableitung von UserControl. Wenn Sie ein Steuerelement erstellen, das von UserControl erbt, fügen Sie vorhandene Komponenten zur UserControl-Instanz hinzu, benennen die Komponenten und verweisen auf Ereignishandler in XAML. Anschließend können Sie auf die benannten Elemente verweisen und die Ereignishandler im Code definieren. Dieses Entwicklungsmodell ähnelt sehr dem Modell für die Anwendungsentwicklung in WPF.

Wenn Sie es richtig erstellen, kann eine UserControl-Instanz die Vorteile von Multimediainhalten, Stilen und Triggern nutzen. Wenn Ihr Steuerelement jedoch von UserControl erbt, können Personen, die Ihr Steuerelement verwenden, keine DataTemplate oder ControlTemplate verwenden, um seine Darstellung anzupassen. Um ein benutzerdefiniertes Steuerelement zu erstellen, das Vorlagen unterstützt, müssen Sie von der Control-Klasse oder einer ihrer abgeleiteten Klassen (außer UserControl) ableiten.

Vorteile des Ableitens von UserControl

Ziehen Sie eine Ableitung von UserControl in Betracht, wenn alle folgenden Punkte zutreffen:

  • Sie möchten das Steuerelement auf eine ähnliche Art und Weise erstellen, wie Sie Ihre Anwendung erstellt haben.

  • Ihr Steuerelement besteht ausschließlich aus vorhandenen Komponenten.

  • Es muss keine komplexe Anpassung unterstützt werden.

Ableiten von Control

Die meisten WPF-Steuerelemente verwenden die Ableitung von der Control-Klasse. Wenn Sie ein Steuerelement erstellen, das von der Control-Klasse erbt, definieren Sie seine Darstellung mithilfe von Vorlagen. Dadurch trennen Sie die Funktionslogik von der visuellen Darstellung. Das Entkoppeln von der Benutzeroberfläche und der Logik können Sie auch gewährleisten, indem Sie Befehle und Bindungen anstelle von Ereignissen verwenden, und Verweise auf Elemente in der ControlTemplate-Vorlage nach Möglichkeit vermeiden. Wenn die Benutzeroberfläche und die Logik Ihres Steuerelements ordnungsgemäß entkoppelt sind, kann ein Benutzer Ihres Steuerelements die ControlTemplate des Steuerelements neu definieren, um ihre Darstellung anzupassen. Obwohl das Erstellen einer benutzerdefinierten Control-Instanz nicht so einfach ist wie das Erstellen einer UserControl-Instanz, bietet eine benutzerdefinierte Control-Instanz die größte Flexibilität.

Vorteile des Ableitens von Control

Erwägen Sie die Ableitung von Control anstelle der Verwendung der UserControl-Klasse, wenn einer der folgenden Punkte zutrifft:

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

  • Das Steuerelement soll verschiedene Designs unterstützen.

Ableitung von FrameworkElement

Steuerelemente, die von UserControl oder Control abgeleitet werden, beruhen auf der Zusammenstellung vorhandener Elemente. Für viele Szenarien ist dies eine akzeptable Lösung, denn jedes Objekt, das von FrameworkElement erbt, kann sich in einer ControlTemplate befinden. Es gibt jedoch Situationen, in denen die Darstellung eines Steuerelements mehr als die Funktionalität einer einfachen Elementzusammensetzung erfordert. Für diese Szenarien ist die Verwendung einer FrameworkElement-Instanz als Basis einer Komponente die richtige Wahl.

Es gibt zwei Standardmethoden zum Erstellen von FrameworkElement-basierten Komponenten: direktes Rendering und benutzerdefinierte Elementzusammensetzung. Beim direkten Rendering wird die OnRender-Methode von FrameworkElement außer Kraft gesetzt und es werden DrawingContext-Vorgänge bereitgestellt, die die visuellen Elemente der Komponente explizit definieren. Dies ist die Methode, die von Image und Border verwendet wird. Bei der benutzerdefinierten Elementzusammensetzung verwenden Sie Objekte des Typs Visual, um die Darstellung Ihrer Komponente zusammenzustellen. Beispiele finden Sie unter Verwenden von DrawingVisual-Objekten. Track ist ein Beispiel für ein Steuerelement in WPF, das eine benutzerdefinierte Elementzusammensetzung verwendet. Sie können auch direktes Rendering und benutzerdefinierte Elementzusammensetzung innerhalb eines Steuerelements kombinieren.

Vorteile der Ableitung von FrameworkElement

Ziehen Sie eine Ableitung von FrameworkElement in Betracht, wenn einer der folgenden Punkte zutrifft:

  • Sie brauchen eine genaue Steuerung der Darstellung Ihres Steuerelements, die darüber hinausgeht, was mit einer einfachen Elementzusammensetzung möglich ist.

  • Sie möchten die Darstellung Ihres Steuerelements definieren, indem Sie Ihre eigene Renderinglogik definieren.

  • Sie möchten vorhandene Elemente auf neuartige Weise zusammenstellen, die über das hinausgeht, was mit UserControl und Control möglich ist.

Grundlagen des Erstellens von Steuerelementen

Wie bereits erläutert, ist eine der leistungsstärksten Funktionen von WPF die Möglichkeit, über die Festlegung grundlegender Eigenschaften eines Steuerelements zur Bearbeitung seiner Darstellung und seines 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, um Benutzern Ihres benutzerdefinierten Steuerelements das Zugreifen auf diese Funktionen auf die gleiche Art und Weise zu ermöglichen, wie es bei einem in WPF enthaltenen Steuerelement möglich wäre.

Verwenden von Abhängigkeitseigenschaften

Falls es sich bei einer Eigenschaft um eine Abhängigkeitseigenschaft handelt, können Sie folgendermaßen vorgehen:

  • Die Eigenschaft in einem Stil festlegen.

  • Die Eigenschaft an eine Datenquelle binden.

  • Eine dynamische Ressource als den Wert der Eigenschaft verwenden.

  • Die Eigenschaft animieren.

Falls Sie möchten, dass eine Eigenschaft Ihres Steuerelements eine dieser Funktionen unterstützt, sollten Sie die Eigenschaft als eine Abhängigkeitseigenschaft implementieren. Im folgenden Beispiel wird eine Abhängigkeitseigenschaft mit dem Namen Value wie folgt definiert:

  • Definieren Sie einen DependencyProperty-Bezeichner namens ValueProperty als publicstaticreadonly Feld.

  • Registrieren Sie den Eigenschaftsnamen im Eigenschaftensystem, indem Sie DependencyProperty.Register, aufrufen, um Folgendes anzugeben:

    • Den Namen der Eigenschaft.

    • Den Typ der Eigenschaft.

    • Den Typ, der die Eigenschaft besitzt.

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

  • Eine CLR-Wrappereigenschaft mit dem Namen Value wird definiert. Es ist der gleiche Name, mit dem die Abhängigkeitseigenschaft registriert wurde, indem die get- und set-Zugriffmethoden der Eigenschaft implementiert wurden. Beachten Sie, dass die get- und set-Zugriffsmethoden nur GetValue bzw. SetValue aufrufen. Es wird empfohlen, dass die Zugriffsmethoden von Abhängigkeitseigenschaften keine zusätzliche Logik enthalten, da Clients und WPF die Zugriffsmethoden umgehen und GetValue sowie SetValue direkt aufrufen. Falls eine Eigenschaft z.B. an eine Datenquelle gebunden ist, wird die set-Zugriffmethode der Eigenschaft nicht aufgerufen. Anstatt zusätzliche Logik zu den Get- und Set-Zugriffsmethoden hinzuzufügen, verwenden Sie die Delegaten ValidateValueCallback, CoerceValueCallback, und PropertyChangedCallback, um auf den Wert zu reagieren oder ihn zu überprüfen, wenn er sich ändert. Weitere Informationen zu diesen Rückrufen finden Sie unter Rückrufe und Validierung von Abhängigkeitseigenschaften.

  • Definieren Sie eine Methode für die CoerceValueCallback-Instanz namens CoerceValue. CoerceValue stellt sicher, dass Value größer oder gleich MinValue und kleiner oder gleich MaxValue ist.

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

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

Weitere Informationen finden Sie unter Benutzerdefinierte Abhängigkeitseigenschaften.

Verwenden von Routingereignissen

Ebenso 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 empfiehlt es sich, Ihr Ereignis als ein Routingereignis zu implementieren, da Routingereignisse folgendes Verhalten unterstützen:

  • Ereignisse für mehrere Steuerelemente können über ein übergeordnetes Steuerelement behandelt werden. Falls es sich bei dem Ereignis um ein Bubbling-Ereignis handelt, kann ein einzelnes übergeordnetes Element in der Elementstruktur das Ereignis abonnieren. Anschließend können Anwendungsentwickler mit einem Handler auf das Ereignis mehrerer Steuerelemente reagieren. Wenn Ihr Steuerelement z. B. Teil eines jeden Elements in einem ListBox ist (da es in einer DataTemplate enthalten ist), kann der Entwickler der Anwendung den Ereignishandler für das Ereignis Ihres Steuerelements im ListBox definieren. Beim Auftreten des Ereignisses bei einem der Steuerelemente wird der Ereignishandler aufgerufen.

  • Routingereignisse können in einer EventSetter-Instanz verwendet werden, die es Anwendungsentwicklern ermöglicht, den Handler eines Ereignisses innerhalb eines Stils anzugeben.

  • Routingereignisse können in einer EventTrigger-Instanz verwendet werden, was für die Animation von Eigenschaften mithilfe von XAML nützlich ist. Weitere Informationen finden Sie unter Übersicht über Animation.

Im folgenden Beispiel wird ein Routingereignis wie folgt definiert:

  • Definieren Sie einen RoutedEvent-Bezeichner namens ValueChangedEvent als publicstaticreadonly Feld.

  • Registrieren Sie das Routingereignis, indem Sie die EventManager.RegisterRoutedEvent-Methode aufrufen. Das Beispiel gibt die folgenden Informationen an, wenn es RegisterRoutedEvent aufruft:

    • Der Name des Ereignisses lautet ValueChanged.

    • Die Routingstrategie ist die Bubble-Strategie, d. h., zuerst wird ein Ereignishandler in der Quelle (im Objekt, das das Ereignis auslöst) aufgerufen. Anschließend werden die Ereignishandler der übergeordneten Elemente der Quelle nacheinander aufgerufen. Es wird mit dem Ereignishandler des jeweils nächsten übergeordneten Elements begonnen.

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

    • NumericUpDown ist der besitzende Typ des Ereignisses.

  • Ein öffentliches Ereignis mit dem Namen ValueChanged, das die Zugriffsmethoden-Deklarationen umfasst, wird deklariert. Das Beispiel ruft AddHandler in der Deklaration der add-Zugriffsmethode und RemoveHandler in der Deklaration der remove-Zugriffsmethode auf, um die WPF-Ereignisdienste zu verwenden.

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

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

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

Verwendung von Bindungen

Erwägen Sie Datenbindungen, um die Benutzeroberfläche Ihres Steuerelements von der Logik zu entkoppeln. Dies ist besonders wichtig, wenn Sie die Darstellung Ihres Steuerelements mithilfe einer ControlTemplate-Instanz definieren. Bei der Verwendung einer Datenbindung können Sie u.U. auf die Verweise auf bestimmte Teile der Benutzeroberfläche aus dem Code verzichten. Es ist eine gute Idee, Verweise auf Elemente zu vermeiden, die sich in der ControlTemplate befinden, denn wenn der Code auf Elemente verweist, die sich in der ControlTemplate befinden und die ControlTemplate geändert wird, muss das referenzierte Element in die neue ControlTemplate einbezogen werden.

Das folgende Beispiel aktualisiert den TextBlock des NumericUpDown-Steuerelements, weist ihm einen Namen zu und verweist auf das Textfeld im Code über seinen Namen.

<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 void UpdateTextBlock()
{
    valueText.Text = Value.ToString();
}
Private Sub UpdateTextBlock()
    valueText.Text = Value.ToString()
End Sub

Im folgenden Beispiel wird eine Bindung für den selben Zweck verwendet.

<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 zu Datenbindungen finden Sie unter Übersicht über Datenbindung.

Darstellung für Designer

Um Support für benutzerdefinierte WPF-Steuerelemente in WPF-Designer für Visual Studio zu erhalten (z. B. bezüglich der Eigenschaftenbearbeitung mit dem Eigenschaftenfenster), befolgen Sie die folgenden Richtlinien. Weitere Informationen zur Entwicklung für den WPF-Designer finden Sie unter Entwerfen von XAML-Code in Visual Studio.

Abhängigkeitseigenschaften

Vergewissern Sie sich, dass Sie CLR get- und set-Zugriffsmethoden wie unter „Verwenden von Abhängigkeitseigenschaften“ beschrieben implementieren. Designer können den Wrapper verwenden, um das Vorhandensein einer Abhängigkeitseigenschaft zu erkennen, aber sie müssen, wie WPF und Clients des Steuerelements, die Zugriffsmethoden nicht aufrufen, wenn sie die Eigenschaft abrufen oder festlegen.

Angefügte Eigenschaften

Angefügte Eigenschaften für benutzerdefinierte Steuerelemente sind unter Einhaltung der folgenden Richtlinien zu implementieren:

  • Verwenden Sie eine publicstaticreadonlyDependencyProperty der Form PropertyNameProperty, die mithilfe der RegisterAttached-Methode verwendet wurde. Der Name der Eigenschaft, die an RegisterAttached übergeben wird, muss mit PropertyName übereinstimmen.

  • Implementieren Sie die publicstatic CLR-Methoden mit den Namen SetPropertyName und GetPropertyName. Beide Methoden sollten eine Klasse, die von DependencyProperty abgeleitet wurde, als erstes Argument akzeptieren. Die SetPropertyName-Methode akzeptiert auch ein Argument, dessen Typ mit dem registrierten Datentyp der Eigenschaft übereinstimmt. Die GetPropertyName-Methode sollte einen Wert zurückgeben, der den gleichen Typ aufweist. Falls die SetPropertyName-Methode fehlt, wird die Eigenschaft als schreibgeschützt gekennzeichnet.

  • SetPropertyName und GetPropertyName müssen direkt auf die Methoden GetValue und SetValue des Zielabhängigkeitsobjekts verweisen. Designer können auf die angefügte Eigenschaft entweder über einen Aufruf des Wrappers für die Methode zugreifen, oder indem sie das Zielabhängigkeitsobjekt direkt aufrufen.

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

Definieren und Verwenden von freigegebenen Ressourcen

Sie können Ihr Steuerelement entweder in die Assembly Ihrer Anwendung, oder in eine separate Assembly einbinden, die in mehreren Anwendungen verwendet werden kann. Der Großteil der in diesem Thema erläuterten Informationen gilt unabhängig von der von Ihnen verwendeten Methode. Ein wesentlicher Unterschied ist dennoch zu beachten. Falls Sie ein Steuerelement in die Assembly einer Anwendung einbinden, können Sie der App.xaml-Datei globale Ressourcen hinzufügen. Einer Assembly, die nur Steuerelemente enthält, wird jedoch kein Application-Objekt zugeordnet, sodass keine App.xaml-Datei verfügbar ist.

Wenn eine Anwendung nach einer Ressource sucht,wird auf drei Ebenen in der folgenden Reihenfolge gesucht:

  1. Die Elementebene.

    Das System beginnt mit dem Element, das auf die Ressource verweist, und sucht anschließend die Ressourcen des logischen übergeordneten Elements durch. Dieser Schritt wir wiederholt, bis das Stammelement erreicht ist.

  2. Die Anwendungsebene.

    Vom Application-Objekt definierte Ressourcen.

  3. Die Designebene.

    Ressourcenverzeichnisse werden auf der Designebene in einem Themes-Unterordner gespeichert. Die Dateien im Themes-Ordner entsprechen den Designs. Dort könnten Sie beispielsweise Aero.NormalColor.xaml, Luna.NormalColor.xaml, Royale.NormalColor.xaml, etc. finden. Möglicherweise ist auch eine Datei mit dem Namen generic.xaml enthalten. Bei einer Suche nach einer Ressource auf der Designebene wird zunächst in der designspezifischen Datei und dann in der Datei generic.xaml gesucht.

Falls Ihr Steuerelement in einer anderen Assembly als die Anwendung liegt, müssen Sie Ihre globale Ressourcen auf die Elementen- oder Designebene umspeichern. Beide Methoden haben ihre Vorteile.

Definieren von Ressourcen auf der Elementebene

Sie können freigegebene Ressourcen auf der Elementebene definieren, indem Sie ein benutzerdefiniertes Ressourcenverzeichnis erstellen und es mit dem Ressourcenverzeichnis Ihres Steuerelements zusammenführen. Falls Sie sich für diese Methode entscheiden, können Sie die Ressourcendatei beliebig benennen und sie im Ordner speichern, wo Ihre Steuerelemente liegen. Für Ressourcen auf der Elementebene können ebenso einfache Zeichenfolgen als Schlüssel verwendet werden. Mit dem folgenden Beispiel erstellen Sie eine LinearGradientBrush-Ressourcendatei mit dem Namen „Dictionary1.xaml“.

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://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 Ressourcenverzeichnis definiert haben, müssen Sie es mit dem Ressourcenverzeichnis des Steuerelements zusammenführen. Sie können dies mithilfe von XAML oder Code erreichen.

Im folgenden Beispiel wird ein Ressourcenverzeichnis mit XAML zusammengeführt.

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

Der Nachteil dieses Ansatzes ist, dass jedes Mal ein ResourceDictionary-Objekt erstellt wird, wenn Sie darauf verweisen. Wenn Ihre Bibliothek z. B. 10 benutzerdefinierte Steuerelemente enthält, und Sie die freigegebenen Ressourcenverzeichnisse für jedes Steuerelement mit XAML zusammenführen, würden 10 identische ResourceDictionary-Objekte erstellt werden. Sie können dies vermeiden, indem Sie eine statische Klasse erstellen, die die Ressourcen im Code zusammenführt und die resultierende ResourceDictionary-Instanz zurückgibt.

Im folgenden Beispiel wird eine Klasse erstellt, die eine freigegebene ResourceDictionary-Instanz 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 Konstruktor des Steuerelements zusammengeführt, bevor sie InitializeComponent aufruft. Da SharedDictionaryManager.SharedDictionary eine statische Eigenschaft ist, wird ResourceDictionary nur einmal erstellt. Da das Ressourcenverzeichnis vor dem Aufruf von InitializeComponent zusammengeführt worden ist, kann das Steuerelement auf die Ressourcen in seiner XAML-Datei zugreifen.

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

Definieren von Ressourcen auf der Designebene

Mit WPF können Sie Ressourcen für verschiedene Windows-Designs erstellen. Als Autor des Steuerelements können Sie eine Ressource für ein bestimmtes Design definieren, um die Darstellung Ihres Steuerelements an das jeweils verwendete Design anzupassen. Beispielsweise unterscheidet sich die Darstellung einer Button im klassischen Windows-Design (dem Standarddesign für Windows 2000) von einer Button im Windows Luna-Design (dem Standarddesign für Windows XP), da die Button für jedes Design eine andere ControlTemplate verwendet.

Designspezifische Ressourcen werden in einem Ressourcenverzeichnis mit dem entsprechenden Dateinamen gespeichert. Diese Dateien müssen sich im Themes-Verzeichnis befinden, einem Unterordner des Ordners, in dem sich das Steuerelement befindet. In der folgenden Tabelle sind die Ressourcenverzeichnisdateien sowie die den einzelnen Dateien zugeordneten Designs aufgelistet:

Name der Ressourcenverzeichnisdatei Windows-Design
Classic.xaml Klassische Windows 9x/2000-Darstellung unter Windows XP
Luna.NormalColor.xaml Blaues Standard-Design unter Windows XP
Luna.Homestead.xaml Olivgrünes Design unter Windows XP
Luna.Metallic.xaml Silbernes Design 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, durchsucht das Steuerelement Classic.xaml nach der Ressource. Falls die Ressource weder in der Datei, die dem aktuellen Design entspricht, noch in Classic.xaml definiert ist, verwendet das Steuerelement die generische Ressource, die sich im Ressourcenverzeichnis mit dem Namen generic.xaml befindet. Die generic.xaml-Datei befindet sich im gleichen Ordner mit den designspezifischen Ressourcenverzeichnisdateien. Obwohl generic.xaml keinem bestimmten Windows-Design entspricht, ist es dennoch ein Ressourcenverzeichnis auf der Designebene.

Das Beispiel für das benutzerdefinierte NumericUpDown-Steuerelement in C# oder Visual Basic mit Unterstützung für die Automatisierung von Design und Benutzeroberfläche enthält zwei Ressourcenwörterbücher für das NumericUpDown-Steuerelement: eines in „generic.xaml“ und das andere in „Luna.NormalColor.xaml“.

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

static NumericUpDown()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown),
               new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}
Shared Sub New()
    DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(GetType(NumericUpDown)))
End Sub
Definieren und Angeben von Schlüsseln für Designressourcen

Wenn Sie eine Ressource auf der Elementebene definieren, können Sie ihr als Schlüssel eine Zeichenfolge zuweisen und über diese Zeichenfolge auf sie zugreifen. Wenn Sie eine Ressource auf der Designebene definieren, müssen Sie eine ComponentResourceKey-Instanz 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 es die ComponentResourceKey-Instanz als Schlüssel angibt.

<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 zu finden, muss die Hostinganwendung über die Information verfügen, dass die Assembly Ressourcen enthält, die steuerelementenspezifisch sind. Sie können dies erreichen, indem Sie die ThemeInfoAttribute-Instanz zu der Assembly hinzufügen, die das Steuerelement enthält. Das ThemeInfoAttribute verfügt über eine GenericDictionaryLocation-Eigenschaft, die den Speicherort der allgemeinen Ressourcen angibt, und eine ThemeDictionaryLocation-Eigenschaft, die den Speicherort der designspezifischen Ressourcen angibt.

Das folgende Beispiel legt die Eigenschaften GenericDictionaryLocation und ThemeDictionaryLocation auf SourceAssembly fest, um anzugeben, dass sich die generischen und designspezifischen Ressourcen in der gleichen Assembly wie das Steuerelement befinden.

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

Weitere Informationen