Filigrana / suggerimento testo / segnaposto TextBox


264

Come posso inserire del testo in una TextBox che viene rimossa automaticamente quando l'utente digita qualcosa al suo interno?


48
Questo è chiamato un 'segnaposto' in HTML. Ne parlo per aiutare le persone a google su questa pagina.
Scott Stafford,

3
Se stai scrivendo app UWP su Windows 10 questo è molto più semplice. <TextBox PlaceholderText = "Cerca" /> Ulteriori informazioni: msdn.microsoft.com/en-us/library/windows/apps/…
Bleak Morn

Risposte:


57

Questo è un esempio che dimostra come creare una casella di testo filigrana in WPF:

<Window x:Class="WaterMarkTextBoxDemo.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WaterMarkTextBoxDemo"
    Height="200" Width="400">

    <Window.Resources>

        <SolidColorBrush x:Key="brushWatermarkBackground" Color="White" />
        <SolidColorBrush x:Key="brushWatermarkForeground" Color="LightSteelBlue" />
        <SolidColorBrush x:Key="brushWatermarkBorder" Color="Indigo" />

        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
        <local:TextInputToVisibilityConverter x:Key="TextInputToVisibilityConverter" />

        <Style x:Key="EntryFieldStyle" TargetType="Grid" >
            <Setter Property="HorizontalAlignment" Value="Stretch" />
            <Setter Property="VerticalAlignment" Value="Center" />
            <Setter Property="Margin" Value="20,0" />
        </Style>

    </Window.Resources>


    <Grid Background="LightBlue">

        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>

        <Grid Grid.Row="0" Background="{StaticResource brushWatermarkBackground}" Style="{StaticResource EntryFieldStyle}" >
            <TextBlock Margin="5,2" Text="This prompt dissappears as you type..." Foreground="{StaticResource brushWatermarkForeground}"
                       Visibility="{Binding ElementName=txtUserEntry, Path=Text.IsEmpty, Converter={StaticResource BooleanToVisibilityConverter}}" />
            <TextBox Name="txtUserEntry" Background="Transparent" BorderBrush="{StaticResource brushWatermarkBorder}" />
        </Grid>

        <Grid Grid.Row="1" Background="{StaticResource brushWatermarkBackground}" Style="{StaticResource EntryFieldStyle}" >
            <TextBlock Margin="5,2" Text="This dissappears as the control gets focus..." Foreground="{StaticResource brushWatermarkForeground}" >
                <TextBlock.Visibility>
                    <MultiBinding Converter="{StaticResource TextInputToVisibilityConverter}">
                        <Binding ElementName="txtUserEntry2" Path="Text.IsEmpty" />
                        <Binding ElementName="txtUserEntry2" Path="IsFocused" />
                    </MultiBinding>
                </TextBlock.Visibility>
            </TextBlock>
            <TextBox Name="txtUserEntry2" Background="Transparent" BorderBrush="{StaticResource brushWatermarkBorder}" />
        </Grid>

    </Grid>

</Window>

TextInputToVisibilityConverter è definito come:

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

Nota: questo non è il mio codice. L'ho trovato qui , ma penso che questo sia l'approccio migliore.


5
Come posso applicarlo in una passwordbox?
Sauron,

91
L'approccio migliore? certamente no! Vuoi davvero digitare così tante righe di codice ogni volta che hai bisogno di una filigrana? La soluzione con una proprietà collegata è molto più facile da riutilizzare ...
Thomas Levesque

5
Prendi in considerazione la creazione di un UserControl.
CSharper

6
Anche se apprezzo molto i tuoi sforzi per aiutare la comunità, devo davvero dire che questo è ben lungi dall'essere un approccio decente.
r41n

2
Questo codice è stato creato da Andy L. Lo puoi trovare su codeproject .
aloisdg si trasferisce su codidact.com il

440

È possibile creare una filigrana che può essere aggiunta a qualsiasi TextBoxcon una proprietà collegata. Ecco la fonte per la proprietà allegata:

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
}

