WPF中的水印/提示文本/占位符TextBox

我怎样才能把一些文本放入一个文本框中,当用户input内容时会自动删除? (在WPF中)

这是一个演示如何在WPF中创build水印文本框的示例:

<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被定义为:

 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(); } } } 

注意:这不是我的代码。 我在这里find了 ,但我认为这是最好的方法。

您可以创build一个可以添加到具有附加属性的任何文本框的水印。 这是附加属性的来源:

  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 } 

附加属性使用一个名为WatermarkAdorner的类,这里是来源:

  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 } 

现在你可以在任何一个TextBox上加水印了:

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

水印可以是任何你想要的(文字,图像…)。 除了为TextBoxes工作,这个水印也适用于ComboBoxes和ItemControls。

这个代码是从这个博客文章改编的 。

只要使用XAML,不需要扩展,不需要转换器:

 <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> 

我不能相信没有人从CodePlex发布明显的Extended WPF Toolkit – WatermarkTextBox 。 它工作得很好,是开源的,如果你想定制。

CodeProject上有一篇关于如何在“3行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> 

好吧,它可能不是3行XAML格式,但它非常简单。

有一点需要注意的是,它在Text属性上使用了一个非标准的扩展方法,叫做“IsEmpty”。 你需要自己实现,但是这篇文章似乎没有提到。

我看到了John Myczek的解决scheme ,以及对ComboBox和PasswordBox兼容性的评论,所以我改进了John Myczek的解决scheme,这里是:

  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 } 

现在,ComboBox也可以编辑,PasswordBox也可以添加水印。 不要忘了使用JoanComasFdz的上面的评论来解决保证金问题。

当然,所有的功劳都归功于约翰·米切克(John Myczek)。

我创build了仅适用于WPF和Silverlight的仅有代码的实现:

 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 } 

用法:

 <TextBoxWatermarked Watermark="Some text" /> 

使用绑定的TextBox使用@ john-myczek的代码时遇到了一些困难。 由于TextBox在更新时不会引发焦点事件,因此水印在新文本下面将保持可见。 要解决这个问题,我只需添加另一个事件处理程序

 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); } } 

简单的解决scheme

  <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> 

好的解决scheme

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

这个库有一个水印。

Nuget包

示例用法:

 <TextBox adorners:Watermark.Text="Write something here" adorners:Watermark.TextStyle="{StaticResource AdornerTextStyle}" adorners:Watermark.VisibleWhen="EmptyAndNotKeyboardFocused"/> 

@Veton – 我真的很喜欢你的解决scheme的简单性,但是我的名声还不够高,

@Tim墨菲 – “双向绑定需要path或XPath”的错误是一个简单的修复…更新的代码,包括一些其他小小的调整(只有WPFtesting):

 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); } } } 

David Owens在这里有一个更完整的“search框”的例子。

您可以使用GetFocus()LostFocus()事件来执行此操作

这里是例子:

  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"; } } 

如果您在Windows 10上编写UWP应用程序,这将更容易。

更多信息: https : //msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.controls.textbox.placeholdertext.aspx

 <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.

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(); } } } 
 <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> 

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> 

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; } 

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

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.

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

怎么运行的:

  • 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; } } 

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> 

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, ... }, ... } 

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> 

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.

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/

 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"); } } } } 
 <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(); } 
 <TextBox Controls:TextBoxHelper.Watermark="Watermark"/> 

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