Watermark / hint / placeholder text in TextBox?
Asked Answered
B

35

324

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

Blast answered 7/5, 2009 at 10:42 Comment(4)
This is called a 'placeholder' in HTML. I mention this to help people google this page.Rudyrudyard
If you are writing UWP apps on Windows 10 this is much easier. <TextBox PlaceholderText="Search"/> More info: msdn.microsoft.com/en-us/library/windows/apps/…Nichrome
This question has so many dupes... I have VTC all the following as a dupe of this one: #40746200 , #11873878, #5594149, #7426118Celebrity
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.Jariah
M
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.

Manslayer answered 7/5, 2009 at 10:46 Comment(5)
How can I apply it in a passwordbox?Candiot
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...Poodle
Consider creating a UserControl.Manslayer
While I really appreciate your effort to help the community, I really need to say this is far from being even a decent approach.Epirus
This code was made by Andy L. You can find it on codeproject.Terrarium
A
472

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.

Anuradhapura answered 7/5, 2009 at 19:10 Comment(32)
Hey john, tried implementing this. Getting an error saying: controls is an undeclared prefix", have you any idea ?Diaeresis
Hey I posted a question about it: linkDiaeresis
Looks like you got your answer. Just need the namespace definition.Anuradhapura
+1, but i think that event handlers should be unwired somewehere(control.GotKeyboardFocus += Control_GotKeyboardFocus...)Waters
@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.Behold
@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.Tati
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)Tati
@JohnMyczek In order to localize the watermark: how can I bind the TextBox.Text in the Watermark xaml declaration to a property from ViewModel?Tati
Anyone knows the reason that this does not work on the passwordboxInattentive
@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; }".Doormat
@UrbanEsc: I post my improved solution that includes a better compatibility to ComboBox & PasswordBox.Doormat
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; }Carburize
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.Quarrelsome
@JohnMyczek I would also like to know how the TextBlock.Text property can be bound to the view model...Abranchiate
@Abranchiate @Tati 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; }Selfidentity
Incredible. Thanks for the drag-n-dropable code. Got the exact results I wanted in about 5 minutes!Maidenhair
Anyone got msdn.microsoft.com/en-us/library/bb514547%28v=vs.90%29.aspx this error?Equinox
Never mind check out #5397834Equinox
This is great but not for RightToLeft layouts. when I use FlowDirection="RightToLeft then the text becomes flipped and unreadable.Touchandgo
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.Amalberga
The watermark remains visible when the textbox is covered by another UI element. The Extended WPF Toolkit WatermarkTextBox didn't have this problem.Basipetal
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.Delirium
I voted this one down because when loading text from the code behind, it doesn't actually remove the watermark.Dropout
Clarification: you need to add xmlns:controls="clr-your namespace" to your XAML headerSoubise
How can I get the watermark to come back from code behind?Benoite
How can I remove the watermark when my datacontext changes? ThanksPolytheism
@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.Marelda
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.Ezara
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.Particularize
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.Particularize
@Ezara 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.Knit
All this for just a watermark!!!?Rotund
G
391

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>
Geochronology answered 10/2, 2014 at 8:43 Comment(16)
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.Condyloma
You may want to add a Padding="6,3,0,0" to the TextBlock.Terrarium
Very nice, but it does not work on Windows Phone Silverlight :-(Equiponderate
After all the complicated solutions I've seen this one is just wonderful to see. Works perfectly for my purposes!Murmurous
You can avoid the use of a grid by setting a negative top margin (e.g. Margin="5,-22,0,0") on the TextBlock.Perennial
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.Geochronology
How would one make this a reusable Control Template ?Mickel
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.Censure
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.Geochronology
perfect on textblox but how to adapt it on passwordbox ? this do not work : Binding="{Binding Password, ElementName=txPassword}"Marola
@Marola This is because the Password property on a PasswordBox is not bindable for security reasons. You could make it bindable using this example here: 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.Geochronology
@Mickel in your ControlTemplate, name your watermark text block then do this: <ControlTemplate.Triggers> <Trigger Property="SearchTermTextBox.Text" Value=""> <Setter TargetName="WatermarkTextBlock" Property="Visibility" Value="{x:Static Visibility.Visible}" /> </Trigger> </ControlTemplate.Triggers>Timothy
Awesome answer! Especially as I only needed it for one textbox. Thanks!Infestation
I prefer to hide the watermark when the textbox has focus... so I adapted your solution to use a MultiDataTriggerDisassemble
I maybe bit late, but just rewrote your text block, notable difference is to use binding for visibility instead of style/triggers. <TextBlock IsHitTestVisible="False" Text="Enter Search Term Here" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="10,0,0,0" Foreground="DarkGray" Visibility="{Binding Text.IsEmpty,ElementName=SearchTermTextBox, Converter={StaticResource booleanToVisibilityConverter}}"/>Transsonic
Love this example, so elegant and gave me some inspiration to roll my own password box on top of the same idea. Just added a new DP for IsPassword then overlay another textblock on top with the mask. Excellent example. Good WorkWeller
B
60

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.

Bazil answered 5/12, 2011 at 16:43 Comment(5)
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.Cataplasm
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.Basipetal
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.Illuminant
The Extended WPF Toolkit is also not free of charge for commercial use.Jariah
@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.Bazil
M
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.

Manslayer answered 7/5, 2009 at 10:46 Comment(5)
How can I apply it in a passwordbox?Candiot
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...Poodle
Consider creating a UserControl.Manslayer
While I really appreciate your effort to help the community, I really need to say this is far from being even a decent approach.Epirus
This code was made by Andy L. You can find it on codeproject.Terrarium
A
41

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.

Amabelle answered 21/5, 2013 at 9:47 Comment(4)
The TextBox should have IsHitTestVisible="False". Also, it should come after the TextBox, otherwise it might not be visible if the TextBox has background.Cata
That article in CodeProject is just bad.Suzette
How the Text.IsEmpty works: IsEmpty is being resolved from CollectionView.IsEmptyProminence
I am pretty sure there is a typo in @Cata comment: the first "TextBox" should be "TextBlock". I otherwise agree with that comment.Butterfly
C
29

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

Churning answered 5/11, 2015 at 8:31 Comment(1)
This is my fav solutionEquinox
D
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.

Doormat answered 2/5, 2012 at 10:35 Comment(1)
That is in fact an improved version of the beautiful piece @john-myczek code and it worked fined for combo boxes. Thank you both!Wayland
T
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"/>
Thomsen answered 2/7, 2015 at 18:5 Comment(1)
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 +1Wadding
B
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" />
Benzoic answered 1/2, 2011 at 9:37 Comment(8)
Great solution. Why shadow the Foreground property? SetBinding(TextProperty, new Binding()) throws InvalidOperationException: Two-way binding requires Path or XPath?Waterside
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().Benzoic
To use this code in WPF, use BindingOperations.ClearBinding(this, TextProperty) instead of SetBinding(TextProperty, new Binding()) in both places.Averill
This actually changes Text to the watermark. Wouldn't work for me.Royston
probably useful to add namespace lines to this, or fully qualify some of this stuff.Orientate
Also, when I use it, I get an error "Two-Way binding requires Path or XPath"Orientate
@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?Benzoic
I added what I think are the correct namespaces. (some objects have multiple possibilities. Never did figure out the two way binding error.Orientate
A
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);
    }
}
Appraise answered 23/6, 2011 at 13:3 Comment(1)
Wish I had noticed this answer before I did it myself.Royston
R
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"
                >