La proprietà associata utilizza una classe chiamata WatermarkAdorner, ecco quella fonte:

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
}

Ora puoi mettere una filigrana su qualsiasi TextBox in questo modo:

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

La filigrana può essere tutto quello che vuoi (testo, immagini ...). Oltre a lavorare per TextBox, questa filigrana funziona anche con ComboBox e ItemControls.

Questo codice è stato adattato da questo post sul blog .


11
L'ho risolto modificando l'assegnazione del margine del costruttore WatermarkAdorner come: Margine = nuovo spessore (Control.Padding.Left, Control.Padding.Top + 1, Control.Padding.Right, Control.Padding.Bottom)
JoanComasFdz

3
@JohnMyczek Per localizzare la filigrana: come posso associare TextBox.Text nella dichiarazione xaml Watermark a una proprietà di ViewModel?
JoanComasFdz,

7
@Matze @JoanComasFdz Ecco come posso associare la TextBlock.Textproprietà al mio modello di vista (metterlo nel WatermarkAdornercostruttore): FrameworkElement feWatermark = watermark as FrameworkElement; if(feWatermark != null && feWatermark.DataContext == null) { feWatermark.DataContext = this.Control.DataContext; }
Sean Hall

9
Possibile collegamento di memoria qui. Stai aggiungendo controlli filigranati al dizionario statico interno ma non li rimuovi mai. Ciò probabilmente impedirà che le tue visualizzazioni vengano raccolte in modo inutile una volta che hai finito con esse. Considererei l'utilizzo di un riferimento debole qui.
Jared G

3
Oltre al dizionario statico di itemcontrols, anche il codice PropertyDescriptor perde memoria. Devi chiamare RemoveValueChanged (). Quindi fai attenzione quando usi questo codice.
muku,

284

Basta usare XAML, nessuna estensione, nessun convertitore:

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

3
Estremamente semplice, anche il migliore. Non so perché dovresti usare tutte quelle altre quando potresti avere queste 10 righe di script xaml e basta. Grazie.
dj.lnxss,

4
Potresti voler aggiungere un Padding="6,3,0,0"a TextBlock.
aloisdg si trasferisce su codidact.com il

1
Molto bello, ma non funziona su Windows Phone Silverlight :-(
Andrea Antonangeli,

14
Come si potrebbe rendere questo un modello di controllo riutilizzabile?
Richard,

2
@cyrianox Questo perché la proprietà Password su PasswordBox non è associabile per motivi di sicurezza. Potresti renderlo associabile usando questo esempio qui: wpftutorial.net/PasswordBox.html tuttavia è probabilmente più veloce e più semplice usare l'evento PasswordChanged e il codice dietro per impostare la visibilità in questo caso.
apc

54

Non riesco a credere che nessuno abbia pubblicato l'ovvio Extended WPF Toolkit - WatermarkTextBox di Xceed. Funziona abbastanza bene ed è open source nel caso si desideri personalizzare.


5
Sulla mia macchina win8 tutti i controlli WPF Toolkit hanno stili di Windows 7 (angoli arrotondati, ecc.). E qualsiasi controllo del toolkit WPF sembra completamente fuori posto se miscelato con controlli standard.
Gman,

1
L'approccio "Proprietà allegata" di John Myczek presenta un bug per cui se la casella di testo fosse coperta da un altro elemento, la filigrana si diffonderebbe e sarebbe ancora visibile. Questa soluzione non ha questo problema. (Vorrei averlo notato prima poiché sto già utilizzando il toolkit comunque). Merita più voti.
Dax Fohl,

La soluzione di John Myczek ha anche un'apparente perdita di memoria, in cui WatermarkService manterrà un riferimento in un dizionario statico a qualsiasi ItemControl a cui è collegata una filigrana. Potrebbe sicuramente essere risolto, ma proverò la versione estesa di WPF Toolkit.
mbargiel,

34

C'è un articolo su CodeProject su come farlo in "3 righe di 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, beh, potrebbe non essere formato da 3 righe di XAML, ma lo è piuttosto semplice.

Una cosa da notare, tuttavia, è che utilizza un metodo di estensione non standard sulla proprietà Text, chiamato "IsEmpty". È necessario implementarlo da soli, tuttavia l'articolo non sembra menzionarlo.


9
TextBox dovrebbe avere IsHitTestVisible="False". Inoltre, dovrebbe venire dopo TextBox, altrimenti potrebbe non essere visibile se TextBox ha uno sfondo.
ANeves

L'articolo in CodeProject è semplicemente negativo.
X


19

Ho visto la soluzione di John Myczek e i suoi commenti sulla compatibilità ComboBoxe PasswordBox, quindi ho migliorato la soluzione di John Myczek, ed eccola qui:

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
}

