320

How can I put some text into a TextBox which will be removed automatically when the user types something in it?

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
  • 56
    This is called a 'placeholder' in HTML. I mention this to help people google this page. – Scott Stafford Feb 10 '14 at 02:14
  • 4
    If you are writing UWP apps on Windows 10 this is much easier. More info: https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.controls.textbox.placeholdertext.aspx – Bleak Morn Feb 02 '16 at 17:44
  • This question has so many dupes... I have VTC all the following as a dupe of this one: https://stackoverflow.com/questions/40745700/how-do-i-create-a-textbox-hint-simmiliar-to-sos-search-box , https://stackoverflow.com/questions/11873378/adding-placeholder-text-to-textbox, https://stackoverflow.com/questions/5593649/in-xaml-textbox-with-a-watermark-that-disappears-after-first-iinput, https://stackoverflow.com/questions/7425618/how-can-i-add-a-hint-text-to-wpf-textbox – StayOnTarget Jun 24 '21 at 12:30
  • 2
    I love the capabilities of WPF, but this is a prime example why it's a pain to work with it. Things like that should already be built in like it is in HTML. – Martin Braun Jan 09 '22 at 16:54

34 Answers34

469

You can create a watermark that can be added to any TextBox with an Attached Property. Here is the source for the Attached Property:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;

/// <summary>
/// Class that provides the Watermark attached property
/// </summary>
public static class WatermarkService
{
    /// <summary>
    /// Watermark Attached Dependency Property
    /// </summary>
    public static readonly DependencyProperty WatermarkProperty = DependencyProperty.RegisterAttached(
       "Watermark",
       typeof(object),
       typeof(WatermarkService),
       new FrameworkPropertyMetadata((object)null, new PropertyChangedCallback(OnWatermarkChanged)));

    #region Private Fields

    /// <summary>
    /// Dictionary of ItemsControls
    /// </summary>
    private static readonly Dictionary<object, ItemsControl> itemsControls = new Dictionary<object, ItemsControl>();

    #endregion

    /// <summary>
    /// Gets the Watermark property.  This dependency property indicates the watermark for the control.
    /// </summary>
    /// <param name="d"><see cref="DependencyObject"/> to get the property from</param>
    /// <returns>The value of the Watermark property</returns>
    public static object GetWatermark(DependencyObject d)
    {
        return (object)d.GetValue(WatermarkProperty);
    }

    /// <summary>
    /// Sets the Watermark property.  This dependency property indicates the watermark for the control.
    /// </summary>
    /// <param name="d"><see cref="DependencyObject"/> to set the property on</param>
    /// <param name="value">value of the property</param>
    public static void SetWatermark(DependencyObject d, object value)
    {
        d.SetValue(WatermarkProperty, value);
    }

    /// <summary>
    /// Handles changes to the Watermark property.
    /// </summary>
    /// <param name="d"><see cref="DependencyObject"/> that fired the event</param>
    /// <param name="e">A <see cref="DependencyPropertyChangedEventArgs"/> that contains the event data.</param>
    private static void OnWatermarkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Control control = (Control)d;
        control.Loaded += Control_Loaded;

        if (d is ComboBox)
        {
            control.GotKeyboardFocus += Control_GotKeyboardFocus;
            control.LostKeyboardFocus += Control_Loaded;
        }
        else if (d is TextBox)
        {
            control.GotKeyboardFocus += Control_GotKeyboardFocus;
            control.LostKeyboardFocus += Control_Loaded;
            ((TextBox)control).TextChanged += Control_GotKeyboardFocus;
        }

        if (d is ItemsControl && !(d is ComboBox))
        {
            ItemsControl i = (ItemsControl)d;

            // for Items property  
            i.ItemContainerGenerator.ItemsChanged += ItemsChanged;
            itemsControls.Add(i.ItemContainerGenerator, i);

            // for ItemsSource property  
            DependencyPropertyDescriptor prop = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, i.GetType());
            prop.AddValueChanged(i, ItemsSourceChanged);
        }
    }

    #region Event Handlers

    /// <summary>
    /// Handle the GotFocus event on the control
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">A <see cref="RoutedEventArgs"/> that contains the event data.</param>
    private static void Control_GotKeyboardFocus(object sender, RoutedEventArgs e)
    {
        Control c = (Control)sender;
        if (ShouldShowWatermark(c))
        {
            ShowWatermark(c);
        }
        else
        {
            RemoveWatermark(c);
        }
    }

    /// <summary>
    /// Handle the Loaded and LostFocus event on the control
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">A <see cref="RoutedEventArgs"/> that contains the event data.</param>
    private static void Control_Loaded(object sender, RoutedEventArgs e)
    {
        Control control = (Control)sender;
        if (ShouldShowWatermark(control))
        {
            ShowWatermark(control);
        }
    }

    /// <summary>
    /// Event handler for the items source changed event
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">A <see cref="EventArgs"/> that contains the event data.</param>
    private static void ItemsSourceChanged(object sender, EventArgs e)
    {
        ItemsControl c = (ItemsControl)sender;
        if (c.ItemsSource != null)
        {
            if (ShouldShowWatermark(c))
            {
                ShowWatermark(c);
            }
            else
            {
                RemoveWatermark(c);
            }
        }
        else
        {
            ShowWatermark(c);
        }
    }

    /// <summary>
    /// Event handler for the items changed event
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">A <see cref="ItemsChangedEventArgs"/> that contains the event data.</param>
    private static void ItemsChanged(object sender, ItemsChangedEventArgs e)
    {
        ItemsControl control;
        if (itemsControls.TryGetValue(sender, out control))
        {
            if (ShouldShowWatermark(control))
            {
                ShowWatermark(control);
            }
            else
            {
                RemoveWatermark(control);
            }
        }
    }

    #endregion

    #region Helper Methods

    /// <summary>
    /// Remove the watermark from the specified element
    /// </summary>
    /// <param name="control">Element to remove the watermark from</param>
    private static void RemoveWatermark(UIElement control)
    {
        AdornerLayer layer = AdornerLayer.GetAdornerLayer(control);

        // layer could be null if control is no longer in the visual tree
        if (layer != null)
        {
            Adorner[] adorners = layer.GetAdorners(control);
            if (adorners == null)
            {
                return;
            }

            foreach (Adorner adorner in adorners)
            {
                if (adorner is WatermarkAdorner)
                {
                    adorner.Visibility = Visibility.Hidden;
                    layer.Remove(adorner);
                }
            }
        }
    }

    /// <summary>
    /// Show the watermark on the specified control
    /// </summary>
    /// <param name="control">Control to show the watermark on</param>
    private static void ShowWatermark(Control control)
    {
        AdornerLayer layer = AdornerLayer.GetAdornerLayer(control);

        // layer could be null if control is no longer in the visual tree
        if (layer != null)
        {
            layer.Add(new WatermarkAdorner(control, GetWatermark(control)));
        }
    }

    /// <summary>
    /// Indicates whether or not the watermark should be shown on the specified control
    /// </summary>
    /// <param name="c"><see cref="Control"/> to test</param>
    /// <returns>true if the watermark should be shown; false otherwise</returns>
    private static bool ShouldShowWatermark(Control c)
    {
        if (c is ComboBox)
        {
            return (c as ComboBox).Text == string.Empty;
        }
        else if (c is TextBoxBase)
        {
            return (c as TextBox).Text == string.Empty;
        }
        else if (c is ItemsControl)
        {
            return (c as ItemsControl).Items.Count == 0;
        }
        else
        {
            return false;
        }
    }

    #endregion
}

The Attached Property uses a class called WatermarkAdorner, here is that source:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;

/// <summary>
/// Adorner for the watermark
/// </summary>
internal class WatermarkAdorner : Adorner
{
    #region Private Fields

    /// <summary>
    /// <see cref="ContentPresenter"/> that holds the watermark
    /// </summary>
    private readonly ContentPresenter contentPresenter;

    #endregion

    #region Constructor

    /// <summary>
    /// Initializes a new instance of the <see cref="WatermarkAdorner"/> class
    /// </summary>
    /// <param name="adornedElement"><see cref="UIElement"/> to be adorned</param>
    /// <param name="watermark">The watermark</param>
    public WatermarkAdorner(UIElement adornedElement, object watermark) :
       base(adornedElement)
    {
        this.IsHitTestVisible = false;

        this.contentPresenter = new ContentPresenter();
        this.contentPresenter.Content = watermark;
        this.contentPresenter.Opacity = 0.5;
        this.contentPresenter.Margin = new Thickness(Control.Margin.Left + Control.Padding.Left, Control.Margin.Top + Control.Padding.Top, 0, 0);

        if (this.Control is ItemsControl && !(this.Control is ComboBox))
        {
            this.contentPresenter.VerticalAlignment = VerticalAlignment.Center;
            this.contentPresenter.HorizontalAlignment = HorizontalAlignment.Center;
        }

        // Hide the control adorner when the adorned element is hidden
        Binding binding = new Binding("IsVisible");
        binding.Source = adornedElement;
        binding.Converter = new BooleanToVisibilityConverter();
        this.SetBinding(VisibilityProperty, binding);
    }

    #endregion

    #region Protected Properties

    /// <summary>
    /// Gets the number of children for the <see cref="ContainerVisual"/>.
    /// </summary>
    protected override int VisualChildrenCount
    {
        get { return 1; }
    }

    #endregion

    #region Private Properties

    /// <summary>
    /// Gets the control that is being adorned
    /// </summary>
    private Control Control
    {
        get { return (Control)this.AdornedElement; }
    }

    #endregion

    #region Protected Overrides

    /// <summary>
    /// Returns a specified child <see cref="Visual"/> for the parent <see cref="ContainerVisual"/>.
    /// </summary>
    /// <param name="index">A 32-bit signed integer that represents the index value of the child <see cref="Visual"/>. The value of index must be between 0 and <see cref="VisualChildrenCount"/> - 1.</param>
    /// <returns>The child <see cref="Visual"/>.</returns>
    protected override Visual GetVisualChild(int index)
    {
        return this.contentPresenter;
    }