Rossuck answered 28/5, 2019 at 6:0 Comment(0)
M
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);
    }
  }

}
Manducate answered 25/3, 2011 at 14:37 Comment(0)
S
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";
        }
    }
Signory answered 24/12, 2013 at 6:56 Comment(0)
M
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.

Masuria answered 7/5, 2009 at 10:48 Comment(0)
G
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>
Geanticlinal answered 15/1, 2016 at 13:43 Comment(1)
In newer version of MahApps.Metro, it would be: <AdornerDecorator> <TextBox> <mah:TextBoxHelper.Watermark>Enter text here...</mah:TextBoxHelper.Watermark> </TextBox> </AdornerDecorator> where namespace is xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"Winterize
M
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;
}
Mitchum answered 21/1, 2017 at 20:25 Comment(0)
B
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();
        }

    }
}
Bower answered 12/10, 2009 at 21:32 Comment(0)
S
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.

Scopp answered 14/9, 2011 at 8:4 Comment(1)
This is a perfect example that describes how not to do it, especially with WPF.Pinon
G
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>
Guardianship answered 16/4, 2015 at 19:13 Comment(0)
N
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>
Nunez answered 20/7, 2015 at 7:20 Comment(1)
Please add some explanationMetaprotein
R
1
<TextBox Controls:TextBoxHelper.Watermark="Watermark"/>

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