Ora, a ComboBoxpuò essere anche EditableePasswordBox può anche aggiungere una filigrana. Non dimenticare di utilizzare il commento di JoanComasFdz sopra per risolvere il problema del margine.

E, naturalmente, tutto il merito va a John Myczek.


4
Questa è in effetti una versione migliorata del bellissimo codice piece @ john-myczek e ha funzionato multato per le caselle combinate. Grazie ad entrambi!
saamorim,

12

Soluzione semplice usando lo stile:

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

Ottima soluzione:

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


1
Questa è la mia soluzione preferita
HoKy22,

10

Questa libreria ha una filigrana.

Pacchetto Nuget

Esempio di utilizzo:

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

9

Ho creato un'implementazione di solo codice siple che funziona bene anche per WPF e 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
}

Uso:

<TextBoxWatermarked Watermark="Some text" />

Ottima soluzione Perché ombreggiare la proprietà Foreground? SetBinding (TextProperty, new Binding ()) genera InvalidOperationException: l'associazione bidirezionale richiede Path o XPath?
Tim Murphy,

Nascondo la proprietà Foreground perché TextBoxWatermarked lo utilizza per i propri scopi. Non so perché viene lanciato InvalidOperationException, forse se usi WPF (l'ho usato con Silverlight) devi passare null invece di new Binding ().
Vitaliy Ulantikov,

2
Per utilizzare questo codice in WPF, utilizzare BindingOperations.ClearBinding(this, TextProperty)invece che SetBinding(TextProperty, new Binding())in entrambe le posizioni.
Sebastian Krysmanski,

1
Questo in realtà cambia Textalla filigrana. Non funzionerebbe per me.
lobsterism,

probabilmente utile per aggiungere righe di spazio dei nomi a questo, o qualificare completamente alcune di queste cose.
Richard,

6

Ho riscontrato un po 'di difficoltà durante l'utilizzo del codice di @ john-myczek con un TextBox associato. Poiché TextBox non genera un evento attivo quando viene aggiornato, la filigrana rimarrà visibile sotto il nuovo testo. Per risolvere questo problema, ho semplicemente aggiunto un altro gestore di eventi:

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

1
Vorrei aver notato questa risposta prima di farlo da solo.
lobsterism,

5

@Veton - Mi piace molto la semplicità della tua soluzione, ma la mia reputazione non è ancora abbastanza elevata da ostacolarti.

@Tim Murphy - L'errore "Il binding bidirezionale richiede Path o XPath" è stato una soluzione semplice ... codice aggiornato che include alcune altre piccole modifiche (testato solo da WPF):

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

}

3

puoi usare GetFocus()eLostFocus() eventi per farlo

ecco l'esempio:

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

2