    /// <summary>
    /// Implements any custom measuring behavior for the adorner.
    /// </summary>
    /// <param name="constraint">A size to constrain the adorner to.</param>
    /// <returns>A <see cref="Size"/> object representing the amount of layout space needed by the adorner.</returns>
    protected override Size MeasureOverride(Size constraint)
    {
        // Here's the secret to getting the adorner to cover the whole control
        this.contentPresenter.Measure(Control.RenderSize);
        return Control.RenderSize;
    }

    /// <summary>
    /// When overridden in a derived class, positions child elements and determines a size for a <see cref="FrameworkElement"/> derived class. 
    /// </summary>
    /// <param name="finalSize">The final area within the parent that this element should use to arrange itself and its children.</param>
    /// <returns>The actual size used.</returns>
    protected override Size ArrangeOverride(Size finalSize)
    {
        this.contentPresenter.Arrange(new Rect(finalSize));
        return finalSize;
    }

    #endregion
}

Now you can put a watermark on any TextBox like this:

<AdornerDecorator>
   <TextBox x:Name="SearchTextBox">
      <controls:WatermarkService.Watermark>
         <TextBlock>Type here to search text</TextBlock>
      </controls:WatermarkService.Watermark>
   </TextBox>
</AdornerDecorator>

The watermark can be anything you want (text, images ...). In addition to working for TextBoxes, this watermark also works for ComboBoxes and ItemControls.

This code was adapted from this blog post.

Vadim Ovchinnikov
  • 13,327
  • 5
  • 62
  • 90
