2

I want my textbox to have a watermark style. I have the code below which I got here https://stackoverflow.com/a/21672408/9928363

<Grid>
    <TextBox  Width="250"  VerticalAlignment="Center" HorizontalAlignment="Left" x:Name="SearchTermTextBox" Margin="5"/>
    <TextBlock IsHitTestVisible="False" Text="Enter Search Term Here" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="10,0,0,0" Foreground="DarkGray">
        <TextBlock.Style>
            <Style TargetType="{x:Type TextBlock}">
                <Setter Property="Visibility" Value="Collapsed"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Text, ElementName=SearchTermTextBox}" Value="">
                        <Setter Property="Visibility" Value="Visible"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </TextBlock.Style>
    </TextBlock>
</Grid>

I have lots of Textbox and I only want some of it to use the style but not all. How will I do that?

RockinRowl
  • 31
  • 6

2 Answers2

1

There are a number of ways to add a watermark to a textbox, including replacing the ControlTemplate of your TextBox (as your question implies). However, replacing the ControlTemplate of the TextBox may not be ideal because in so doing you become responsible for drawing the entire control, including the border, styling different states etc. This is not too difficult (you can use Visual Studio or Espression Blend to copy the template from your current theme), but unless you do a lot of work you will lose the WPF feature of adapting the styling of common controls to the current Windows theme.

If you want a simple, re-usable, pure XAML approach that doesn't require changing the control template, then declaring a style resource using a VisualBrush is one effective approach.

See below, where we have 3 text boxes, the watermark style is applied to two of them. This style goes a little further than your example by removing the watermark when the textbox has the input focus.

<Window ...>
    <Window.Resources>
        <Style TargetType="TextBox" x:Key="Watermark">
            <Style.Resources>
                <VisualBrush x:Key="WatermarkBrush" AlignmentX="Left" AlignmentY="Center" Stretch="None">
                    <VisualBrush.Visual>
                        <Label Content="Enter Search Term Here" Foreground="LightGray" />
                    </VisualBrush.Visual>
                </VisualBrush>
            </Style.Resources>
            <Style.Triggers>
                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="">
                    <Setter Property="Background" Value="{StaticResource WatermarkBrush}" />
                </DataTrigger>
                <Trigger Property="IsKeyboardFocused" Value="True">
                    <Setter Property="Background" Value="{x:Null}" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <StackPanel>
        <TextBox Width="250" VerticalAlignment="Center" FontSize="20"
            HorizontalAlignment="Left" Style="{StaticResource Watermark}"/>
        <TextBox Width="250" VerticalAlignment="Center" 
            HorizontalAlignment="Left"/>
        <TextBox Width="250" VerticalAlignment="Center" 
            HorizontalAlignment="Left" Style="{StaticResource Watermark}"/>
    </StackPanel>
</Window>

If you need the watermark to react to different font sizes, you could make use of the Stretch property:-

<VisualBrush x:Key="WatermarkBrush" AlignmentX="Left" AlignmentY="Center" Stretch="Uniform">
    <VisualBrush.Visual>
        <Label Padding="2 1" Content="Enter Search Term Here" Foreground="LightGray" />
    </VisualBrush.Visual>
</VisualBrush>
Rob
  • 1,472
  • 12
  • 24
  • Thanks Rob, this is what I need, however, how can I make the Label content dynamic? each textboxes have different watermark text. – RockinRowl Jun 12 '18 at 22:39
  • I'm afraid that's where this method meets its limitations. The text in the VisualBrush is static, and can't be changed without creating multiple styles. In that case I think one of the other mechanisms would be better. – Rob Jun 13 '18 at 07:58
  • what other mechanisms? – RockinRowl Jun 13 '18 at 22:05
  • Hi @RockinRowl, I'll add another answer that provides what you need via an attached behaviour – Rob Jun 20 '18 at 16:00
0

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.

enter image description here

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.

High Contrast


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.

Rob
  • 1,472
  • 12
  • 24