modo più semplice per WaterMark Of TextBox

 <Window.Resources>
    <Style x:Key="MyWaterMarkStyle" TargetType="{x:Type TextBox}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TextBox}">
                    <Grid>
                        <Border Background="White" BorderBrush="#FF7D8683" BorderThickness="1"/>
                        <ScrollViewer x:Name="PART_ContentHost" Margin="5,0,0,0" VerticalAlignment="Center" />
                        <Label Margin="5,0,0,0" x:Name="WaterMarkLabel" Content="{TemplateBinding Tag}" VerticalAlignment="Center"
                           Visibility="Collapsed" Foreground="Gray" FontFamily="Arial"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="Text" Value=""/>
                            </MultiTrigger.Conditions>
                            <Setter Property="Visibility" TargetName="WaterMarkLabel" Value="Visible"/>
                        </MultiTrigger>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter Property="Foreground" Value="DimGray"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

e aggiungi la casella di testo StaticResource style

  <TextBox
                Style="{StaticResource MyWaterMarkStyle}"
                Tag="Search Category"
                Grid.Row="0"
                Text="{Binding CategorySearch,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                TextSearch.Text="Search Category"
                >

1
<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" />

Questo può aiutarti a controllarlo con il tuo codice. Quando applicato alla casella della password, mostrerà la password, che scomparirà quando i tipi di utente.


1

Bene, ecco il mio: non necessariamente il migliore, ma poiché è semplice è facile modificarlo secondo i tuoi gusti.

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

Il convertitore, come è scritto ora, non è necessario che sia un MultiConverter, ma in questo modo può essere esteso facilmente

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

e infine il codice dietro:

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

    }
}

1
<TextBox x:Name="OrderTxt" HorizontalAlignment="Left" VerticalAlignment="Top" VerticalContentAlignment="Center" Margin="10,10,0,0" Width="188" Height="32"/>

<Label IsHitTestVisible="False" Content="Order number" DataContext="{Binding ElementName=OrderTxt}" Foreground="DarkGray">
    <Label.Style>
        <Style TargetType="{x:Type Label}">
            <Setter Property="Visibility" Value="Collapsed"/>
            <Setter Property="Width" Value="{Binding Width}"/>
            <Setter Property="Height" Value="{Binding Height}"/>
            <Setter Property="Margin" Value="{Binding Margin}"/>
            <Setter Property="VerticalAlignment" Value="{Binding VerticalAlignment}"/>
            <Setter Property="HorizontalAlignment" Value="{Binding HorizontalAlignment}"/>
            <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment}"/>
            <Style.Triggers>
                <DataTrigger Binding="{Binding Text}" Value="">
                    <Setter Property="Visibility" Value="Visible"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Label.Style>
</Label>

Per favore, aggiungi qualche spiegazione
Mohit Jain,

1

MahApps.Metro per WPF ha un controllo filigrana integrato, se preferisci non creare il tuo. È abbastanza semplice da usare.

 <AdornerDecorator>
            <TextBox Name="txtSomeText"
                     Width="200"
                     HorizontalAlignment="Right">
                <Controls:TextBoxHelper.Watermark>I'm a watermark!</Controls:TextBoxHelper.Watermark>
            </TextBox>
        </AdornerDecorator>

1

Imposta la casella di testo con il testo segnaposto in un colore morbido ...

public MainWindow ( )
{
    InitializeComponent ( );
    txtInput.Text = "Type something here...";
    txtInput.Foreground = Brushes.DimGray;
}

Quando la casella di testo viene messa a fuoco, deselezionala e cambia il colore del testo

private void txtInput_GotFocus ( object sender, EventArgs e )
{
    MessageBox.Show ( "got focus" );
    txtInput.Text = "";
    txtInput.Foreground = Brushes.Red;
}

1

Ecco la soluzione più semplice:

            <Grid>
                <Label Content="Placeholder text" VerticalAlignment="Center" Margin="10">
                    <Label.Style>
                        <Style TargetType="Label">
                            <Setter Property="Foreground" Value="Transparent"/>
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding Expression}" Value="">
                                    <Setter Property="Foreground" Value="Gray"/>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </Label.Style>
                </Label>
                <TextBox HorizontalAlignment="Stretch" Margin="5" Background="Transparent"
                 Text="{Binding Expression, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" Padding="5">
                </TextBox>
        </Grid>

