How can I put some text into a TextBox which is removed automatically when user types something in it?
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 though, is that it uses a non-standard extension method on the Text property, called "IsEmpty". You need to implement this yourself, however the article doesn't seem to mention that.
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();
}
}
}
@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);
}
}
}
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.
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"/>
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.