John Myczek
  • 12,076
  • 4
  • 31
  • 44
  • 2
    Hey john, tried implementing this. Getting an error saying: controls is an undeclared prefix", have you any idea ? – Reza M. Mar 22 '11 at 19:09
  • 2
    Hey I posted a question about it: [link](http://stackoverflow.com/questions/5397334/watermark-using-john-myczeks-class) – Reza M. Mar 22 '11 at 20:34
  • Looks like you got your answer. Just need the namespace definition. – John Myczek Mar 23 '11 at 13:39
  • 2
    +1, but i think that event handlers should be unwired somewehere(control.GotKeyboardFocus += Control_GotKeyboardFocus...) – 6opuc Aug 31 '11 at 05:18
  • @JohnMyczek Thanks for this wonderful code snippet, however, using this on a combobox with IsEditable=true will not work. The watermark will be displayed even though an item has been selected, rendering both the watermark and the item text at the same time and therefore unreadable. –  Feb 10 '12 at 18:01
  • 2
    @JohnMyczek Nice solution! But when setting a margin to the textbox, the watermark does not place correctly in the middle of the Textbox. it appears a little to the bottom. – JoanComasFdz Mar 09 '12 at 11:53
  • 12
    I've solved it modifying the WatermarkAdorner constructor Margin assignment as: Margin = new Thickness(Control.Padding.Left, Control.Padding.Top+1, Control.Padding.Right, Control.Padding.Bottom) – JoanComasFdz Mar 09 '12 at 13:55
  • 4
    @JohnMyczek In order to localize the watermark: how can I bind the TextBox.Text in the Watermark xaml declaration to a property from ViewModel? – JoanComasFdz Mar 14 '12 at 10:20
  • 1
    Anyone knows the reason that this does not work on the passwordbox – klashagelqvist Mar 18 '12 at 12:04
  • 1
    @klashagelqvist: PasswordBox isn't inherit TextBox, so you should add it yourself. in WatermarkService class at OnWatermarkChanged method you should put "if (d is ComboBox || d is TextBox || d is PasswordBox)", and at ShouldShowWatermark method you should add "else if (c is PasswordBox) { (c as PasswordBox).Password == string.Empty; }". – Seffix May 02 '12 at 08:40
  • 1
    @UrbanEsc: I post my [improved solution](http://stackoverflow.com/questions/833943/watermark-textbox-in-wpf/10412007#10412007) that includes a better compatibility to ComboBox & PasswordBox. – Seffix May 02 '12 at 10:42
  • 1
    If you want to add this to a password box you'll have to add the following to the ShouldShowWatermark method: else if(c is PasswordBox) { return (c as PasswordBox).Password == string.Empty; } – JRadness Feb 21 '13 at 01:13
  • 1
    FYI, if a control is given the focus before it is loaded, the watermark service gets into a confused state. I fixed this by adding a separate event handler for the lost keyboard focused event and checking if the control has the focus in the loaded event. – Josh G May 16 '13 at 14:11
  • 2
    @JohnMyczek I would also like to know how the TextBlock.Text property can be bound to the view model... – Matze Jun 13 '13 at 15:53
  • 7
    @Matze @JoanComasFdz Here's how I can bind the `TextBlock.Text` property to my view model (put this in the `WatermarkAdorner` constructor): `FrameworkElement feWatermark = watermark as FrameworkElement;` `if(feWatermark != null && feWatermark.DataContext == null)` { `feWatermark.DataContext = this.Control.DataContext;` } – Sean Hall Aug 21 '13 at 18:35
  • 1
    Incredible. Thanks for the drag-n-dropable code. Got the exact results I wanted in about 5 minutes! – Killnine Sep 27 '13 at 21:14
  • Anyone got http://msdn.microsoft.com/en-us/library/bb514547%28v=vs.90%29.aspx this error? – HoKy22 Nov 20 '13 at 14:26
  • Never mind check out http://stackoverflow.com/questions/5397334/watermark-using-john-myczeks-class – HoKy22 Nov 20 '13 at 14:50
  • 1
    This is great but not for RightToLeft layouts. when I use `FlowDirection="RightToLeft` then the text becomes flipped and unreadable. – mammadalius Jan 14 '14 at 15:56
  • 9
    Possible memory link here. You are adding watermarked controls to the internal static dictionary but never removing them. This will probably prevent your views from being garbage collected once you are done with them. I'd consider using a weak reference here. – Jared G Mar 06 '14 at 04:17
  • 2
    The watermark remains visible when the textbox is covered by another UI element. The Extended WPF Toolkit WatermarkTextBox didn't have this problem. – Dax Fohl Mar 19 '14 at 13:42
  • Does this work for a `PasswordBox`? I've tried it myself, and it works splendidly for `TextBox`, but not `PasswordBox`. **I've just noticed that Beej has an answer down the page that works with Password Boxes which is an edit of this solution.** – Charles W Mar 28 '14 at 14:31
  • I voted this one down because when loading text from the code behind, it doesn't actually remove the watermark. – SpoiledTechie.com Apr 10 '14 at 19:45
  • Clarification: you need to add xmlns:controls="clr-your namespace" to your XAML header – sadegh saati Apr 12 '14 at 11:38
  • How can I get the watermark to come back from code behind? – paparazzo Jan 20 '15 at 19:30
  • How can I remove the watermark when my datacontext changes? Thanks – Matt Fitzmaurice Feb 04 '15 at 12:25
  • 1
    @JaredG Agreed, looks like a memory leak to me as well. This code is not (in its current form) suitable for production, but still a good demonstration of abstraction in WPF. – Doug Feb 11 '15 at 15:53
  • 3
    Besides the static dictionary of itemcontrols, the PropertyDescriptor code also leaks memory. You need to call RemoveValueChanged(). So be careful when you use this code. – muku Jul 20 '15 at 10:02
  • 1
    Great work @JohnMyczek. I would like to note that unless there is a specific reason that the AttachedProperty is of type Object it should probably be changed to string. I found some strange behavior with the property declared as an Object. It appears that if you return a string from your viewModel that is the concatenation of at least one variable and a string constant that the service's prop changed will be called even if the result is the same. If the value returned is the result of joining two constants or the type of attchd prop is changed to string the prop changed will not fire if same. – Jack Woodward May 16 '16 at 22:55
  • 1
    regarding my comment above, the behavior is because obj ref comparison happens if the attached property is of type object. If you return a string constant or the concatenation of two or more constants the object will always be the same and the prop changed event will not fire. However if your concatenation included a variable then the object returned will be different every time and prop changed event will be called even if the string value does not. – Jack Woodward May 16 '16 at 23:13
  • @muku Do you know where to put these fixes so it is ready for production? This is the most popular post on this subject so if changes are needed it would be best to explain where before this gets pushed out into applications. – Jakxna360 Jun 10 '21 at 08:01
  • 1
    All this for just a watermark!!!? – Vidar Apr 29 '22 at 07:17
386

Just using XAML, no extensions, no converters:

<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>
apc
  • 5,306
  • 1
  • 17
  • 26
  • 9
    Extremely simple one, best imo also. I don't know why you would use all those other ones when you could have these 10 lines of xaml script and that's it. Thanks. – dj.lnxss Oct 08 '14 at 22:59
  • 4
    You may want to add a `Padding="6,3,0,0"` to the `TextBlock`. – aloisdg Jan 14 '15 at 22:59
  • 1
    Very nice, but it does not work on Windows Phone Silverlight :-( – Andrea Antonangeli Feb 28 '15 at 12:43
  • 1
    After all the complicated solutions I've seen this one is just wonderful to see. Works perfectly for my purposes! – RosieC Mar 10 '15 at 16:56
  • You can avoid the use of a grid by setting a negative top margin (e.g. Margin="5,-22,0,0") on the TextBlock. – Brian Hinchey Sep 08 '15 at 02:05
  • Wouldn't that depend upon the parent container? Using a negative margin would work for a StackPanel with a vertical orientation but you would have to adjust the margin depending upon the container and its options. The above xmal could easily be addapted to make a custom control or just copied and pasted into any container. – apc Sep 08 '15 at 07:43
  • 20
    How would one make this a reusable Control Template ? – Richard Oct 05 '15 at 21:13
  • It doesn't work on my machine ('VisualStudio 2013' ) ...After compiling the code it shows there is an error but doesn't point out the error, though. I am confused. – Nikhil G Jan 11 '17 at 06:28
  • Have you checked your output and immediate window? Note this is for WPF applications. So far I have used this in VS 2010,2012,2013,2015 without issue. – apc Jan 11 '17 at 08:30
  • perfect on textblox but how to adapt it on passwordbox ? this do not work : Binding="{Binding Password, ElementName=txPassword}" – cyrianox Mar 31 '17 at 11:58
  • 2
    @cyrianox This is because the Password property on a PasswordBox is not bindable for security reasons. You could make it bindable using this example here: https://wpftutorial.net/PasswordBox.html however it is probably quicker and easier just to use the PasswordChanged event and code behind to set the visibility in this case. – apc Apr 03 '17 at 07:15
  • @Richard in your ControlTemplate, name your watermark text block then do this: `` `` `` `` `` – Skrymsli Jan 16 '18 at 20:27
  • 1
    Awesome answer! Especially as I only needed it for one textbox. Thanks! – Steve Jun 30 '19 at 14:37
  • I prefer to hide the watermark when the textbox has focus... so I adapted your solution to use a MultiDataTrigger – voidengine Feb 26 '20 at 13:19
  • I maybe bit late, but just rewrote your text block, notable difference is to use binding for visibility instead of style/triggers. – pushpraj Sep 08 '21 at 06:16
59

I can't believe that no one posted the obvious Extended WPF Toolkit - WatermarkTextBox from Xceed. It works quite well and is open source in case you want to customise.

Edit: though still open source, this library is only free for non-commercial use now, see pricing and license.

dain
  • 6,475
  • 1
  • 38
  • 47
  • 6
    On my win8 machine all WPF Toolkit controls have windows 7 styles (rounded corners, etc). And any WPF toolkit control looks completely out of place when mixed with standard controls. – Gman Mar 27 '13 at 18:25
  • 1
    The "Attached Property" approach by John Myczek has a bug whereby if the textbox was covered by another element, the watermark would bleed through and still be visible. This solution doesn't have that issue. (Wish I'd noticed this earlier since I'm already using the toolkit anyway). Deserves more upvotes. – Dax Fohl Mar 19 '14 at 13:52
  • John Myczek's solution also has an apparent memory leak, where the WatermarkService will keep a reference in a static dictionary to any ItemsControl to which a watermark gets attached. It could definitely be fixed, but I'll give the Extended WPF Toolkit version a try. – mbargiel May 15 '14 at 16:14
  • 1
    The Extended WPF Toolkit is also [not free of charge for commercial use](https://github.com/xceedsoftware/wpftoolkit/blob/master/license.md). – Martin Braun Jan 09 '22 at 16:58
  • @MartinBraun that's a shame! I'm pretty sure it was on a less restrictive licence 10+ years ago when I posted this answer, but thanks for pointing it out! I'll edit this in the answer. – dain Jan 10 '22 at 17:32
57

This is a sample which demonstrates how to create a watermark textbox in WPF:

<Window x:Class="WaterMarkTextBoxDemo.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WaterMarkTextBoxDemo"
    Height="200" Width="400">

    <Window.Resources>

        <SolidColorBrush x:Key="brushWatermarkBackground" Color="White" />
        <SolidColorBrush x:Key="brushWatermarkForeground" Color="LightSteelBlue" />
        <SolidColorBrush x:Key="brushWatermarkBorder" Color="Indigo" />

        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
        <local:TextInputToVisibilityConverter x:Key="TextInputToVisibilityConverter" />

        <Style x:Key="EntryFieldStyle" TargetType="Grid" >
            <Setter Property="HorizontalAlignment" Value="Stretch" />
            <Setter Property="VerticalAlignment" Value="Center" />
            <Setter Property="Margin" Value="20,0" />
        </Style>

    </Window.Resources>


    <Grid Background="LightBlue">

        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>

        <Grid Grid.Row="0" Background="{StaticResource brushWatermarkBackground}" Style="{StaticResource EntryFieldStyle}" >
            <TextBlock Margin="5,2" Text="This prompt dissappears as you type..." Foreground="{StaticResource brushWatermarkForeground}"
                       Visibility="{Binding ElementName=txtUserEntry, Path=Text.IsEmpty, Converter={StaticResource BooleanToVisibilityConverter}}" />
            <TextBox Name="txtUserEntry" Background="Transparent" BorderBrush="{StaticResource brushWatermarkBorder}" />
        </Grid>

        <Grid Grid.Row="1" Background="{StaticResource brushWatermarkBackground}" Style="{StaticResource EntryFieldStyle}" >
            <TextBlock Margin="5,2" Text="This dissappears as the control gets focus..." Foreground="{StaticResource brushWatermarkForeground}" >
                <TextBlock.Visibility>
                    <MultiBinding Converter="{StaticResource TextInputToVisibilityConverter}">
                        <Binding ElementName="txtUserEntry2" Path="Text.IsEmpty" />
                        <Binding ElementName="txtUserEntry2" Path="IsFocused" />
                    </MultiBinding>
                </TextBlock.Visibility>
            </TextBlock>
            <TextBox Name="txtUserEntry2" Background="Transparent" BorderBrush="{StaticResource brushWatermarkBorder}" />
        </Grid>

    </Grid>

</Window>

TextInputToVisibilityConverter is defined as:

using System;
using System.Windows.Data;
using System.Windows;

namespace WaterMarkTextBoxDemo
{
    public class TextInputToVisibilityConverter : IMultiValueConverter
    {
        public object Convert( object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture )
        {
            // Always test MultiValueConverter inputs for non-null
            // (to avoid crash bugs for views in the designer)
            if (values[0] is bool && values[1] is bool)
            {
                bool hasText = !(bool)values[0];
                bool hasFocus = (bool)values[1];

                if (hasFocus || hasText)
                    return Visibility.Collapsed;
            }

            return Visibility.Visible;
        }


        public object[] ConvertBack( object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture )
        {
            throw new NotImplementedException();
        }
    }
}

Note: This is not my code. I found it here, but I think this is the best approach.

M. Jahedbozorgan
  • 6,914
  • 2
  • 46
  • 51
  • 5
    How can I apply it in a passwordbox? – Sauron Oct 22 '09 at 13:06
  • 105
    The best approach? certainly not! Do you really want to type so many lines of code every time you need a watermark? The solution with an attached property is much easier to reuse... – Thomas Levesque Mar 04 '11 at 14:21
  • 5
    Consider creating a UserControl. – M. Jahedbozorgan Mar 05 '11 at 11:06
  • 6
    While I really appreciate your effort to help the community, I really need to say this is far from being even a decent approach. – r41n Dec 05 '13 at 15:33
  • 2
    This code was made by Andy L. You can find it on [codeproject](http://www.codeproject.com/Articles/26977/A-WatermarkTextBox-in-lines-of-XAML). – aloisdg Jan 14 '15 at 14:18
40

There is an article on CodeProject on how to do it in "3 lines of XAML".

<Grid Background="{StaticResource brushWatermarkBackground}">
  <TextBlock Margin="5,2" Text="Type something..."
             Foreground="{StaticResource brushForeground}"
             Visibility="{Binding ElementName=txtUserEntry, Path=Text.IsEmpty,
                          Converter={StaticResource BooleanToVisibilityConverter}}" />
  <TextBox Name="txtUserEntry" Background="Transparent"
           BorderBrush="{StaticResource brushBorder}" />
</Grid>

Ok, well it might not be 3 lines of XAML formatted, but it is pretty simple.

One thing to note: the IsEmpty property on Text is not a property of string, but of ICollectionView, and can be set explicitely as Path=Text.(componentModel:ICollectionView.IsEmpty) (with xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"). Detailed explanation here.

Arkane
  • 352
  • 4
  • 10
René
  • 9,880
  • 4
  • 43
  • 49
  • 10
    The TextBox should have `IsHitTestVisible="False"`. Also, it should come after the TextBox, otherwise it might not be visible if the TextBox has background. – ANeves Jan 11 '16 at 18:00
  • That article in CodeProject is just bad. – Xam Jun 08 '19 at 03:21
  • 3
    How the `Text.IsEmpty` works: [IsEmpty is being resolved from CollectionView.IsEmpty](https://stackoverflow.com/a/25700024/285795) – ΩmegaMan Jan 17 '20 at 23:00
  • I am pretty sure there is a typo in @ANeves comment: the first "TextBox" should be "TextBlock". I otherwise agree with that comment. – Arkane Mar 11 '22 at 09:21
28

Simple solution using style:

<TextBox>
    <TextBox.Style>
        <Style TargetType="TextBox" xmlns:sys="clr-namespace:System;assembly=mscorlib">
            <Style.Resources>
                <VisualBrush x:Key="CueBannerBrush" AlignmentX="Left" AlignmentY="Center" Stretch="None">
                    <VisualBrush.Visual>
                        <Label Content="MM:SS:HH AM/PM" Foreground="LightGray" />
                    </VisualBrush.Visual>
                </VisualBrush>
            </Style.Resources>
            <Style.Triggers>
                <Trigger Property="Text" Value="{x:Static sys:String.Empty}">
                    <Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
                </Trigger>
                <Trigger Property="Text" Value="{x:Null}">
                    <Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
                </Trigger>
                <Trigger Property="IsKeyboardFocused" Value="True">
                    <Setter Property="Background" Value="White" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </TextBox.Style>
</TextBox>

Great solution:

https://code.msdn.microsoft.com/windowsdesktop/In-place-hit-messages-for-18db3a6c

Vadim Ovchinnikov
  • 13,327
  • 5
  • 62
  • 90
Narottam Goyal
  • 3,534
  • 27
  • 26
22

I saw John Myczek's solution, and its comments about Compatibility to ComboBox and PasswordBox, so I improved John Myczek's solution, and here it is:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;

/// <summary>
/// Class that provides the Watermark attached property
/// </summary>
public static class WatermarkService
{
    /// <summary>
    /// Watermark Attached Dependency Property
    /// </summary>
    public static readonly DependencyProperty WatermarkProperty = DependencyProperty.RegisterAttached(
       "Watermark",
       typeof(object),
       typeof(WatermarkService),
       new FrameworkPropertyMetadata((object)null, new PropertyChangedCallback(OnWatermarkChanged)));

    #region Private Fields

    /// <summary>
    /// Dictionary of ItemsControls
    /// </summary>
    private static readonly Dictionary<object, ItemsControl> itemsControls = new Dictionary<object, ItemsControl>();

    #endregion

    /// <summary>
    /// Gets the Watermark property.  This dependency property indicates the watermark for the control.
    /// </summary>
    /// <param name="d"><see cref="DependencyObject"/> to get the property from</param>
    /// <returns>The value of the Watermark property</returns>
    public static object GetWatermark(DependencyObject d)
    {
        return (object)d.GetValue(WatermarkProperty);
    }

    /// <summary>
    /// Sets the Watermark property.  This dependency property indicates the watermark for the control.
    /// </summary>
    /// <param name="d"><see cref="DependencyObject"/> to set the property on</param>
    /// <param name="value">value of the property</param>
    public static void SetWatermark(DependencyObject d, object value)
    {
        d.SetValue(WatermarkProperty, value);
    }

    /// <summary>
    /// Handles changes to the Watermark property.
    /// </summary>
    /// <param name="d"><see cref="DependencyObject"/> that fired the event</param>
    /// <param name="e">A <see cref="DependencyPropertyChangedEventArgs"/> that contains the event data.</param>
    private static void OnWatermarkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Control control = (Control)d;
        control.Loaded += Control_Loaded;

        if (d is TextBox || d is PasswordBox)
        {
            control.GotKeyboardFocus += Control_GotKeyboardFocus;
            control.LostKeyboardFocus += Control_Loaded;
        }
        else if (d is ComboBox)
        {
            control.GotKeyboardFocus += Control_GotKeyboardFocus;
            control.LostKeyboardFocus += Control_Loaded;
            (d as ComboBox).SelectionChanged += new SelectionChangedEventHandler(SelectionChanged);
        }
        else if (d is ItemsControl)
        {
            ItemsControl i = (ItemsControl)d;

            // for Items property  
            i.ItemContainerGenerator.ItemsChanged += ItemsChanged;
            itemsControls.Add(i.ItemContainerGenerator, i);

            // for ItemsSource property  
            DependencyPropertyDescriptor prop = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, i.GetType());
            prop.AddValueChanged(i, ItemsSourceChanged);
        }
    }

    /// <summary>
    /// Event handler for the selection changed event
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">A <see cref="ItemsChangedEventArgs"/> that contains the event data.</param>
    private static void SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        Control control = (Control)sender;
        if (ShouldShowWatermark(control))
        {
            ShowWatermark(control);
        }
        else
        {
            RemoveWatermark(control);
        }
    }

    #region Event Handlers

    /// <summary>
    /// Handle the GotFocus event on the control
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">A <see cref="RoutedEventArgs"/> that contains the event data.</param>
    private static void Control_GotKeyboardFocus(object sender, RoutedEventArgs e)
    {
        Control c = (Control)sender;
        if (ShouldShowWatermark(c))
        {
            RemoveWatermark(c);
        }
    }

    /// <summary>
    /// Handle the Loaded and LostFocus event on the control
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">A <see cref="RoutedEventArgs"/> that contains the event data.</param>
    private static void Control_Loaded(object sender, RoutedEventArgs e)
    {
        Control control = (Control)sender;
        if (ShouldShowWatermark(control))
        {
            ShowWatermark(control);
        }
    }

    /// <summary>
    /// Event handler for the items source changed event
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">A <see cref="EventArgs"/> that contains the event data.</param>
    private static void ItemsSourceChanged(object sender, EventArgs e)
    {
        ItemsControl c = (ItemsControl)sender;
        if (c.ItemsSource != null)
        {
            if (ShouldShowWatermark(c))
            {
                ShowWatermark(c);
            }
            else
            {
                RemoveWatermark(c);
            }
        }
        else
        {
            ShowWatermark(c);
        }
    }

    /// <summary>
    /// Event handler for the items changed event
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">A <see cref="ItemsChangedEventArgs"/> that contains the event data.</param>
    private static void ItemsChanged(object sender, ItemsChangedEventArgs e)
    {
        ItemsControl control;
        if (itemsControls.TryGetValue(sender, out control))
        {
            if (ShouldShowWatermark(control))
            {
                ShowWatermark(control);
            }
            else
            {
                RemoveWatermark(control);
            }
        }
    }

    #endregion

    #region Helper Methods

    /// <summary>
    /// Remove the watermark from the specified element
    /// </summary>
    /// <param name="control">Element to remove the watermark from</param>
    private static void RemoveWatermark(UIElement control)
    {
        AdornerLayer layer = AdornerLayer.GetAdornerLayer(control);

        // layer could be null if control is no longer in the visual tree
        if (layer != null)
        {
            Adorner[] adorners = layer.GetAdorners(control);
            if (adorners == null)
            {
                return;
            }

            foreach (Adorner adorner in adorners)
            {
                if (adorner is WatermarkAdorner)
                {
                    adorner.Visibility = Visibility.Hidden;
                    layer.Remove(adorner);
                }
            }
        }
    }

    /// <summary>
    /// Show the watermark on the specified control
    /// </summary>
    /// <param name="control">Control to show the watermark on</param>
    private static void ShowWatermark(Control control)
    {
        AdornerLayer layer = AdornerLayer.GetAdornerLayer(control);

        // layer could be null if control is no longer in the visual tree
        if (layer != null)
        {
            layer.Add(new WatermarkAdorner(control, GetWatermark(control)));
        }
    }

    /// <summary>
    /// Indicates whether or not the watermark should be shown on the specified control
    /// </summary>
    /// <param name="c"><see cref="Control"/> to test</param>
    /// <returns>true if the watermark should be shown; false otherwise</returns>
    private static bool ShouldShowWatermark(Control c)
    {
        if (c is ComboBox)
        {
            return (c as ComboBox).SelectedItem == null;
            //return (c as ComboBox).Text == string.Empty;
        }
        else if (c is TextBoxBase)
        {
            return (c as TextBox).Text == string.Empty;
        }
        else if (c is PasswordBox)
        {
            return (c as PasswordBox).Password == string.Empty;
        }
        else if (c is ItemsControl)
        {
            return (c as ItemsControl).Items.Count == 0;
        }
        else
        {
            return false;
        }
    }

    #endregion
}

Now, a ComboBox can be also Editable, and PasswordBox can add a watermark too. Don't forget to use JoanComasFdz's comment above to solve the margin problem.

And, of course, All the credit goes to John Myczek.

Vadim Ovchinnikov
  • 13,327
  • 5
  • 62
  • 90
Seffix
  • 1,049
  • 1
  • 12
  • 16
  • 5
    That is in fact an improved version of the beautiful piece @john-myczek code and it worked fined for combo boxes. Thank you both! – saamorim Jun 07 '13 at 13:26
16

This library has a watermark.

Nuget package

Sample usage:

<TextBox adorners:Watermark.Text="Write something here" 
         adorners:Watermark.TextStyle="{StaticResource AdornerTextStyle}"
         adorners:Watermark.VisibleWhen="EmptyAndNotKeyboardFocused"/>
Johan Larsson
  • 17,112
  • 9
  • 74
  • 88
  • That's exactly what I was looking for. Working with WPF after UWP seems such an effort. But this solution is very similar to what I would do in UWP (Basically not have any boiler plate code for this) Thanks +1 – iam.Carrot Oct 01 '20 at 20:11
9

I have created siple code-only implementation which works fine for WPF and Silverlight as well:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;

public class TextBoxWatermarked : TextBox
{
    #region [ Dependency Properties ]

    public static DependencyProperty WatermarkProperty = DependencyProperty.Register
    (
         "Watermark",
         typeof(string),
         typeof(TextBoxWatermarked),
         new PropertyMetadata(new PropertyChangedCallback(OnWatermarkChanged))
    );
    
    #endregion
    
    
    #region [ Fields ]
    
    private bool _isWatermarked;
    private Binding _textBinding;

    #endregion


    #region [ Properties ]

    protected new Brush Foreground
    {
        get { return base.Foreground; }
        set { base.Foreground = value; }
    }

    public string Watermark
    {
        get { return (string)GetValue(WatermarkProperty); }
        set { SetValue(WatermarkProperty, value); }
    }

    #endregion


    #region [ .ctor ]
    
    public TextBoxWatermarked()
    {
        Loaded += (s, ea) => ShowWatermark();
    }
    
    #endregion


    #region [ Event Handlers ]

    protected override void OnGotFocus(RoutedEventArgs e)
    {
        base.OnGotFocus(e);
        HideWatermark();
    }
    
    protected override void OnLostFocus(RoutedEventArgs e)
    {
        base.OnLostFocus(e);
        ShowWatermark();
    }
    
    private static void OnWatermarkChanged(DependencyObject sender, DependencyPropertyChangedEventArgs ea)
    {
        var tbw = sender as TextBoxWatermarked;
        if (tbw == null) return;
        tbw.ShowWatermark();
    }

    #endregion


    #region [ Methods ]

    private void ShowWatermark()
    {
        if (string.IsNullOrEmpty(base.Text))
        {
            _isWatermarked = true;
            base.Foreground = new SolidColorBrush(Colors.Gray);
            var bindingExpression = GetBindingExpression(TextProperty);
            _textBinding = bindingExpression == null ? null : bindingExpression.ParentBinding;
            if (bindingExpression != null)
                bindingExpression.UpdateSource();
            SetBinding(TextProperty, new Binding());
            base.Text = Watermark;
        }
    }

    private void HideWatermark()
    {
        if (_isWatermarked)
        {
            _isWatermarked = false;
            ClearValue(ForegroundProperty);
            base.Text = "";
            SetBinding(TextProperty, _textBinding ?? new Binding());
        }
    }

    #endregion
}

Usage:

<TextBoxWatermarked Watermark="Some text" />
ASh
  • 34,632
  • 9
  • 60
  • 82
Vitaliy Ulantikov
  • 10,157
  • 3
  • 61
  • 54
  • Great solution. Why shadow the Foreground property? SetBinding(TextProperty, new Binding()) throws InvalidOperationException: Two-way binding requires Path or XPath? – Tim Murphy Feb 08 '11 at 21:11
  • I hide Foreground property because TextBoxWatermarked uses it for its own purposes. I don't know why InvalidOperationException is throwed, maybe if you use WPF (I used it with Silverlight) you need to pass null instead of new Binding(). – Vitaliy Ulantikov Feb 10 '11 at 11:10
  • 2
    To use this code in WPF, use `BindingOperations.ClearBinding(this, TextProperty)` instead of `SetBinding(TextProperty, new Binding())` in both places. – Sebastian Krysmanski Feb 25 '13 at 09:18
  • 1
    This actually changes `Text` to the watermark. Wouldn't work for me. – lobsterism Jan 16 '15 at 19:42
  • probably useful to add namespace lines to this, or fully qualify some of this stuff. – Richard June Sep 07 '16 at 13:27
  • Also, when I use it, I get an error "Two-Way binding requires Path or XPath" – Richard June Sep 07 '16 at 14:04
  • @RichardJune, what can I say 5+ years later being completely out of context... :) I implemented it in my project, it was working there, so I shared the same code here. Not sure if it depends on a framework version. Would you suggest an edit fixing the issue please? – Vitaliy Ulantikov Sep 09 '16 at 06:25
  • I added what I think are the correct namespaces. (some objects have multiple possibilities. Never did figure out the two way binding error. – Richard June Sep 09 '16 at 13:13
8

I ran into a bit of difficulty when using @john-myczek's code with a bound TextBox. As the TextBox doesn't raise a focus event when it's updated, the watermark would remain visible underneath the new text. To fix this, I simply added another event handler:

if (d is ComboBox || d is TextBox)
{
    control.GotKeyboardFocus += Control_GotKeyboardFocus;
    control.LostKeyboardFocus += Control_Loaded;

    if (d is TextBox)
        (d as TextBox).TextChanged += Control_TextChanged;
}


private static void Control_TextChanged(object sender, RoutedEventArgs e)
{
    var tb = (TextBox)sender;
    if (ShouldShowWatermark(tb))
    {
        ShowWatermark(tb);
    }
    else
    {
        RemoveWatermark(tb);
    }
}
Community
  • 1
  • 1
Jonathan
  • 13,947
  • 17
  • 94
  • 123
7

simplest Way to WaterMark Of TextBox

 <Window.Resources>
    <Style x:Key="MyWaterMarkStyle" TargetType="{x:Type TextBox}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TextBox}">
                    <Grid>
                        <Border Background="White" BorderBrush="#FF7D8683" BorderThickness="1"/>
                        <ScrollViewer x:Name="PART_ContentHost" Margin="5,0,0,0" VerticalAlignment="Center" />
                        <Label Margin="5,0,0,0" x:Name="WaterMarkLabel" Content="{TemplateBinding Tag}" VerticalAlignment="Center"
                           Visibility="Collapsed" Foreground="Gray" FontFamily="Arial"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="Text" Value=""/>
                            </MultiTrigger.Conditions>
                            <Setter Property="Visibility" TargetName="WaterMarkLabel" Value="Visible"/>
                        </MultiTrigger>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter Property="Foreground" Value="DimGray"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

and add textbox StaticResource style

  <TextBox
                Style="{StaticResource MyWaterMarkStyle}"
                Tag="Search Category"
                Grid.Row="0"
                Text="{Binding CategorySearch,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                TextSearch.Text="Search Category"
                >
Ghotekar Rahul
  • 322
  • 3
  • 10
5

@Veton - I really like the simplicity of your solution but my reputation isn't high enough to bump you yet.

@Tim Murphy - That "Two-way binding requires Path or XPath" error was an easy fix... updated code including some other little tweaks (only WPF tested):

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;

public class TextBoxWatermarked : TextBox
{
  public string Watermark
  {
    get { return (string)GetValue(WaterMarkProperty); }
    set { SetValue(WaterMarkProperty, value); }
  }
  public static readonly DependencyProperty WaterMarkProperty =
      DependencyProperty.Register("Watermark", typeof(string), typeof(TextBoxWatermarked), new PropertyMetadata(new PropertyChangedCallback(OnWatermarkChanged)));

  private bool _isWatermarked = false;
  private Binding _textBinding = null;

  public TextBoxWatermarked()
  {
    Loaded += (s, ea) => ShowWatermark();
  }

  protected override void OnGotFocus(RoutedEventArgs e)
  {
    base.OnGotFocus(e);
    HideWatermark();
  }

  protected override void OnLostFocus(RoutedEventArgs e)
  {
    base.OnLostFocus(e);
    ShowWatermark();
  }

  private static void OnWatermarkChanged(DependencyObject sender, DependencyPropertyChangedEventArgs ea)
  {
    var tbw = sender as TextBoxWatermarked;
    if (tbw == null || !tbw.IsLoaded) return; //needed to check IsLoaded so that we didn't dive into the ShowWatermark() routine before initial Bindings had been made
    tbw.ShowWatermark();
  }

  private void ShowWatermark()
  {
    if (String.IsNullOrEmpty(Text) && !String.IsNullOrEmpty(Watermark))
    {
      _isWatermarked = true;

      //save the existing binding so it can be restored
      _textBinding = BindingOperations.GetBinding(this, TextProperty);

      //blank out the existing binding so we can throw in our Watermark
      BindingOperations.ClearBinding(this, TextProperty);

      //set the signature watermark gray
      Foreground = new SolidColorBrush(Colors.Gray);

      //display our watermark text
      Text = Watermark;
    }
  }

  private void HideWatermark()
  {
    if (_isWatermarked)
    {
      _isWatermarked = false;
      ClearValue(ForegroundProperty);
      Text = "";
      if (_textBinding != null) SetBinding(TextProperty, _textBinding);
    }
  }

}
Beej
  • 51
  • 1
  • 2
3

you can use GetFocus() and LostFocus() events to do this

here is the example:

    private void txtData1_GetFocus(object sender, RoutedEventArgs e)
    {
        if (txtData1.Text == "TextBox1abc")
        {
            txtData1.Text = string.Empty;
        }
    }

    private void txtData1_LostFocus(object sender, RoutedEventArgs e)
    {
        if (txtData1.Text == string.Empty)
        {
            txtData1.Text = "TextBox1abc";
        }
    }
arne
  • 4,514
  • 1
  • 28
  • 47
durga
  • 31
  • 1
2

MahApps.Metro for WPF has a built-in watermark control, if you'd rather not roll your own. It's fairly straightforward to use.

 <AdornerDecorator>
            <TextBox Name="txtSomeText"
                     Width="200"
                     HorizontalAlignment="Right">
                <Controls:TextBoxHelper.Watermark>I'm a watermark!</Controls:TextBoxHelper.Watermark>
            </TextBox>
        </AdornerDecorator>
Michael Armes
  • 1,056
  • 2
  • 17
  • 31
  • 2
    In newer version of `MahApps.Metro`, it would be: ` Enter text here... ` where namespace is `xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"` – nam Jul 29 '21 at 21:11
2

Set up the text box with placeholder text in a soft color...

public MainWindow ( )
{
    InitializeComponent ( );
    txtInput.Text = "Type something here...";
    txtInput.Foreground = Brushes.DimGray;
}

When the text box gets the focus, clear it and change the text color

private void txtInput_GotFocus ( object sender, EventArgs e )
{
    MessageBox.Show ( "got focus" );
    txtInput.Text = "";
    txtInput.Foreground = Brushes.Red;
}
Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164
jackm
  • 21
  • 1
2
<Window.Resources>

    <Style x:Key="TextBoxUserStyle" BasedOn="{x:Null}" TargetType="{x:Type TextBox}">
      <Setter Property="Foreground" Value="Black"/>
      <Setter Property="HorizontalAlignment" Value="Center"/>
      <Setter Property="VerticalContentAlignment" Value="Center"/>
      <Setter Property="Width" Value="225"/>
      <Setter Property="Height" Value="25"/>
      <Setter Property="FontSize" Value="12"/>
      <Setter Property="Padding" Value="1"/>
      <Setter Property="Margin" Value="5"/>
      <Setter Property="AllowDrop" Value="true"/>
      <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type TextBox}">
            <Border x:Name="OuterBorder" BorderBrush="#5AFFFFFF" BorderThickness="1,1,1,1" CornerRadius="4,4,4,4">
              <Border x:Name="InnerBorder" Background="#FFFFFFFF" BorderBrush="#33000000" BorderThickness="1,1,1,1" CornerRadius="3,3,3,3">
                <ScrollViewer SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" x:Name="PART_ContentHost"/>
              </Border>
            </Border>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>

    <Style x:Key="PasswordBoxVistaStyle" BasedOn="{x:Null}" TargetType="{x:Type PasswordBox}">
      <Setter Property="Foreground" Value="Black"/>
      <Setter Property="HorizontalAlignment" Value="Center"/>
      <Setter Property="VerticalContentAlignment" Value="Center"/>
      <Setter Property="Width" Value="225"/>
      <Setter Property="Height" Value="25"/>
      <Setter Property="FontSize" Value="12"/>
      <Setter Property="Padding" Value="1"/>
      <Setter Property="Margin" Value="5"/>
      <Setter Property="AllowDrop" Value="true"/>
      <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type PasswordBox}">
            <Border x:Name="OuterBorder" BorderBrush="#5AFFFFFF" BorderThickness="1,1,1,1" CornerRadius="4,4,4,4">
              <Border x:Name="InnerBorder" Background="#FFFFFFFF" BorderBrush="#33000000" BorderThickness="1,1,1,1" CornerRadius="3,3,3,3">
                <Grid>
                  <Label x:Name="lblPwd" Content="Password" FontSize="11" VerticalAlignment="Center" Margin="2,0,0,0" FontFamily="Verdana" Foreground="#828385" Padding="0"/>
                  <ScrollViewer SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" x:Name="PART_ContentHost"/>
                </Grid>
              </Border>
            </Border>
            <ControlTemplate.Triggers>
              <Trigger Property="IsFocused" Value="True">
                <Setter Property="Visibility" TargetName="lblPwd" Value="Hidden"/>
              </Trigger>
            </ControlTemplate.Triggers>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </Window.Resources>


        <PasswordBox Style="{StaticResource PasswordBoxVistaStyle}" Margin="169,143,22,0" Name="txtPassword" FontSize="14" TabIndex="2" Height="31" VerticalAlignment="Top" />

This can help check it with your code.When applied to password box,it will show Password,which will disappear when usertypes.

abmv
  • 7,042
  • 17
  • 62
  • 100
1

Well here is mine: not necessarily the best, but as it is simple it is easy to edit to your taste.

<UserControl x:Class="WPFControls.ShadowedTextBox"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WPFControls"
    Name="Root">
<UserControl.Resources>
    <local:ShadowConverter x:Key="ShadowConvert"/>
</UserControl.Resources>
<Grid>
    <TextBox Name="textBox" 
             Foreground="{Binding ElementName=Root, Path=Foreground}"
             Text="{Binding ElementName=Root, Path=Text, UpdateSourceTrigger=PropertyChanged}"
             TextChanged="textBox_TextChanged"
             TextWrapping="Wrap"
             VerticalContentAlignment="Center"/>
    <TextBlock Name="WaterMarkLabel"
           IsHitTestVisible="False"
           Foreground="{Binding ElementName=Root,Path=Foreground}"
           FontWeight="Thin"
           Opacity=".345"
           FontStyle="Italic"
           Text="{Binding ElementName=Root, Path=Watermark}"
           VerticalAlignment="Center"
           TextWrapping="Wrap"
           TextAlignment="Center">
        <TextBlock.Visibility>
            <MultiBinding Converter="{StaticResource ShadowConvert}">
                <Binding ElementName="textBox" Path="Text"/>
            </MultiBinding>
        </TextBlock.Visibility> 
    </TextBlock>
</Grid>

The converter, as it is written now it is not necessary that it is a MultiConverter, but in this wasy it can be extended easily

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace WPFControls
{
    class ShadowConverter:IMultiValueConverter
    {
        #region Implementation of IMultiValueConverter

        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            var text = (string) values[0];
            return text == string.Empty
                       ? Visibility.Visible
                       : Visibility.Collapsed;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            return new object[0];
        }

        #endregion
    }
}