Rhetorical answered 11/1, 2017 at 21:43 Comment(1)
I wonder why nobody upvoted this answer. It works perfectly when Wpf app is created with Template Studio in Visual Studio 2022Vulpine
J
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
Justly answered 10/4, 2017 at 18:38 Comment(1)
The first if (DosyaİhtivaEdenDizin.Text.Length == 0) should be if (DosyaİhtivaEdenDizin.Text equals "Hedef Dizin Belirtin")Keilakeily
P
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.

Poliard answered 29/10, 2017 at 15:17 Comment(0)
E
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.

Elissa answered 10/1, 2018 at 8:22 Comment(0)
K
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>
Kopeisk answered 12/2, 2021 at 10:7 Comment(0)
C
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

Cassidy answered 16/7, 2010 at 13:30 Comment(0)
P
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;
    }
}
Peptidase answered 10/6, 2014 at 18:57 Comment(0)
A
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>
Antonomasia answered 27/10, 2014 at 14:32 Comment(0)
R
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,
        ...
    },
    ...
}
Royston answered 16/1, 2015 at 20:38 Comment(0)
D
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.

Duiker answered 7/10, 2015 at 15:17 Comment(0)
G
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/

Groundage answered 14/11, 2015 at 23:47 Comment(0)
S
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");
                }
            }
        }
}
Scammony answered 27/10, 2016 at 16:51 Comment(0)
E
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.

Equidistant answered 28/11, 2016 at 18:16 Comment(1)
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, 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!Zins
B
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" />
Buddha answered 9/5, 2018 at 4:9 Comment(0)
W
0

I went with creating a re-useable usercontrol and added a few extras to support binding to text on either a textbox or password box.

This is the Usercontrol XAML:

<UserControl x:Class="Controls.Textbox"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:XXXXXXXX"
         xmlns:controls="clr-namespace:XXXXXXXX.Controls"
         mc:Ignorable="d" 
         d:DesignHeight="450" d:DesignWidth="800" 
         DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}">
