Watermark / hint text / placeholder TextBox

后端 未结 30 2520
遇见更好的自我
遇见更好的自我 2020-11-22 02:20

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

相关标签:
30条回答
  • 2020-11-22 02:20

    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.

    0 讨论(0)
  • 2020-11-22 02:22

    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();
            }
    
        }
    }
    
    0 讨论(0)
  • 2020-11-22 02:24

    @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);
        }
      }
    
    }
    
    0 讨论(0)
  • 2020-11-22 02:24

    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.

    0 讨论(0)
  • 2020-11-22 02:25

    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"/>
    
    0 讨论(0)
  • 2020-11-22 02:28

    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.

    0 讨论(0)
提交回复
热议问题