and finally the code behind:

using System.Windows;
using System.Windows.Controls;

namespace WPFControls
{
    /// <summary>
    /// Interaction logic for ShadowedTextBox.xaml
    /// </summary>
    public partial class ShadowedTextBox : UserControl
    {
        public event TextChangedEventHandler TextChanged;

        public ShadowedTextBox()
        {
            InitializeComponent();
        }

        public static readonly DependencyProperty WatermarkProperty =
            DependencyProperty.Register("Watermark",
                                        typeof (string),
                                        typeof (ShadowedTextBox),
                                        new UIPropertyMetadata(string.Empty));

        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text",
                                        typeof (string),
                                        typeof (ShadowedTextBox),
                                        new UIPropertyMetadata(string.Empty));

        public static readonly DependencyProperty TextChangedProperty =
            DependencyProperty.Register("TextChanged",
                                        typeof (TextChangedEventHandler),
                                        typeof (ShadowedTextBox),
                                        new UIPropertyMetadata(null));

        public string Watermark
        {
            get { return (string)GetValue(WatermarkProperty); }
            set
            {
                SetValue(WatermarkProperty, value);
            }
        }

        public string Text
        {
            get { return (string) GetValue(TextProperty); }
            set{SetValue(TextProperty,value);}
        }

        private void textBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (TextChanged != null) TextChanged(this, e);
        }

        public void Clear()
        {
            textBox.Clear();
        }

    }
}
lobsterism
  • 3,469
  • 2
  • 22
  • 36