<Grid>
    <Grid.Resources>
        <controls:PasswordConverter x:Key="PasswordConverter"/>
    </Grid.Resources>
    <TextBox x:Name="tb_Text" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
             SelectionChanged="tb_Text_SelectionChanged">
        <TextBox.Style>
            <Style TargetType="TextBox">
                <Setter Property="Foreground" Value="{Binding Foreground}"/>
                <Setter Property="CaretBrush" Value="{Binding Foreground}"/>
                <Setter Property="FontSize" Value="{Binding FontSize}"/>
                <Setter Property="Padding" Value="6,3"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding IsPassword}" Value="True">
                        <Setter Property="Foreground" Value="Transparent"/>
                        <Setter Property="CaretBrush" Value="Transparent"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </TextBox.Style>
    </TextBox>
    <TextBlock x:Name="tb_PlaceHolder">
        <TextBlock.Style>
            <Style TargetType="TextBlock">
                <Setter Property="IsHitTestVisible" Value="False"/>
                <Setter Property="Text" Value="{Binding PlaceHolder}"/>
                <Setter Property="VerticalAlignment" Value="Center"/>
                <Setter Property="Visibility" Value="Collapsed"/>
                <Setter Property="Margin" Value="10,0"/>
                <Setter Property="Opacity" Value=".5"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding ElementName=tb_Text, Path=Text}" Value="">
                        <Setter Property="Visibility" Value="Visible"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </TextBlock.Style>
    </TextBlock>
    <TextBlock x:Name="tb_PasswordMask">
        <TextBlock.Style>
            <Style TargetType="TextBlock">
                <Setter Property="IsHitTestVisible" Value="False"/>
                <Setter Property="Foreground" Value="{Binding Foreground}"/>
                <Setter Property="Background" Value="{Binding Background}"/>
                <Setter Property="FontSize" Value="{Binding FontSize}"/>
                <Setter Property="Text" Value="{Binding Text, 
                    RelativeSource={RelativeSource Mode=FindAncestor, 
                    AncestorType=UserControl}, 
                    UpdateSourceTrigger=PropertyChanged, 
                    Converter={StaticResource PasswordConverter},
                    ConverterParameter=°}"/>
                <Setter Property="VerticalAlignment" Value="Center"/>
                <Setter Property="Visibility" Value="Collapsed"/>
                <Setter Property="Margin" Value="10,0"/>
                <Setter Property="Opacity" Value=".7"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding IsPassword}" Value="True">
                        <Setter Property="Visibility" Value="Visible"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </TextBlock.Style>
    </TextBlock>
</Grid>

And the XAML.VB

Namespace Controls
Public Class Textbox

    Public Shared ReadOnly PlaceHolderProperty As DependencyProperty =
        DependencyProperty.RegisterAttached("PlaceHolder",
                                            GetType(String),
                                            GetType(Textbox),
                                            New FrameworkPropertyMetadata(
                                                Nothing,
                                                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault))
    Public Property PlaceHolder As String
        Get
            Return GetValue(PlaceHolderProperty)
        End Get
        Set(value As String)
            SetValue(PlaceHolderProperty, value)
        End Set
    End Property
    Public Shared ReadOnly TextProperty As DependencyProperty =
        DependencyProperty.RegisterAttached("Text",
                                            GetType(String),
                                            GetType(Textbox),
                                            New FrameworkPropertyMetadata(
                                                Nothing,
                                                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault))
    Public Property Text As String
        Get
            Return GetValue(TextProperty)
        End Get
        Set(value As String)
            SetValue(TextProperty, value)
        End Set
    End Property

    Public Shared ReadOnly IsPasswordProperty As DependencyProperty = DependencyProperty.Register("IsPassword", GetType(Boolean), GetType(Textbox))
    Public Property IsPassword As Boolean
        Get
            Return GetValue(IsPasswordProperty)
        End Get
        Set(value As Boolean)
            SetValue(IsPasswordProperty, value)
        End Set
    End Property

    Private Sub tb_Text_SelectionChanged(sender As Object, e As RoutedEventArgs)
        Dim tb As Windows.Controls.TextBox = sender
        If tb.SelectedText = "" Then Exit Sub
        If IsPassword Then
            e.Handled = True
            tb.SelectedText = ""
        End If
    End Sub
End Class

Public Class PasswordConverter
    Implements IValueConverter

    Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
        Dim s As String = value
        If IsNothing(parameter) Then parameter = "*"
        If IsNothing(s) Then Return ""
        Return StrDup(s.Length, parameter) & "|"
    End Function

    Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
        Throw New NotImplementedException()
    End Function
End Class

End Namespace

Then it can be used anywhere:

<controls:Textbox PlaceHolder="Username" 
                      Width="200"
                      FontSize="20"
                      Text="{Binding Username, UpdateSourceTrigger=PropertyChanged}"/>
    <controls:Textbox PlaceHolder="Password" 
                      IsPassword="True"
                      HorizontalAlignment="Center" 
                      VerticalAlignment="Center" Width="200"
                      FontSize="20"
                      Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}"/>
Weller answered 24/1 at 23:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.