297 votes

Filigrane / texte d'indication / placeholder TextBox dans WPF

Comment puis-je mettre du texte dans une zone de texte qui s'efface automatiquement lorsque l'utilisateur tape quelque chose dedans ? (En WPF)

54 votes

C'est ce qu'on appelle un "placeholder" en HTML. Je le mentionne pour aider les gens à trouver cette page sur Google.

4 votes

Si vous écrivez des applications UWP sur Windows 10, c'est beaucoup plus facile. <TextBox PlaceholderText="Search"/> Plus d'infos : msdn.microsoft.com/fr/us/library/Windows/apps/

2 votes

J'aime les capacités de WPF, mais ceci est un exemple parfait de la raison pour laquelle il est difficile de travailler avec lui. Ce genre de choses devrait déjà être intégré comme c'est le cas en HTML.

460voto

John Myczek Points 6205

Vous pouvez créer un filigrane qui peut être ajouté à n'importe quel TextBox avec une propriété attachée. Voici la source de la propriété attachée :

   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 || d is TextBox)
         {
            control.GotKeyboardFocus += Control_GotKeyboardFocus;
            control.LostKeyboardFocus += Control_Loaded;
         }

         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))
         {
            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 propriété attachée utilise une classe appelée WatermarkAdorner, voici la source :

   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
   }

Maintenant vous pouvez mettre un filigrane sur n'importe quel TextBox comme ceci :

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

Le filigrane peut être tout ce que vous voulez (texte, images ...). En plus de fonctionner pour les TextBox, ce filigrane fonctionne également pour les ComboBox et les ItemControls.

Ce code a été adapté de cet article de blog .

2 votes

Hey John, j'ai essayé d'implémenter ceci. J'obtiens une erreur disant : controls is an undeclared prefix", avez-vous une idée ?

2 votes

Hé, j'ai posté une question à ce sujet : enlace

0 votes

On dirait que vous avez votre réponse. J'ai juste besoin de la définition de l'espace de nom.

359voto

apc Points 91

Il suffit d'utiliser XAML, sans extensions, sans convertisseurs :

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

6 votes

Extrêmement simple, le meilleur imo aussi. Je ne sais pas pourquoi vous utilisez tous ces autres alors que vous pourriez avoir ces 10 lignes de xaml script et c'est tout. Merci.

4 votes

Vous pouvez ajouter un Padding="6,3,0,0" à la TextBlock .

1 votes

Très joli, mais il ne fonctionne pas sur Windows Phone Silverlight :-(

59voto

dain Points 4393

Je ne peux pas croire que personne n'a posté l'évident Extended WPF Toolkit - WatermarkTextBox de CodePlex. Il fonctionne assez bien et est open source au cas où vous voudriez le personnaliser.

6 votes

Sur ma machine win8, tous les contrôles WPF Toolkit ont des styles Windows 7 (coins arrondis, etc.). Et tout contrôle WPF Toolkit semble complètement déplacé lorsqu'il est mélangé à des contrôles standard.

1 votes

L'approche "Attached Property" de John Myczek présente un problème : si la zone de texte est recouverte par un autre élément, le filigrane transparaît et reste visible. Cette solution ne présente pas ce problème. (J'aurais aimé le remarquer plus tôt puisque j'utilise déjà la boîte à outils de toute façon). Mérite plus de votes positifs.

0 votes

La solution de John Myczek a également une fuite de mémoire apparente, où le WatermarkService gardera une référence dans un dictionnaire statique à tout ItemsControl auquel un filigrane est attaché. Cela pourrait certainement être corrigé, mais je vais essayer la version Extended WPF Toolkit.

56voto

CSharper Points 3177

Voici un exemple qui montre comment créer une boîte de texte en filigrane dans WPF :

(Ce n'est pas mon code, je l'ai trouvé sur le net, mais je pense que c'est la meilleure approche).

<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 est défini comme :

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

5 votes

Comment puis-je l'appliquer dans une boîte à mots ?

104 votes

La meilleure approche ? Certainement pas ! Voulez-vous vraiment taper autant de lignes de code chaque fois que vous avez besoin d'un filigrane ? La solution avec une propriété attachée est beaucoup plus facile à réutiliser...

5 votes

Pensez à créer un UserControl.

36voto

René Points 4134

Il existe un article sur CodeProject sur comment le faire en "3 lignes de 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, bien, ce n'est peut-être pas 3 lignes de XAML formatées, mais c'est est assez simple.

Il faut cependant noter qu'il utilise une méthode d'extension non standard sur la propriété Text, appelée "IsEmpty". Vous devez l'implémenter vous-même, mais l'article ne semble pas le mentionner.

10 votes

La TextBox doit avoir IsHitTestVisible="False" . De plus, il doit venir après la TextBox, sinon il pourrait ne pas être visible si la TextBox a un arrière-plan.

0 votes

Cet article de CodeProject est tout simplement mauvais.

3 votes

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X