Dabblernl
  • 15,831
  • 18
  • 96
  • 148
1

Here's my approach Is great for MVVM where I also check if the Text box has focus, you can also use a regular trigger just for the text value as well the point is I just change the background Image when value changes:

                    <TextBox.Style>
                        <Style TargetType="TextBox">

                            <Style.Triggers>
                                <MultiTrigger>
                                    <MultiTrigger.Conditions>
                                        <Condition Property="IsFocused" Value="True"/>
                                        <Condition Property="Text" Value=""/>
                                    </MultiTrigger.Conditions>
                                    <MultiTrigger.Setters>
                                        <Setter Property="Background">
                                            <Setter.Value>
                                                <ImageBrush ImageSource="/Images/Scan.PNG" Stretch="Uniform" AlignmentX="Left"/>
                                            </Setter.Value>
                                        </Setter>
                                    </MultiTrigger.Setters>
                                </MultiTrigger>

                            </Style.Triggers>
                        </Style>
                    </TextBox.Style>
                </TextBox>
Joe B
  • 738
  • 7
  • 28
1
<TextBox x:Name="OrderTxt" HorizontalAlignment="Left" VerticalAlignment="Top" VerticalContentAlignment="Center" Margin="10,10,0,0" Width="188" Height="32"/>

<Label IsHitTestVisible="False" Content="Order number" DataContext="{Binding ElementName=OrderTxt}" Foreground="DarkGray">
    <Label.Style>
        <Style TargetType="{x:Type Label}">
            <Setter Property="Visibility" Value="Collapsed"/>
            <Setter Property="Width" Value="{Binding Width}"/>
            <Setter Property="Height" Value="{Binding Height}"/>
            <Setter Property="Margin" Value="{Binding Margin}"/>
            <Setter Property="VerticalAlignment" Value="{Binding VerticalAlignment}"/>
            <Setter Property="HorizontalAlignment" Value="{Binding HorizontalAlignment}"/>
            <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment}"/>
            <Style.Triggers>
                <DataTrigger Binding="{Binding Text}" Value="">
                    <Setter Property="Visibility" Value="Visible"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Label.Style>
