Behaviors in WPF

Behaviors in WPF
10/2026
Noel H. | Solution Engineer

TL;DR

Behaviors sind ein zentrales Werkzeug in der WPF-Entwicklung nach dem MVVM-Pattern. Sie kapseln UI-Logik in wiederverwendbaren Klassen, die an ein Control angehängt werden, und verhindern so, dass Logik unnötig im Code-Behind landet.

Der Beitrag zeigt zwei praxisnahe Beispiele: ein SelectAllOnFocusBehavior, das den Text einer TextBox beim Fokussieren automatisch markiert, sowie ein WatermarkBehavior, das per Adorner einen Platzhaltertext einblendet. Beide verdeutlichen typische Stolperfallen, etwa das Timing von Fokus-Events oder die verzögerte Verfügbarkeit des AdornerLayer.

Das Ergebnis: schlankerer Code-Behind, testbarere ViewModels und wiederverwendbare Logik.

Was sind Behaviors?

In WPF-Projekten, die nach dem MVVM-Pattern implementiert wurden, kann es zu den folgenden Problemen kommen: Logik, die in das ViewModel gehört, landet im Code-Behind, weil das Event kein direktes Binding unterstützt, oder es werden Eigenschaften benötigt, die das Control nicht hergibt. Hier kommen Behaviors ins Spiel.

Ein Behavior ist eine wiederverwendbare Klasse, die an ein WPF-Control angehängt wird und dadurch dessen Verhalten erweitert. Durch Microsoft.Xaml.Behaviors.Wpf wird die Basisklasse für Behaviors (Behavior<T>) bereitgestellt.

‍

Implementierung von Behaviors

‍

SelectAllOnFocusBehavior

Beschreibung: Selektiert beim Fokussieren einer TextBox automatisch den gesamten Text. Nützlich für Eingabefelder, damit der Nutzer sofort mit dem Tippen überschreiben kann, ohne erst alles manuell markieren zu müssen.

Besonderheit: Ein direkter Aufruf von SelectAll() im GotKeyboardFocus-Handler reicht nicht aus. Ein Mausklick löst zuerst GotKeyboardFocus aus und hebt die Selektion danach wieder auf. Durch Dispatcher.BeginInvoke und DispatcherPriority.Input wird sichergestellt, dass SelectAll() erst nach dem Maus-Handler ausgeführt wird.

Behavior:

1public sealed class SelectAllOnFocusBehavior : Behavior<TextBox> 
2{ 
3    protected override void OnAttached() 
4    { 
5        base.OnAttached(); 
6        AssociatedObject.GotKeyboardFocus += OnGotKeyboardFocus; 
7    } 
8
9    protected override void OnDetaching() 
10    { 
11        AssociatedObject.GotKeyboardFocus -= OnGotKeyboardFocus; 
12        base.OnDetaching(); 
13    }  
14
15    private void OnGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) 
16    { 
17        // Dispatcher.BeginInvoke ensures SelectAll runs AFTER the mouse-click 
18        // handler that would otherwise immediately deselect the text. 
19        AssociatedObject.Dispatcher.BeginInvoke( 
20            DispatcherPriority.Input, 
21            new Action(() => AssociatedObject.SelectAll())); 
22    } 
23} 

‍

XAML:

<TextBox Text="{Binding Betrag}">
    <i:Interaction.Behaviors>
        <behaviors:SelectAllOnFocusBehavior />
    </i:Interaction.Behaviors>
</TextBox>

‍

WatermarkBehavior

Beschreibung: Zeigt einen Platzhaltertext in einer leeren, nicht fokussierten TextBox an, realisiert über einen WPF-Adorner, der transparent über das Control gezeichnet wird. Der Text verschwindet, sobald der Nutzer das Feld fokussiert oder Text eingibt.

Besonderheit: Der AdornerLayer ist zum Zeitpunkt von OnAttached() noch nicht verfügbar und das visuelle Element ist noch nicht im Visual Tree verankert. Darum wartet das Behavior auf das Loaded-Event, um dort den Adorner zu registrieren.

Behavior:

1public sealed class WatermarkBehavior : Behavior<TextBox>
2{
3    // Watermark text
4    public static readonly DependencyProperty WatermarkProperty =
5        DependencyProperty.Register(
6            nameof(Watermark),
7            typeof(string),
8            typeof(WatermarkBehavior),
9            new PropertyMetadata(string.Empty, OnWatermarkPropertyChanged));
10
11    public string Watermark
12    {
13        get => (string)GetValue(WatermarkProperty);
14        set => SetValue(WatermarkProperty, value);
15    }
16
17    // WatermarkBrush
18    public static readonly DependencyProperty WatermarkBrushProperty =
19        DependencyProperty.Register(
20            nameof(WatermarkBrush),
21            typeof(Brush),
22            typeof(WatermarkBehavior),
23            new PropertyMetadata(Brushes.Gray, OnWatermarkPropertyChanged));
24
25    public Brush WatermarkBrush
26    {
27        get => (Brush)GetValue(WatermarkBrushProperty);
28        set => SetValue(WatermarkBrushProperty, value);
29    }
30
31    private WatermarkAdorner? _adorner;
32
33    private static void OnWatermarkPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
34    {
35        if (d is WatermarkBehavior behavior)
36            behavior.RefreshAdorner();
37    }
38
39    protected override void OnAttached()
40    {
41        base.OnAttached();
42
43        AssociatedObject.TextChanged += OnTextChanged;
44        AssociatedObject.GotFocus    += OnFocusChanged;
45        AssociatedObject.LostFocus   += OnFocusChanged;
46
47        if (AssociatedObject.IsLoaded)
48            AddAdorner();
49        else
50            AssociatedObject.Loaded += AddAdornerOnce;
51    }
52
53    protected override void OnDetaching()
54    {
55        AssociatedObject.TextChanged -= OnTextChanged;
56        AssociatedObject.GotFocus    -= OnFocusChanged;
57        AssociatedObject.LostFocus   -= OnFocusChanged;
58        AssociatedObject.Loaded      -= AddAdornerOnce;
59
60        RemoveAdorner();
61
62        base.OnDetaching();
63    }
64
65    private void AddAdornerOnce(object sender, RoutedEventArgs e)
66    {
67        AssociatedObject.Loaded -= AddAdornerOnce;
68        AddAdorner();
69    }
70
71    private void AddAdorner()
72    {
73        var layer = AdornerLayer.GetAdornerLayer(AssociatedObject);
74        if (layer == null) return;
75
76        // remove any existing WatermarkAdorner to prevent stacking
77        var existing = layer.GetAdorners(AssociatedObject);
78        if (existing != null)
79        {
80            foreach (var a in existing)
81            {
82                if (a is WatermarkAdorner)
83                {
84                    layer.Remove(a);
85                }
86            }
87        }
88
89        _adorner = new WatermarkAdorner(AssociatedObject, Watermark ?? string.Empty, WatermarkBrush ?? Brushes.Gray);
90        layer.Add(_adorner);
91        UpdateAdornerVisibility();
92    }
93
94    private void RemoveAdorner()
95    {
96        if (_adorner == null) return;
97
98        var layer = AdornerLayer.GetAdornerLayer(AssociatedObject);
99        layer?.Remove(_adorner);
100        _adorner = null;
101    }
102
103    private void RefreshAdorner()
104    {
105        if (AssociatedObject == null || !AssociatedObject.IsLoaded) return;
106        RemoveAdorner();
107        AddAdorner();
108    }
109
110    private void UpdateAdornerVisibility()
111    {
112        if (_adorner == null) return;
113
114        bool showWatermark = string.IsNullOrEmpty(AssociatedObject.Text)
115                             && !AssociatedObject.IsKeyboardFocused;
116
117        _adorner.Visibility = showWatermark ? Visibility.Visible : Visibility.Collapsed;
118    }
119
120    private void OnTextChanged(object sender, TextChangedEventArgs e)
121        => UpdateAdornerVisibility();
122
123    private void OnFocusChanged(object sender, RoutedEventArgs e)
124        => UpdateAdornerVisibility();
125}

‍

Adorner:

1internal sealed class WatermarkAdorner : Adorner 
2{ 
3    private readonly string _text; 
4    private readonly Brush _brush; 
5    public WatermarkAdorner(UIElement adornedElement, string text, Brush brush) 
6        : base(adornedElement) 
7
8    { 
9        _text = text; 
10        _brush = brush; 
11        IsHitTestVisible = false; 
12    } 
13  
14    protected override Size MeasureOverride(Size constraint) 
15
16    { 
17
18        return constraint; 
19    } 
20  
21    protected override Size ArrangeOverride(Size finalSize) 
22
23    { 
24        return finalSize; 
25
26    } 
27    protected override void OnRender(DrawingContext drawingContext) 
28
29    { 
30        var adornedElement = AdornedElement as TextBox; 
31        if (adornedElement == null) return; 
32
33        var typeface = new Typeface("Open Sans, Arial"); 
34        var fontSize = adornedElement.FontSize > 0 ? adornedElement.FontSize : 14.0; 
35        var pixelsPerDip = VisualTreeHelper.GetDpi(this).PixelsPerDip; 
36
37
38        var formattedText = new FormattedText( 
39            _text, 
40            CultureInfo.CurrentCulture, 
41            FlowDirection.LeftToRight, 
42            typeface, 
43            fontSize, 
44            _brush, 
45            pixelsPerDip); 
46
47        double paddingX = 4.0; 
48        double availableHeight = adornedElement.ActualHeight; 
49        double y = (availableHeight - formattedText.Height) / 2.0; 
50        drawingContext.DrawText(formattedText, new Point(paddingX, y)); 
51    } 
52} 

‍

XAML:

<TextBox> 
    <i:Interaction.Behaviors> 
        <bh:WatermarkBehavior Watermark="E-Mail-Adresse" /> 
    </i:Interaction.Behaviors> 
</TextBox> 

‍

Fazit

Behaviors sind ein wichtiges Werkzeug in WPF-MVVM. Sie lassen sich für unterschiedliche Use Cases verwenden – von simpler UX-Verbesserung über die Umgehung von Einschränkungen bis hin zu generischer Infrastruktur.

Durch Behaviors wird der Code-Behind schlanker, das ViewModel testbarer und die Logik wiederverwendbar, was für MVVM spricht.

Noel H. | Solution Engineer
Noel H. | Solution Engineer

Developer Training

Pfeil-Icon – Mehr erfahren

Mehr zum Thema

Pfeil nach rechts (Verlinkung)
Angular: Webcam ansteuern
09/2026

Angular: Webcam ansteuern

Blauer Pfeil nach rechts (Verlinkung)
Unittests in C#
09/2026

Unittests in C#

Blauer Pfeil nach rechts (Verlinkung)
MCP-Server in C# bauen
09/2026

MCP-Server in C# bauen

Blauer Pfeil nach rechts (Verlinkung)
KI in der Software­entwicklung: welche Werkzeuge wir einsetzen und wie Ihre Daten geschützt sind
09/2026

KI in der Software­entwicklung: welche Werkzeuge wir einsetzen und wie Ihre Daten geschützt sind

Blauer Pfeil nach rechts (Verlinkung)