As stated in an earlier answer, sometimes it is preferable not to replace the ControlTemplate of the standard controls. So here's a method to create a watermark via an attached property, that keeps the existing control template intact. This has the advantage that you can set the watermark to any text, including setting it at runtime via data binding.
For demonstration the XAML will create 3 textboxes (see below). The bottom textbox has a watermark whose Text property is bound to the Text property of the top textbox, which demonstrates the dynamic nature of the watermark text. Typing a number into the second textbox will change the font size of the Text (and watermark) of the bottom textbox.

The above image is taken from Windows 10 standard theme. Here is is again after setting the Windows theme to High Contrast. As you can see the existing ControlTemplate (and color scheme) has been respected.

XAML
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:behaviors="clr-namespace:WpfApp2.Behaviors"
mc:Ignorable="d"
Title="MainWindow" Height="250" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="Watermark" Grid.Row="0"/>
<TextBox Grid.Row="0" Grid.Column="1" Margin="5"
x:Name="watermark" Width="250" HorizontalAlignment="Left" Text="enter search term here"/>
<Label Content="Font Size" Grid.Row="1"/>
<TextBox Grid.Row="1" Grid.Column="1" Margin="5"
x:Name="fontSize" Width="250" VerticalAlignment="Center" HorizontalAlignment="Left"
behaviors:TextBoxExtensions.Watermark="Enter a font size"/>
<Label Content="Result" Grid.Row="2"/>
<TextBox Grid.Row="2" Grid.Column="2" Margin="5"
Width="200" VerticalAlignment="Center" HorizontalAlignment="Left"
FontSize="{Binding Text, ElementName=fontSize}"
behaviors:TextBoxExtensions.Watermark="{Binding Path=Text, ElementName=watermark}" />
</Grid>
</Window>
TextBoxExtensions.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace WpfApp2.Behaviors
{
public class TextBoxExtensions
{
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.RegisterAttached(
"Watermark",
typeof(string),
typeof(TextBoxExtensions),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, OnWatermarkTextChanged)
);
private static void OnWatermarkTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var tb = d as TextBox;
if (tb != null)
{
var textChangedHandler = new TextChangedEventHandler((s, ea) => ShowOrHideWatermark(s as TextBox));
var focusChangedHandler = new DependencyPropertyChangedEventHandler((s, ea) => ShowOrHideWatermark(s as TextBox));
var sizeChangedHandler = new SizeChangedEventHandler((s, ea) => ShowOrHideWatermark(s as TextBox));
if (string.IsNullOrEmpty(e.OldValue as string))
{
tb.TextChanged += textChangedHandler;
tb.IsKeyboardFocusedChanged += focusChangedHandler;
// We need SizeChanged events because the Background brush is sized according to the control size
tb.SizeChanged += sizeChangedHandler;
}
if (string.IsNullOrEmpty(e.NewValue as string))
{
tb.TextChanged -= textChangedHandler;
tb.IsKeyboardFocusedChanged -= focusChangedHandler;
tb.SizeChanged -= sizeChangedHandler;
}
ShowOrHideWatermark(tb);
}
}
public static string GetWatermark(DependencyObject element)
{
return (string)element.GetValue(WatermarkProperty);
}
public static void SetWatermark(DependencyObject element, string value)
{
element.SetValue(WatermarkProperty, value);
}
private static void ShowOrHideWatermark(TextBox tb)
{
// Restore TextBox background to style/theme value
tb.ClearValue(TextBox.BackgroundProperty);
if (string.IsNullOrEmpty(tb.Text) && !tb.IsKeyboardFocused)
{
var wm = GetWatermark(tb);
if (!string.IsNullOrEmpty(wm))
{
tb.Background = CreateTextBrush(wm, tb);
}
}
}
private static Brush CreateTextBrush(string text, TextBox tb)
{
Grid g = new Grid
{
Background = tb.Background,
Width = tb.ActualWidth,
Height = tb.ActualHeight
};
g.Children.Add(new Label
{
Padding = new Thickness(2,1,1,1),
FontSize = tb.FontSize,
FontFamily = tb.FontFamily,
Foreground = Brushes.LightGray,
Content = text
});
VisualBrush vb = new VisualBrush
{
Visual = g,
Stretch = Stretch.None,
AlignmentX = AlignmentX.Left,
AlignmentY = AlignmentY.Center,
};
return vb;
}
}
}
None of the watermark mechanisms is perfect for every scenario. This particular solution has the benefit of preserving the default theme, will use the font size and family of the TextBox, and will respect the background color set on the TextBox control via a style or theme. However it will not work correctly if you attempt to set the Background property of the TextBox directly in XAML as this value will be wiped out by the watermark.