</Label>
Mohit Jain
  • 30,259
  • 8
  • 73
  • 100
Christophe
  • 11
  • 1
1
<TextBox Controls:TextBoxHelper.Watermark="Watermark"/>

Add mahapps.metro to your project. Add textbox with the above code to the window.

Ketan Dubey
  • 430
  • 8
  • 19
  • I wonder why nobody upvoted this answer. It works perfectly when Wpf app is created with Template Studio in Visual Studio 2022 – RisingHerc Jul 20 '23 at 09:10
1

Look at another simple solotion:

I'm focused GotFocus and LostFocus events.

XAML:

<Grid>
<TextBlock x:Name="DosyaİhtivaEdenDizinYansıması" Text="Hedef Dizin Belirtin" VerticalAlignment="Center" HorizontalAlignment="Center" TextAlignment="Center" Foreground="White" Background="Transparent" Width="500" MinWidth="300" Margin="10,0,0,0" Opacity="0.7"/>
<TextBox x:Name="DosyaİhtivaEdenDizin" CaretBrush="White" Foreground="White" Background="Transparent" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" MinHeight="40" BorderThickness="1" BorderBrush="White" Width="500" MinWidth="300" Margin="10,0,0,0" GotFocus="DosyaİhtivaEdenDizin_GotFocus" LostFocus="DosyaİhtivaEdenDizin_LostFocus"/>
</Grid>

C#:

    #region DosyaİhtivaEdenDizin
    private void DosyaİhtivaEdenDizin_GotFocus(object sender, RoutedEventArgs e)
    {
        if (DosyaİhtivaEdenDizin.Text.Length == 0)
        {
            DosyaİhtivaEdenDizinYansıması.Text = "";
        }
    }

    private void DosyaİhtivaEdenDizin_LostFocus(object sender, RoutedEventArgs e)
    {
        if (DosyaİhtivaEdenDizin.Text.Length == 0)
        {
            DosyaİhtivaEdenDizinYansıması.Text = "Hedef Dizin Belirtin";
        }
    }
    #endregion
EgoistDeveloper
  • 775
  • 2
  • 13
  • 37
  • The first `if (DosyaİhtivaEdenDizin.Text.Length == 0)` should be `if (DosyaİhtivaEdenDizin.Text equals "Hedef Dizin Belirtin")` – Leon Dec 15 '17 at 04:32
1

Here is the simplest solution:

            <Grid>
                <Label Content="Placeholder text" VerticalAlignment="Center" Margin="10">
                    <Label.Style>
                        <Style TargetType="Label">
                            <Setter Property="Foreground" Value="Transparent"/>
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding Expression}" Value="">
                                    <Setter Property="Foreground" Value="Gray"/>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </Label.Style>
                </Label>
                <TextBox HorizontalAlignment="Stretch" Margin="5" Background="Transparent"
                 Text="{Binding Expression, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" Padding="5">
                </TextBox>
        </Grid>

This is a textbox with transparent backgound overlaying a label. The label's gray text is turned transparent by a data trigger that fires whenever the bound text is something other than empty string.

AQuirky
  • 4,691
  • 2
  • 32
  • 51
1