Questa è una casella di testo con backgound trasparente sovrapposto a un'etichetta. Il testo grigio dell'etichetta viene reso trasparente da un trigger di dati che viene attivato ogni volta che il testo associato è diverso da una stringa vuota.


1

Vedi anche questa risposta . Puoi farlo molto più facilmente con un VisualBrush e alcuni trigger in uno stile:

 <TextBox>
    <TextBox.Style>
        <Style TargetType="TextBox" xmlns:sys="clr-namespace:System;assembly=mscorlib">
            <Style.Resources>
                <VisualBrush x:Key="CueBannerBrush" AlignmentX="Left" AlignmentY="Center" Stretch="None">
                    <VisualBrush.Visual>
                        <Label Content="Search" Foreground="LightGray" />
                    </VisualBrush.Visual>
                </VisualBrush>
            </Style.Resources>
            <Style.Triggers>
                <Trigger Property="Text" Value="{x:Static sys:String.Empty}">
                    <Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
                </Trigger>
                <Trigger Property="Text" Value="{x:Null}">
                    <Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
                </Trigger>
                <Trigger Property="IsKeyboardFocused" Value="True">
                    <Setter Property="Background" Value="White" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </TextBox.Style>
</TextBox>

Per aumentare la riutilizzabilità di questo stile, puoi anche creare una serie di proprietà associate per controllare il testo, il colore, l'orientamento ecc.


0

ciao ho messo questo compito in un comportamento. quindi devi solo aggiungere qualcosa di simile alla tua casella di testo

<i:Interaction.Behaviors>
         <Behaviors:TextBoxWatermarkBehavior Label="Test Watermark" LabelStyle="{StaticResource StyleWatermarkLabel}"/>
</i:Interaction.Behaviors>

puoi trovare il mio post sul blog qui


0

La mia soluzione è abbastanza semplice.

Nella mia finestra di accesso. lo xaml è così.

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

il codice è così.

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

Basta decidere di nascondere o mostrare la casella di testo della filigrana è sufficiente. Anche se non bello, ma funziona bene.


Questo è un esempio perfetto che descrive come non farlo, specialmente con WPF.
Alexandru Dicu,

0

Questa tecnica utilizza la proprietà Background per mostrare / nascondere la casella di testo segnaposto.
Il segnaposto viene mostrato evento quando Textbox ha lo stato attivo

Come funziona:

  • Se vuoto, lo sfondo di TextBox è impostato su Trasparente per mostrare il testo di PlaceHolder.
  • Quando non è vuoto, impostare su Bianco per coprire il testo di PlaceHolder.

Ecco un esempio di base. Per i miei scopi l'ho trasformato in un 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>

Ecco ValueConverter per rilevare stringhe non vuote in 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;
    }
}

0

È possibile mantenere un valore separato per il testo inserito e impostarlo insieme al campo "Testo" della casella di testo negli eventi "GotFocus" e "LostFocus". Quando ottieni lo stato attivo, ti consigliamo di deselezionare la casella di testo se non c'è alcun valore. E quando perdi il focus, ti consigliamo di impostare il valore "Testo" dalla casella di testo e quindi reimpostare il valore "Testo" della casella di testo sul segnaposto se è vuoto.

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

Quindi devi solo assicurarti che il valore "Testo" della casella di testo sia inizializzato nel testo del segnaposto.

<TextBox x:Name="usernameTextBox" Text="Username" GotFocus="usernameTextBox_GotFocus" LostFocus="usernameTextBox_LostFocus" />

È possibile estrarlo ulteriormente in una classe che estende la classe "TextBox" e quindi riutilizzarla nel progetto.

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

E quindi questo può essere aggiunto direttamente in 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>

0

Se invece di avere la visibilità della filigrana dipende dallo stato attivo del controllo, si desidera che dipenda dal fatto che l'utente abbia inserito un testo, è possibile aggiornare la risposta di John Myczek (dal OnWatermarkChangedbasso) a

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

