Водяной знак / текст подсказки / заполнитель TextBox - PullRequest
233 голосов
/ 07 мая 2009

Как я могу поместить некоторый текст в текстовое поле, которое удаляется автоматически, когда пользователь что-то вводит в него? (В WPF)

Ответы [ 33 ]

415 голосов
/ 07 мая 2009

Вы можете создать водяной знак, который можно добавить к любому TextBox с помощью свойства Attached. Вот источник прикрепленного свойства:

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
}

Теперь вы можете поставить водяной знак на любом текстовом поле, например так:

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

Водяной знак может быть любым, что вы хотите (текст, изображения ...). В дополнение к работе с TextBoxes, этот водяной знак также работает для ComboBoxes и ItemControls.

Этот код был адаптирован из этого сообщения в блоге .

245 голосов
/ 10 февраля 2014

Просто используя 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>
52 голосов
/ 07 мая 2009

Это пример, который демонстрирует, как создать текстовое поле водяного знака в 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 определяется как:

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

Примечание: Это не мой код. Я нашел это здесь , но я думаю, что это лучший подход.

47 голосов
/ 05 декабря 2011

Не могу поверить, что никто не опубликовал очевидный Extended WPF Toolkit - WatermarkTextBox от Xceed. Он работает довольно хорошо и с открытым исходным кодом, если вы хотите настроить.

29 голосов
/ 21 мая 2013

В 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». Вам нужно реализовать это самостоятельно, однако в статье, похоже, об этом не говорится.

17 голосов
/ 02 мая 2012

Я видел решение Джона Мычека и его комментарии о совместимости с ComboBox и PasswordBox, поэтому я улучшил решение Джона Мычека, и вот оно:

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 также может быть Editable, и PasswordBox также может добавлять водяной знак. Не забудьте использовать комментарий JoanComasFdz выше для решения проблемы маржи.

И, конечно, вся заслуга принадлежит Джону Мычеку.

11 голосов
/ 05 ноября 2015

Простое решение с использованием стиля:

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

Отличное решение:

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

9 голосов
/ 02 июля 2015

Эта библиотека имеет водяной знак.

Пакет Nuget

Пример использования:

<TextBox adorners:Watermark.Text="Write something here" 
         adorners:Watermark.TextStyle="{StaticResource AdornerTextStyle}"
         adorners:Watermark.VisibleWhen="EmptyAndNotKeyboardFocused"/>
8 голосов
/ 01 февраля 2011

Я создал реализацию только для кода siple, которая также отлично работает для 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" />
6 голосов
/ 23 июня 2011

Я столкнулся с некоторыми трудностями при использовании кода @ john-myczek со связанным TextBox. Поскольку 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);
    }
}
...