Also, see this answer. You can accomplish this much more easily with a VisualBrush and some triggers in a Style:

 <TextBox>
    <TextBox.Style>
        <Style TargetType="TextBox" xmlns:sys="clr-namespace:System;assembly=mscorlib">
            <Style.Resources>
                <VisualBrush x:Key="CueBannerBrush" AlignmentX="Left" AlignmentY="Center" Stretch="None">
                    <VisualBrush.Visual>
                        <Label Content="Search" Foreground="LightGray" />
                    </VisualBrush.Visual>
                </VisualBrush>
            </Style.Resources>
            <Style.Triggers>
                <Trigger Property="Text" Value="{x:Static sys:String.Empty}">
                    <Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
                </Trigger>
                <Trigger Property="Text" Value="{x:Null}">
                    <Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
                </Trigger>
                <Trigger Property="IsKeyboardFocused" Value="True">
                    <Setter Property="Background" Value="White" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </TextBox.Style>
</TextBox>

To increase the re-usability of this Style, you can also create a set of attached properties to control the actual cue banner text, color, orientation etc.

Ali Asad
  • 1,235
  • 1
  • 18
  • 33
1

Here is another simple solution in XAML:

XAML:

       <TextBox>
            <TextBox.Resources>
                <Style TargetType="TextBox">
                    <Style.Triggers>
                        <Trigger Property="IsFocused" Value="True">
                            <!--text color-->
                            <Setter Property="Foreground" Value="Black"/>
                            <Setter Property="Text" Value=""/>
                        </Trigger>
                        <Trigger Property="IsFocused" Value="False">
                            <!--placeholder color-->
                            <Setter Property="Foreground" Value="Gray"/>
                            <!--placeholder here-->
                            <Setter Property="Text" Value="Placeholder"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </TextBox.Resources>
        </TextBox>
robbelDev
  • 11
  • 2
1

My solution is quite simple.

In my login window. the xaml is like this.

 <DockPanel HorizontalAlignment="Center" VerticalAlignment="Center" Height="80" Width="300" LastChildFill="True">
        <Button Margin="5,0,0,0" Click="login_Click" DockPanel.Dock="Right"  VerticalAlignment="Center" ToolTip="Login to system">
            Login
        </Button>
        <StackPanel>
            <TextBox x:Name="userNameWatermarked" Height="25" Foreground="Gray" Text="UserName" GotFocus="userNameWatermarked_GotFocus"></TextBox>
            <TextBox x:Name="userName" Height="25"  TextChanged="loginElement_TextChanged" Visibility="Collapsed" LostFocus="userName_LostFocus" ></TextBox>
            <TextBox x:Name="passwordWatermarked" Height="25" Foreground="Gray" Text="Password"  Margin="0,5,0,5" GotFocus="passwordWatermarked_GotFocus"></TextBox>
            <PasswordBox x:Name="password" Height="25" PasswordChanged="password_PasswordChanged" KeyUp="password_KeyUp" LostFocus="password_LostFocus" Margin="0,5,0,5" Visibility="Collapsed"></PasswordBox>
            <TextBlock x:Name="loginError" Visibility="Hidden" Foreground="Red" FontSize="12"></TextBlock>
        </StackPanel>
    </DockPanel>

the code is like this.

private void userNameWatermarked_GotFocus(object sender, RoutedEventArgs e)
    {
        userNameWatermarked.Visibility = System.Windows.Visibility.Collapsed;
        userName.Visibility = System.Windows.Visibility.Visible;
        userName.Focus();
    }

    private void userName_LostFocus(object sender, RoutedEventArgs e)
    {
        if (string.IsNullOrEmpty(this.userName.Text))
        {
            userName.Visibility = System.Windows.Visibility.Collapsed;
            userNameWatermarked.Visibility = System.Windows.Visibility.Visible;
        }
    }

    private void passwordWatermarked_GotFocus(object sender, RoutedEventArgs e)
    {
        passwordWatermarked.Visibility = System.Windows.Visibility.Collapsed;
        password.Visibility = System.Windows.Visibility.Visible;
        password.Focus();
    }

    private void password_LostFocus(object sender, RoutedEventArgs e)
    {
        if (string.IsNullOrEmpty(this.password.Password))
        {
            password.Visibility = System.Windows.Visibility.Collapsed;
            passwordWatermarked.Visibility = System.Windows.Visibility.Visible;
        }
    }

Just decide to hide or show the watermark textbox is enough. Though not beautiful,but work well.

Ruikuan
  • 19
  • 2
0

This technique uses the Background property to show / hide placeholder textbox.
Placeholder is shown event when Textbox has the focus

How it works:

  • When empty, TextBox background set to Transparent to show PlaceHolder text.
  • When not empty background set to White to cover up PlaceHolder text.

Here is basic example. For my own purposes I turned this into a UserControl.

<Grid>
    <Grid.Resources>
        <ux:NotEmptyConverter x:Key="NotEmptyConverter" />

        <Style TargetType="{x:Type Control}" x:Key="DefaultStyle">
            <Setter Property="FontSize" Value="20" />
            <Setter Property="Margin" Value="10"/>
            <Setter Property="VerticalAlignment" Value="Center"></Setter>
            <Setter Property="VerticalContentAlignment" Value="Center"></Setter>
        </Style>

        <Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource DefaultStyle}"></Style>

    </Grid.Resources>

    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <TextBox Grid.Row="0" Text="Placeholder Text Is Here" Foreground="DarkGray" />
    <TextBox Grid.Row="0" Name="TextBoxEdit" 
            Text="{Binding Path=FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
        <TextBox.Style>
            <Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource DefaultStyle}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=FirstName.Length, FallbackValue=0, TargetNullValue=0}" Value="0">
                        <Setter Property="Background" Value="Transparent"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Path=FirstName, FallbackValue=0, TargetNullValue=0, Converter={StaticResource NotEmptyConverter}}" Value="false">
                        <Setter Property="Background" Value="White"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </TextBox.Style>
    </TextBox>
</Grid>

Here is the ValueConverter to detect non-empty strings in the DataTrigger.

public class NotEmptyConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var s = value as string;
        return string.IsNullOrEmpty(s);
    }
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }
}
0

You can keep a seperate value for the entered text and you can set it along with the "Text" field of the text box in the "GotFocus" and "LostFocus" events. When you get the focus, you'll want to clear the text box if there is no value. And when you loss the focus, you'll want to set the get the "Text" value from the text box and then reset the "Text" value of the text box to the place holder if it is empty.

private String username = "";

private void usernameTextBox_GotFocus(object sender, RoutedEventArgs e) {
  if (String.IsNullOrEmpty(username)) {
    usernameTextBox.Text = "";
  }
}

private void usernameTextBox_LostFocus(object sender, RoutedEventArgs e) {
  username = usernameTextBox.Text;
  if (String.IsNullOrEmpty(usernameTextBox.Text)) {
    usernameTextBox.Text = "Username";
  }
}

Then you just have to make sure that the "Text" value of the text box is initialized to the place holder text.

<TextBox x:Name="usernameTextBox" Text="Username" GotFocus="usernameTextBox_GotFocus" LostFocus="usernameTextBox_LostFocus" />

You can further extract this into a class that extends the "TextBox" class and then reuse it through out your project.

namespace UI {
  public class PlaceholderTextBox : TextBox {
    public String Value { get; set; }
    public String PlaceholderText { get; set; }
    public Brush PlaceholderBrush { get; set; }
    private Brush ValuedBrush { get; set; }

    public PlaceholderTextBox() : base() {}

    protected override void OnInitialized(EventArgs e) {
      base.OnInitialized(e);

      ValuedBrush = this.Foreground;

      if (String.IsNullOrEmpty(this.Text)) {
        this.Text = PlaceholderText;
        this.Foreground = PlaceholderBrush;
      }
    }

    protected override void OnGotFocus(System.Windows.RoutedEventArgs e) {
      this.Foreground = ValuedBrush;
      if (String.IsNullOrEmpty(Value)) {
        this.Text = "";
      }

      base.OnGotFocus(e);
    }

    protected override void OnLostFocus(System.Windows.RoutedEventArgs e) {
      Value = this.Text;
      if (String.IsNullOrEmpty(this.Text)) {
        this.Text = PlaceholderText;
        this.Foreground = PlaceholderBrush;
      }

      base.OnLostFocus(e);
    }
  }
}

And then this can be added in the directly in the xaml.

<Window x:Class="UI.LoginWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:m="clr-namespace:UI"
        Initialized="Window_Initialized">
    <Grid>
        <m:PlaceholderTextBox x:Name="usernameTextBox" PlaceholderText="Username" PlaceholderBrush="Gray" />
    </Grid>
</Window>
0

If rather than having the watermark's visibility depend on the control's focus state, you want it to depend on whether the user has entered any text, you can update John Myczek's answer (from OnWatermarkChanged down) to

static void OnWatermarkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    var textbox = (TextBox)d;
    textbox.Loaded += UpdateWatermark;
    textbox.TextChanged += UpdateWatermark;
}

static void UpdateWatermark(object sender, RoutedEventArgs e) {
    var textbox = (TextBox)sender;
    var layer = AdornerLayer.GetAdornerLayer(textbox);
    if (layer != null) {
        if (textbox.Text == string.Empty) {
            layer.Add(new WatermarkAdorner(textbox, GetWatermark(textbox)));
        } else {
            var adorners = layer.GetAdorners(textbox);
            if (adorners == null) {
                return;
            }

            foreach (var adorner in adorners) {
                if (adorner is WatermarkAdorner) {
                    adorner.Visibility = Visibility.Hidden;
                    layer.Remove(adorner);
                }
            }
        }
    }
}

This makes more sense if your textbox gets focus automatically when displaying the form, or when databinding to the Text property.

Also if your watermark is always just a string, and you need the style of the watermark to match the style of the textbox, then in the Adorner do:

contentPresenter = new ContentPresenter {
    Content = new TextBlock {
        Text = (string)watermark,
        Foreground = Control.Foreground,
        Background = Control.Background,
        FontFamily = Control.FontFamily,
        FontSize = Control.FontSize,
        ...
    },
    ...
}
lobsterism
  • 3,469
  • 2
  • 22
  • 36