Ciò ha più senso se la casella di testo viene messa a fuoco automaticamente durante la visualizzazione del modulo o quando si esegue il databinding alla proprietà Text.

Inoltre, se la filigrana è sempre solo una stringa e hai bisogno che lo stile della filigrana corrisponda allo stile della casella di testo, quindi in Adorner:

contentPresenter = new ContentPresenter {
    Content = new TextBlock {
        Text = (string)watermark,
        Foreground = Control.Foreground,
        Background = Control.Background,
        FontFamily = Control.FontFamily,
        FontSize = Control.FontSize,
        ...
    },
    ...
}

0

Ecco il mio approccio è ottimo per MVVM dove controllo anche se la casella di testo ha lo stato attivo, puoi anche usare un trigger normale solo per il valore del testo e il punto è che cambio l'immagine di sfondo quando cambia il valore:

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

0

Ho deciso di risolverlo tramite un comportamento. Usa una Hintproprietà per definire il testo da visualizzare (potrebbe anche essere un oggetto, se preferisci) e una Valueproprietà per valutare se il suggerimento dovrebbe essere visibile o meno.

Il comportamento è dichiarato come segue:

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

Avvolge il bersaglio con il suo modello, aggiungendo un'etichetta:

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

Per usarlo, basta aggiungerlo come comportamento e associare i tuoi valori (nel mio caso lo aggiungo in un ControlTemplate, quindi l'associazione):

<ContentControl>
    <i:Interaction.Behaviors>
        <behaviors:HintBehavior Value="{Binding Property, RelativeSource={RelativeSource TemplatedParent}}"
                                                        Hint="{Binding Hint, RelativeSource={RelativeSource TemplatedParent}}" />
    </i:Interaction.Behaviors>
    <TextBox ... />
</ContentControl>

Mi piacerebbe un feedback se questa è considerata una soluzione pulita. Non richiede dizionari statici e quindi non ha perdite di memoria.


0

Ho trovato questo modo di farlo in un modo molto semplice e veloce

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

Forse può aiutare chiunque cerchi di farlo

Fonte: http://www.admindiaries.com/displaying-a-please-select-watermark-type-text-in-a-wpf-combobox/


0
namespace PlaceholderForRichTexxBoxInWPF
{
public MainWindow()
        {
            InitializeComponent();
            Application.Current.MainWindow.WindowState = WindowState.Maximized;// maximize window on load

            richTextBox1.GotKeyboardFocus += new KeyboardFocusChangedEventHandler(rtb_GotKeyboardFocus);
            richTextBox1.LostKeyboardFocus += new KeyboardFocusChangedEventHandler(rtb_LostKeyboardFocus);
            richTextBox1.AppendText("Place Holder");
            richTextBox1.Foreground = Brushes.Gray;
        }
 private void rtb_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
        {
            if (sender is RichTextBox)
            {
                TextRange textRange = new TextRange(richTextBox1.Document.ContentStart, richTextBox1.Document.ContentEnd); 

                if (textRange.Text.Trim().Equals("Place Holder"))
                {
                    ((RichTextBox)sender).Foreground = Brushes.Black;
                    richTextBox1.Document.Blocks.Clear();
                }
            }
        }


        private void rtb_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
        {
            //Make sure sender is the correct Control.
            if (sender is RichTextBox)
            {
                //If nothing was entered, reset default text.
                TextRange textRange = new TextRange(richTextBox1.Document.ContentStart, richTextBox1.Document.ContentEnd); 

                if (textRange.Text.Trim().Equals(""))
                {
                    ((RichTextBox)sender).Foreground = Brushes.Gray;
                    ((RichTextBox)sender).AppendText("Place Holder");
                }
            }
        }
}

0
<TextBox Controls:TextBoxHelper.Watermark="Watermark"/>

Aggiungi mahapps.metro al tuo progetto. Aggiungi casella di testo con il codice sopra alla finestra.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.