Zurück zur Startseite

Benutzerdefinierte WPF-Steuerelemente: Stile und angefügte Eigenschaften

Der Artikel beschreibt Methoden zum Erstellen benutzerdefinierter WPF-Steuerelemente ohne Vererbung und UserControl. Er behandelt angefügte Eigenschaften zur Parametrisierung von Stilen, die Implementierung animierter Umschalter über VisualStateManager und die Architektur des Themen-Systems.

Formatierung von WPF-Steuerelementen: Schaltflächen und Umschalter ohne Code-Behind
Advertisement 728x90

Benutzerdefinierte WPF-Steuerelemente erstellen: Buttons und Schalter stylen ohne Vererbung

Beim Entwickeln von WPF-Anwendungen besteht oft die Notwendigkeit, Standardsteuerelemente an ein Corporate Design anzupassen, ohne kommerzielle Bibliotheken kaufen zu müssen. Statt benutzerdefinierter UserControls zu erstellen oder von Basisklassen zu erben, ist es effizienter, Styles und angehängte Eigenschaften zu verwenden – das erhält die Kompatibilität mit dem Framework und vereinfacht die Wartung.

Architektur des Themen-Systems in WPF

Um das Erscheinungsbild der Anwendung zu verwalten, empfiehlt es sich, alle Ressourcen zentral in einem Style-Ordner zu bündeln. Jedes Theme (z. B. hell und dunkel) wird als separater Ordner eingerichtet, der eine Datei mit der Farbpalette und das Hauptwörterbuch der Ressourcen enthält. So lässt sich das Theme zur Laufzeit einfach wechseln, indem ein einziges ResourceDictionary in Application.Resources ersetzt wird.

Die Implementierung des Themenwechsels sieht wie folgt aus:

Google AdInline article slot
public void ChangeTheme(Theme theme)
{
    if (CurrentTheme == theme) return;

    CurrentTheme = theme;
    Uri themeSource = theme switch
    {
        Theme.Dark => new Uri("pack://application:,,,/CustomWpfControls;component/Style/DarkTheme/DarkTheme.xaml", UriKind.Absolute),
        Theme.Light => new Uri("pack://application:,,,/CustomWpfControls;component/Style/LightTheme/LightTheme.xaml", UriKind.Absolute),
        _ => throw new ArgumentOutOfRangeException(nameof(theme))
    };

    var dictionary = Resources.MergedDictionaries[0];
    dictionary.Clear();
    dictionary.Source = themeSource;
}

Dieser Ansatz sorgt dafür, dass alle UI-Elemente ihre Darstellung sofort aktualisieren, ohne das Fenster neu laden zu müssen.

Buttons mit angehängten Eigenschaften stylen

Statt einer benutzerdefinierten RoundedButton-Klasse zu erstellen, ist es besser, eine angehängte Eigenschaft CornerRadius in einer statischen Klasse zu definieren. So können Sie den Eckenradius direkt in XAML festlegen, ohne das Kompositionsprinzip zu verletzen:

public static class RoundedButton
{
    public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.RegisterAttached(
        "CornerRadius",
        typeof(CornerRadius),
        typeof(RoundedButton),
        new FrameworkPropertyMetadata(new CornerRadius(0d), 
            FrameworkPropertyMetadataOptions.AffectsMeasure | 
            FrameworkPropertyMetadataOptions.AffectsRender));

    public static void SetCornerRadius(UIElement element, CornerRadius value) =>
        element.SetValue(CornerRadiusProperty, value);

    public static CornerRadius GetCornerRadius(UIElement element) =>
        (CornerRadius)element.GetValue(CornerRadiusProperty);
}

Im Button-Style-Template wird diese Eigenschaft über eine Bindung an TemplatedParent verwendet:

Google AdInline article slot
<Border CornerRadius="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(customWpfControls:RoundedButton.CornerRadius)}" ... />

Vorteile dieses Ansatzes:

  • Kein zusätzlicher Code-Behind-Code notwendig
  • Volle Kompatibilität mit Button erhalten
  • Möglichkeit, den Radius sowohl im Style als auch auf Instanz-Ebene zu überschreiben
  • Unterstützung für Animationen und Status-Trigger

ToggleButton mit animiertem Schalter implementieren

Ein benutzerdefinierter Schalter im iOS-Stil lässt sich vollständig über ControlTemplate umsetzen, ohne eine einzige Zeile C#-Code. Wichtige Template-Komponenten:

  • Canvas für präzise Elementpositionierung
  • DrawingImage für Icons (Häkchen und Kreis)
  • Border mit RenderTransform für animierte Bewegung
  • VisualStateManager zur Reaktion auf Checked/Unchecked

Die Verschiebungsanimation wird über DoubleAnimation in einem Storyboard umgesetzt:

Google AdInline article slot
<DoubleAnimation Duration="0:0:0.1"
                 To="20"
                 AccelerationRatio="0.2"
                 DecelerationRatio="0.7"
                 Storyboard.TargetName="Switcher"
                 Storyboard.TargetProperty="(RenderTransform).(TranslateTransform.X)"/>

Wichtig: Geben Sie keinen Anfangswert (From) an, damit die Animation immer von der aktuellen Position startet – das verhindert Sprünge bei häufigem Umschalten.

Empfehlungen zur Organisation von Styles

Für die Skalierbarkeit des Projekts sollten Sie diese Regeln befolgen:

  • Basis-Style (ohne x:Key) sollte nur Template und gemeinsame Setter enthalten
  • Spezifische Varianten (PrimaryButton, IconButton usw.) über BasedOn erben
  • Alle Farben in eine separate Datei auslagern (z. B. DarkColors.xaml)
  • Angehängte Eigenschaften nach Funktionalität in statischen Klassen gruppieren

Das erleichtert Refactorings und ermöglicht die schnelle Erstellung neuer Steuerelementtypen basierend auf vorhandenen Templates.

Wichtige Punkte

  • Vermeiden Sie die Vererbung von Button/ToggleButton, wenn sich die Verhaltenslogik nicht ändert
  • Angehängte Eigenschaften sind der bevorzugte Weg, Style-Funktionalität zu erweitern
  • VisualStateManager bietet deklarative Statusverwaltung ohne Code
  • Zentralisiertes Themen-System vereinfacht die Unterstützung mehrerer Farbschemata
  • Animationen sollten relative Werte (ohne From) verwenden, um visuelle Artefakte zu vermeiden

— Editorial Team

Advertisement 728x90

Weiterlesen