0

hi i put this task into a behavior. so you just have to add somthing like this to your textbox

<i:Interaction.Behaviors>
         <Behaviors:TextBoxWatermarkBehavior Label="Test Watermark" LabelStyle="{StaticResource StyleWatermarkLabel}"/>
</i:Interaction.Behaviors>

you can find my blog post here

blindmeis
  • 22,175
  • 7
  • 55
  • 74
0

I decided to solve this via a Behavior. It uses a Hint property to define the text to display (could also be an object, if you prefer) and a Value property to evaluate wether the hint should be visible or not.

The Behavior is declared as follows:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Interactivity;
using System.Windows.Media;

    public class HintBehavior : Behavior<ContentControl>
    {
        public static readonly DependencyProperty HintProperty = DependencyProperty
            .Register("Hint", typeof (string), typeof (HintBehavior)
            //, new FrameworkPropertyMetadata(null, OnHintChanged)
            );

        public string Hint
        {
            get { return (string) GetValue(HintProperty); }
            set { SetValue(HintProperty, value); }
        }

        public static readonly DependencyProperty ValueProperty = DependencyProperty
            .Register("Value", typeof (object), typeof (HintBehavior)
                , new FrameworkPropertyMetadata(null, OnValueChanged));

        private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var visible = e.NewValue == null;
            d.SetValue(VisibilityProperty, visible ? Visibility.Visible : Visibility.Collapsed);
        }

        public object Value
        {
            get { return GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        public static readonly DependencyProperty VisibilityProperty = DependencyProperty
            .Register("Visibility", typeof (Visibility), typeof (HintBehavior)
                , new FrameworkPropertyMetadata(Visibility.Visible
                    //, new PropertyChangedCallback(OnVisibilityChanged)
                    ));

        public Visibility Visibility
        {
            get { return (Visibility) GetValue(VisibilityProperty); }
            set { SetValue(VisibilityProperty, value); }
        }

        public static readonly DependencyProperty ForegroundProperty = DependencyProperty
            .Register("Foreground", typeof (Brush), typeof (HintBehavior)
                , new FrameworkPropertyMetadata(new SolidColorBrush(Colors.DarkGray)
                    //, new PropertyChangedCallback(OnForegroundChanged)
                    ));

        public Brush Foreground
        {
            get { return (Brush) GetValue(ForegroundProperty); }
            set { SetValue(ForegroundProperty, value); }
        }

        public static readonly DependencyProperty MarginProperty = DependencyProperty
            .Register("Margin", typeof (Thickness), typeof (HintBehavior)
                , new FrameworkPropertyMetadata(new Thickness(4, 5, 0, 0)
                    //, new PropertyChangedCallback(OnMarginChanged)
                    ));

        public Thickness Margin
        {
            get { return (Thickness) GetValue(MarginProperty); }
            set { SetValue(MarginProperty, value); }
        }


        private static ResourceDictionary _hintBehaviorResources;

        public static ResourceDictionary HintBehaviorResources
        {
            get
            {
                if (_hintBehaviorResources == null)
                {
                    var res = new ResourceDictionary
                    {
                        Source = new Uri("/Mayflower.Client.Core;component/Behaviors/HintBehaviorResources.xaml",
                            UriKind.RelativeOrAbsolute)
                    };
                    _hintBehaviorResources = res;
                }
                return _hintBehaviorResources;
            }
        }


        protected override void OnAttached()
        {
            base.OnAttached();
            var t = (ControlTemplate) HintBehaviorResources["HintBehaviorWrapper"];
            AssociatedObject.Template = t;
            AssociatedObject.Loaded += OnLoaded;
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            AssociatedObject.Loaded -= OnLoaded;
            var label = (Label) AssociatedObject.Template.FindName("PART_HintLabel", AssociatedObject);
            label.DataContext = this;
            //label.Content = "Hello...";
            label.SetBinding(UIElement.VisibilityProperty, new Binding("Visibility") {Source = this, Mode = BindingMode.OneWay});
            label.SetBinding(ContentControl.ContentProperty, new Binding("Hint") {Source = this, Mode = BindingMode.OneWay});
            label.SetBinding(Control.ForegroundProperty, new Binding("Foreground") {Source = this, Mode = BindingMode.OneWay});
            label.SetBinding(FrameworkElement.MarginProperty, new Binding("Margin") {Source = this, Mode = BindingMode.OneWay});
        }
    }

It wraps the target with it's own template, adding to it a label:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ControlTemplate x:Key="HintBehaviorWrapper" TargetType="{x:Type ContentControl}">
        <Grid>
            <ContentPresenter Content="{TemplateBinding Content}" />
            <Label x:Name="PART_HintLabel" IsHitTestVisible="False" Padding="0" />
        </Grid>
    </ControlTemplate>
</ResourceDictionary>

To use it, just add it as a behavior and bind your values (in my case I add it in a ControlTemplate, hence the binding):

<ContentControl>
    <i:Interaction.Behaviors>
        <behaviors:HintBehavior Value="{Binding Property, RelativeSource={RelativeSource TemplatedParent}}"
                                                        Hint="{Binding Hint, RelativeSource={RelativeSource TemplatedParent}}" />
    </i:Interaction.Behaviors>
    <TextBox ... />
</ContentControl>

I would love feedback if this is considered a clean solution. It does not require static dictionaries and hence has no memory leak.

uTILLIty
  • 473
  • 4
  • 9
0

I found this way to do it in a very fast and easy way

<ComboBox x:Name="comboBox1" SelectedIndex="0" HorizontalAlignment="Left" Margin="202,43,0,0" VerticalAlignment="Top" Width="149">
  <ComboBoxItem Visibility="Collapsed">
    <TextBlock Foreground="Gray" FontStyle="Italic">Please select ...</TextBlock>
  </ComboBoxItem>
  <ComboBoxItem Name="cbiFirst1">First Item</ComboBoxItem>
  <ComboBoxItem Name="cbiSecond1">Second Item</ComboBoxItem>
  <ComboBoxItem Name="cbiThird1">third Item</ComboBoxItem>
</ComboBox>

Maybe it can help to anyone trying to do this

Source: http://www.admindiaries.com/displaying-a-please-select-watermark-type-text-in-a-wpf-combobox/

CrApHeR
  • 2,595
  • 4
  • 25
  • 40
0
namespace PlaceholderForRichTexxBoxInWPF
{
public MainWindow()
        {
            InitializeComponent();
            Application.Current.MainWindow.WindowState = WindowState.Maximized;// maximize window on load

            richTextBox1.GotKeyboardFocus += new KeyboardFocusChangedEventHandler(rtb_GotKeyboardFocus);
            richTextBox1.LostKeyboardFocus += new KeyboardFocusChangedEventHandler(rtb_LostKeyboardFocus);
            richTextBox1.AppendText("Place Holder");
            richTextBox1.Foreground = Brushes.Gray;
        }
 private void rtb_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
        {
            if (sender is RichTextBox)
            {
                TextRange textRange = new TextRange(richTextBox1.Document.ContentStart, richTextBox1.Document.ContentEnd); 

                if (textRange.Text.Trim().Equals("Place Holder"))
                {
                    ((RichTextBox)sender).Foreground = Brushes.Black;
                    richTextBox1.Document.Blocks.Clear();
                }
            }
        }


        private void rtb_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
        {
            //Make sure sender is the correct Control.
            if (sender is RichTextBox)
            {
                //If nothing was entered, reset default text.
                TextRange textRange = new TextRange(richTextBox1.Document.ContentStart, richTextBox1.Document.ContentEnd); 

                if (textRange.Text.Trim().Equals(""))
                {
                    ((RichTextBox)sender).Foreground = Brushes.Gray;
                    ((RichTextBox)sender).AppendText("Place Holder");
                }
            }
        }
}
kupaff
  • 329
  • 4
  • 5
0
<TextBox    Grid.Column="0" Name="TextBox_SearchBar" AcceptsReturn="False" AcceptsTab="False" TextWrapping="Wrap" KeyUp="TextBox_SearchBar_KeyUp" />
<TextBox    Grid.Column="0" Name="TextBox_Watermark" Text="Search Test Sets" Foreground="Gray" GotFocus="TextBox_Watermark_GotFocus" />

private void TextBox_Watermark_GotFocus( object sender, RoutedEventArgs e ) {
    TextBox_Watermark.Visibility = Visibility.Hidden;
    TextBox_SearchBar.Focus();
}

This creates two TextBoxes, one for the field which receives the watermark and one for the watermark. The field which receives the watermark could be a ComboBox or other control ( as long as you get the alignment correct and it gains Focus accordingly ).

The watermark MUST be a TextBox or other control which can GetFocus ( a TextBlock can not GetFocus, as I learned the hard way ). When the watermark TextBox gets focus, it hides itself and gives access to the other TextBox.

If you want to reshow the watermark after the the SearchBar TextBox has lost focus, simply add a LostFocus event handler to TextBox_SearchBar which checks the input and toggles TextBox_Watermark visibility to Hidden or Visible, accordingly.

heyyou482
  • 101
  • 1
  • 5
  • While this code snippet may solve the problem, it doesn't explain why or how it answers the question. Please [include an explanation for your code](//meta.stackexchange.com/q/114762/269535), as that really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion. **Flaggers / reviewers:** [For code-only answers such as this one, downvote, don't delete!](//meta.stackoverflow.com/a/260413/2747593) – Scott Weldon Nov 28 '16 at 19:02
0

Telerik have a Control called the RadWatermarkTextBox specifically to solve this issue. If you are using Telerik controls, simply use in the following manner

<telerik:RadWatermarkTextBox
    Text="{Binding Path=MyTextBoxText}"
    WatermarkContent="Please enter some text" />
ChickenFeet
  • 2,653
  • 22
  • 26