Este é um exemplo que demonstra como criar uma caixa de texto de marca d'água no 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"><SetterProperty="HorizontalAlignment"Value="Stretch"/><SetterProperty="VerticalAlignment"Value="Center"/><SetterProperty="Margin"Value="20,0"/></Style></Window.Resources><GridBackground="LightBlue"><Grid.RowDefinitions><RowDefinition/><RowDefinition/><RowDefinition/></Grid.RowDefinitions><GridGrid.Row="0"Background="{StaticResource brushWatermarkBackground}"Style="{StaticResource EntryFieldStyle}"><TextBlockMargin="5,2"Text="This prompt dissappears as you type..."Foreground="{StaticResource brushWatermarkForeground}"Visibility="{Binding ElementName=txtUserEntry, Path=Text.IsEmpty, Converter={StaticResource BooleanToVisibilityConverter}}"/><TextBoxName="txtUserEntry"Background="Transparent"BorderBrush="{StaticResource brushWatermarkBorder}"/></Grid><GridGrid.Row="1"Background="{StaticResource brushWatermarkBackground}"Style="{StaticResource EntryFieldStyle}"><TextBlockMargin="5,2"Text="This dissappears as the control gets focus..."Foreground="{StaticResource brushWatermarkForeground}"><TextBlock.Visibility><MultiBindingConverter="{StaticResource TextInputToVisibilityConverter}"><BindingElementName="txtUserEntry2"Path="Text.IsEmpty"/><BindingElementName="txtUserEntry2"Path="IsFocused"/></MultiBinding></TextBlock.Visibility></TextBlock><TextBoxName="txtUserEntry2"Background="Transparent"BorderBrush="{StaticResource brushWatermarkBorder}"/></Grid></Grid></Window>
TextInputToVisibilityConverter é definido como:
using System;
using System.Windows.Data;
using System.Windows;
namespace WaterMarkTextBoxDemo{publicclassTextInputToVisibilityConverter:IMultiValueConverter{publicobjectConvert(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]isbool&& values[1]isbool){bool hasText =!(bool)values[0];bool hasFocus =(bool)values[1];if(hasFocus || hasText)returnVisibility.Collapsed;}returnVisibility.Visible;}publicobject[]ConvertBack(objectvalue,Type[] targetTypes,object parameter,System.Globalization.CultureInfo culture ){thrownewNotImplementedException();}}}
Nota: Este não é o meu código. Encontrei aqui , mas acho que essa é a melhor abordagem.
A melhor abordagem? certamente não! Deseja realmente digitar tantas linhas de código sempre que precisar de uma marca d'água? A solução com uma propriedade anexada é muito mais fácil reutilização ...
Thomas Levesque
5
Considere criar um UserControl.
CSharper
6
Embora eu realmente aprecie seu esforço para ajudar a comunidade, preciso dizer que isso está longe de ser uma abordagem decente.
r41n
2
Este código foi criado por Andy L. Você pode encontrá-lo no codeproject .
aloisdg movendo-se para codidact.com 14/01
440
Você pode criar uma marca d'água que possa ser adicionada a qualquer TextBoxuma com uma propriedade anexada. Aqui está a fonte da propriedade anexada:
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>publicstaticclassWatermarkService{/// <summary>/// Watermark Attached Dependency Property/// </summary>publicstaticreadonlyDependencyPropertyWatermarkProperty=DependencyProperty.RegisterAttached("Watermark",typeof(object),typeof(WatermarkService),newFrameworkPropertyMetadata((object)null,newPropertyChangedCallback(OnWatermarkChanged)));#region Private Fields/// <summary>/// Dictionary of ItemsControls/// </summary>privatestaticreadonlyDictionary<object,ItemsControl> itemsControls =newDictionary<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>publicstaticobjectGetWatermark(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>publicstaticvoidSetWatermark(DependencyObject d,objectvalue){
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>privatestaticvoidOnWatermarkChanged(DependencyObject d,DependencyPropertyChangedEventArgs e){Control control =(Control)d;
control.Loaded+=Control_Loaded;if(d isComboBox){
control.GotKeyboardFocus+=Control_GotKeyboardFocus;
control.LostKeyboardFocus+=Control_Loaded;}elseif(d isTextBox){
control.GotKeyboardFocus+=Control_GotKeyboardFocus;
control.LostKeyboardFocus+=Control_Loaded;((TextBox)control).TextChanged+=Control_GotKeyboardFocus;}if(d isItemsControl&&!(d isComboBox)){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>privatestaticvoidControl_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>privatestaticvoidControl_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>privatestaticvoidItemsSourceChanged(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>privatestaticvoidItemsChanged(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>privatestaticvoidRemoveWatermark(UIElement control){AdornerLayer layer =AdornerLayer.GetAdornerLayer(control);// layer could be null if control is no longer in the visual treeif(layer !=null){Adorner[] adorners = layer.GetAdorners(control);if(adorners ==null){return;}foreach(Adorner adorner in adorners){if(adorner isWatermarkAdorner){
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>privatestaticvoidShowWatermark(Control control){AdornerLayer layer =AdornerLayer.GetAdornerLayer(control);// layer could be null if control is no longer in the visual treeif(layer !=null){
layer.Add(newWatermarkAdorner(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>privatestaticboolShouldShowWatermark(Control c){if(c isComboBox){return(c asComboBox).Text==string.Empty;}elseif(c isTextBoxBase){return(c asTextBox).Text==string.Empty;}elseif(c isItemsControl){return(c asItemsControl).Items.Count==0;}else{returnfalse;}}#endregion}
A propriedade anexada usa uma classe chamada WatermarkAdorner, aqui está essa 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>internalclassWatermarkAdorner:Adorner{#region Private Fields/// <summary>/// <see cref="ContentPresenter"/> that holds the watermark/// </summary>privatereadonlyContentPresenter 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>publicWatermarkAdorner(UIElement adornedElement,object watermark):base(adornedElement){this.IsHitTestVisible=false;this.contentPresenter =newContentPresenter();this.contentPresenter.Content= watermark;this.contentPresenter.Opacity=0.5;this.contentPresenter.Margin=newThickness(Control.Margin.Left+Control.Padding.Left,Control.Margin.Top+Control.Padding.Top,0,0);if(this.ControlisItemsControl&&!(this.ControlisComboBox)){this.contentPresenter.VerticalAlignment=VerticalAlignment.Center;this.contentPresenter.HorizontalAlignment=HorizontalAlignment.Center;}// Hide the control adorner when the adorned element is hiddenBinding binding =newBinding("IsVisible");
binding.Source= adornedElement;
binding.Converter=newBooleanToVisibilityConverter();this.SetBinding(VisibilityProperty, binding);}#endregion#region Protected Properties/// <summary>/// Gets the number of children for the <see cref="ContainerVisual"/>./// </summary>protectedoverrideintVisualChildrenCount{get{return1;}}#endregion#region Private Properties/// <summary>/// Gets the control that is being adorned/// </summary>privateControlControl{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>protectedoverrideVisualGetVisualChild(int index){returnthis.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>protectedoverrideSizeMeasureOverride(Size constraint){// Here's the secret to getting the adorner to cover the whole controlthis.contentPresenter.Measure(Control.RenderSize);returnControl.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>protectedoverrideSizeArrangeOverride(Size finalSize){this.contentPresenter.Arrange(newRect(finalSize));return finalSize;}#endregion}
Agora você pode colocar uma marca d'água em qualquer TextBox como este:
<AdornerDecorator><TextBox x:Name="SearchTextBox"><controls:WatermarkService.Watermark><TextBlock>Type here to search text</TextBlock></controls:WatermarkService.Watermark></TextBox></AdornerDecorator>
A marca d'água pode ser o que você quiser (texto, imagens ...). Além de trabalhar para TextBoxes, essa marca d'água também funciona para ComboBoxes e ItemControls.
Eu resolvi modificar a atribuição da margem do construtor WatermarkAdorner como: Margin = new Thickness (Control.Padding.Left, Control.Padding.Top + 1, Control.Padding.Right, Control.Padding.Bottom)
JoanComasFdz
3
@JohnMyczek Para localizar a marca d'água: como vincular o TextBox.Text na declaração xaml Watermark a uma propriedade do ViewModel?
JoanaComasFdz 14/03/12
7
@Matze @JoanComasFdz Aqui está como eu pode vincular a TextBlock.Textpropriedade para o meu modelo de vista (colocar isso no WatermarkAdornerconstrutor): FrameworkElement feWatermark = watermark as FrameworkElement;if(feWatermark != null && feWatermark.DataContext == null) { feWatermark.DataContext = this.Control.DataContext; }
Sean Salão
9
Possível link de memória aqui. Você está adicionando controles de marca d'água ao dicionário estático interno, mas nunca os remove. Provavelmente, isso impedirá que suas visualizações sejam coletadas como lixo quando você terminar com elas. Eu consideraria usar uma referência fraca aqui.
Jared L
3
Além do dicionário estático de itemcontrols, o código PropertyDescriptor também vaza memória. Você precisa chamar RemoveValueChanged (). Portanto, tenha cuidado ao usar esse código.
muku
284
Apenas usando XAML, sem extensões, sem conversores:
<Grid><TextBoxWidth="250"VerticalAlignment="Center"HorizontalAlignment="Left" x:Name="SearchTermTextBox"Margin="5"/><TextBlockIsHitTestVisible="False"Text="Enter Search Term Here"VerticalAlignment="Center"HorizontalAlignment="Left"Margin="10,0,0,0"Foreground="DarkGray"><TextBlock.Style><StyleTargetType="{x:Type TextBlock}"><SetterProperty="Visibility"Value="Collapsed"/><Style.Triggers><DataTriggerBinding="{Binding Text, ElementName=SearchTermTextBox}"Value=""><SetterProperty="Visibility"Value="Visible"/></DataTrigger></Style.Triggers></Style></TextBlock.Style></TextBlock></Grid>
Extremamente simples, melhor imo também. Não sei por que você usaria todos os outros quando poderia ter essas 10 linhas de script xaml e é isso. Obrigado.
precisa saber é o seguinte
4
Você pode adicionar um Padding="6,3,0,0"ao TextBlock.
aloisdg movendo-se para codidact.com 14/01
1
Muito bom, mas ele não funciona no Windows Phone Silverlight :-(
Andrea Antonangeli /
14
Como alguém faria disso um modelo de controle reutilizável?
Richard
2
@cyrianox Isso ocorre porque a propriedade Senha em um PasswordBox não é vinculável por motivos de segurança. Você pode torná-lo vinculável usando este exemplo aqui: wpftutorial.net/PasswordBox.html no entanto, provavelmente é mais rápido e fácil apenas usar o evento PasswordChanged e o código para definir a visibilidade nesse caso.
APC
54
Não acredito que ninguém postou o óbvio Extended WPF Toolkit - WatermarkTextBox do Xceed. Funciona muito bem e é de código aberto, caso você queira personalizar.
Na minha máquina win8, todos os controles do WPF Toolkit têm estilos do Windows 7 (cantos arredondados, etc.). E qualquer controle do kit de ferramentas WPF parece completamente deslocado quando misturado com controles padrão.
Gman
1
A abordagem "Propriedade anexada" de John Myczek possui um erro no qual, se a caixa de texto fosse coberta por outro elemento, a marca d'água passaria e continuaria visível. Esta solução não tem esse problema. (Gostaria de ter notado isso antes, pois já estou usando o kit de ferramentas). Merece mais votos positivos.
Dax Fohl
A solução de John Myczek também possui um aparente vazamento de memória, onde o WatermarkService manterá uma referência em um dicionário estático para qualquer ItemsControl ao qual uma marca d'água é anexada. Definitivamente poderia ser corrigido, mas vou tentar a versão do Extended WPF Toolkit.
Ok, pode não haver 3 linhas de XAML formatadas, mas é bem simples.
Uma coisa a observar, porém, é que ele usa um método de extensão não padrão na propriedade Text, chamada "IsEmpty". Você precisa implementar isso sozinho, no entanto, o artigo não parece mencionar isso.
O TextBox deve ter IsHitTestVisible="False". Além disso, ele deve vir após o TextBox, caso contrário, poderá não estar visível se o TextBox tiver plano de fundo.
Eu vi a solução de John Myczek e seus comentários sobre Compatibilidade ComboBoxe PasswordBox, então, aprimorei a solução de John Myczek, e aqui está:
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>publicstaticclassWatermarkService{/// <summary>/// Watermark Attached Dependency Property/// </summary>publicstaticreadonlyDependencyPropertyWatermarkProperty=DependencyProperty.RegisterAttached("Watermark",typeof(object),typeof(WatermarkService),newFrameworkPropertyMetadata((object)null,newPropertyChangedCallback(OnWatermarkChanged)));#region Private Fields/// <summary>/// Dictionary of ItemsControls/// </summary>privatestaticreadonlyDictionary<object,ItemsControl> itemsControls =newDictionary<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>publicstaticobjectGetWatermark(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>publicstaticvoidSetWatermark(DependencyObject d,objectvalue){
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>privatestaticvoidOnWatermarkChanged(DependencyObject d,DependencyPropertyChangedEventArgs e){Control control =(Control)d;
control.Loaded+=Control_Loaded;if(d isTextBox|| d isPasswordBox){
control.GotKeyboardFocus+=Control_GotKeyboardFocus;
control.LostKeyboardFocus+=Control_Loaded;}elseif(d isComboBox){
control.GotKeyboardFocus+=Control_GotKeyboardFocus;
control.LostKeyboardFocus+=Control_Loaded;(d asComboBox).SelectionChanged+=newSelectionChangedEventHandler(SelectionChanged);}elseif(d isItemsControl){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>privatestaticvoidSelectionChanged(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>privatestaticvoidControl_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>privatestaticvoidControl_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>privatestaticvoidItemsSourceChanged(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>privatestaticvoidItemsChanged(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>privatestaticvoidRemoveWatermark(UIElement control){AdornerLayer layer =AdornerLayer.GetAdornerLayer(control);// layer could be null if control is no longer in the visual treeif(layer !=null){Adorner[] adorners = layer.GetAdorners(control);if(adorners ==null){return;}foreach(Adorner adorner in adorners){if(adorner isWatermarkAdorner){
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>privatestaticvoidShowWatermark(Control control){AdornerLayer layer =AdornerLayer.GetAdornerLayer(control);// layer could be null if control is no longer in the visual treeif(layer !=null){
layer.Add(newWatermarkAdorner(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>privatestaticboolShouldShowWatermark(Control c){if(c isComboBox){return(c asComboBox).SelectedItem==null;//return (c as ComboBox).Text == string.Empty;}elseif(c isTextBoxBase){return(c asTextBox).Text==string.Empty;}elseif(c isPasswordBox){return(c asPasswordBox).Password==string.Empty;}elseif(c isItemsControl){return(c asItemsControl).Items.Count==0;}else{returnfalse;}}#endregion}
Agora, a ComboBoxpode ser também Editablee também PasswordBoxpode adicionar uma marca d'água. Não se esqueça de usar o comentário de JoanComasFdz acima para resolver o problema de margem.
Ótima solução. Por que sombrear a propriedade Foreground? SetBinding (TextProperty, new Binding ()) lança InvalidOperationException: a ligação bidirecional requer Path ou XPath?
Tim Murphy
Oculto a propriedade Foreground porque o TextBoxWatermarked a usa para seus próprios fins. Não sei por que InvalidOperationException é lançada, talvez se você usar o WPF (usei com o Silverlight) precise passar nulo em vez de novo Binding ().
Vitaliy Ulantikov
2
Para usar esse código no WPF, use em BindingOperations.ClearBinding(this, TextProperty)vez de SetBinding(TextProperty, new Binding())nos dois lugares.
Sebastian Krysmanski
1
Na verdade, isso muda Textpara a marca d'água. Não funcionaria para mim.
lobsterism
provavelmente útil adicionar linhas de namespace a isso ou qualificar totalmente algumas dessas coisas.
21716 June Richard
6
Eu tive um pouco de dificuldade ao usar o código de @ john-myczek com um TextBox vinculado. Como o TextBox não gera um evento de foco quando é atualizado, a marca d'água permanece visível embaixo do novo texto. Para corrigir isso, simplesmente adicionei outro manipulador de eventos:
Gostaria de ter notado essa resposta antes de fazer isso sozinho.
lobsterism
5
@ Veton - Eu realmente gosto da simplicidade da sua solução, mas minha reputação ainda não é alta o suficiente para bater em você.
@ Tim Murphy - O erro "Ligação bidirecional requer Caminho ou XPath" foi uma solução fácil ... código atualizado, incluindo alguns outros pequenos ajustes (apenas WPF testado):
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;publicclassTextBoxWatermarked:TextBox{publicstringWatermark{get{return(string)GetValue(WaterMarkProperty);}set{SetValue(WaterMarkProperty,value);}}publicstaticreadonlyDependencyPropertyWaterMarkProperty=DependencyProperty.Register("Watermark",typeof(string),typeof(TextBoxWatermarked),newPropertyMetadata(newPropertyChangedCallback(OnWatermarkChanged)));privatebool _isWatermarked =false;privateBinding _textBinding =null;publicTextBoxWatermarked(){Loaded+=(s, ea)=>ShowWatermark();}protectedoverridevoidOnGotFocus(RoutedEventArgs e){base.OnGotFocus(e);HideWatermark();}protectedoverridevoidOnLostFocus(RoutedEventArgs e){base.OnLostFocus(e);ShowWatermark();}privatestaticvoidOnWatermarkChanged(DependencyObject sender,DependencyPropertyChangedEventArgs ea){var tbw = sender asTextBoxWatermarked;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();}privatevoidShowWatermark(){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 WatermarkBindingOperations.ClearBinding(this,TextProperty);//set the signature watermark grayForeground=newSolidColorBrush(Colors.Gray);//display our watermark textText=Watermark;}}privatevoidHideWatermark(){if(_isWatermarked){
_isWatermarked =false;ClearValue(ForegroundProperty);Text="";if(_textBinding !=null)SetBinding(TextProperty, _textBinding);}}}
O conversor, como está escrito agora, não é necessário que seja um MultiConverter, mas neste wasy pode ser estendido facilmente
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace WPFControls{classShadowConverter:IMultiValueConverter{#region Implementation of IMultiValueConverterpublicobjectConvert(object[] values,Type targetType,object parameter,CultureInfo culture){var text =(string) values[0];return text ==string.Empty?Visibility.Visible:Visibility.Collapsed;}publicobject[]ConvertBack(objectvalue,Type[] targetTypes,object parameter,CultureInfo culture){returnnewobject[0];}#endregion}}
e finalmente o código por trás:
using System.Windows;
using System.Windows.Controls;
namespace WPFControls{/// <summary>/// Interaction logic for ShadowedTextBox.xaml/// </summary>publicpartialclassShadowedTextBox:UserControl{publiceventTextChangedEventHandlerTextChanged;publicShadowedTextBox(){InitializeComponent();}publicstaticreadonlyDependencyPropertyWatermarkProperty=DependencyProperty.Register("Watermark",typeof(string),typeof(ShadowedTextBox),newUIPropertyMetadata(string.Empty));publicstaticreadonlyDependencyPropertyTextProperty=DependencyProperty.Register("Text",typeof(string),typeof(ShadowedTextBox),newUIPropertyMetadata(string.Empty));publicstaticreadonlyDependencyPropertyTextChangedProperty=DependencyProperty.Register("TextChanged",typeof(TextChangedEventHandler),typeof(ShadowedTextBox),newUIPropertyMetadata(null));publicstringWatermark{get{return(string)GetValue(WatermarkProperty);}set{SetValue(WatermarkProperty,value);}}publicstringText{get{return(string)GetValue(TextProperty);}set{SetValue(TextProperty,value);}}privatevoid textBox_TextChanged(object sender,TextChangedEventArgs e){if(TextChanged!=null)TextChanged(this, e);}publicvoidClear(){
textBox.Clear();}}}
O MahApps.Metro para WPF possui um controle de marca d'água incorporado, se você preferir não usar o seu. É bastante simples de usar.
<AdornerDecorator><TextBoxName="txtSomeText"Width="200"HorizontalAlignment="Right"><Controls:TextBoxHelper.Watermark>I'm a watermark!</Controls:TextBoxHelper.Watermark></TextBox></AdornerDecorator>
Esta é uma caixa de texto com fundo transparente sobrepondo um rótulo. O texto cinza do rótulo fica transparente por um gatilho de dados que é acionado sempre que o texto encadernado for algo diferente de uma sequência vazia.
Para aumentar a reutilização desse estilo, você também pode criar um conjunto de propriedades anexadas para controlar o texto, a cor, a orientação, etc.
Este é um exemplo perfeito que descreve como não fazer isso, especialmente com o WPF.
Alexandru Dicu
0
Essa técnica usa a propriedade Background para mostrar / ocultar a caixa de texto do espaço reservado. O espaço reservado é mostrado quando a caixa de texto tem o foco
Como funciona:
Quando vazio, o plano de fundo do TextBox é definido como Transparente para mostrar o texto do PlaceHolder.
Quando o fundo não estiver vazio, defina como Branco para encobrir o texto do PlaceHolder.
Aqui está um exemplo básico. Para meus próprios propósitos, transformei isso em um UserControl.
Você pode manter um valor separado para o texto digitado e pode defini-lo junto com o campo "Texto" da caixa de texto nos eventos "GotFocus" e "LostFocus". Quando você obtiver o foco, desmarque a caixa de texto se não houver valor. E quando você perder o foco, defina o valor "Texto" na caixa de texto e redefina o valor "Texto" da caixa de texto para o espaço reservado, se estiver vazio.
Se, em vez de ter a visibilidade da marca d'água, depender do estado de foco do controle, você deseja que ele dependa se o usuário inseriu algum texto, atualize a resposta de John Myczek (de OnWatermarkChangedbaixo) para
Isso faz mais sentido se sua caixa de texto obtiver foco automaticamente ao exibir o formulário ou ao vincular à propriedade Text.
Além disso, se sua marca d'água for sempre apenas uma string e você precisar do estilo da marca d'água para corresponder ao estilo da caixa de texto, no Adorner, faça:
Aqui está minha abordagem É ótimo para o MVVM, onde também verifico se a caixa de texto tem foco, você também pode usar um gatilho regular apenas para o valor do texto, e o ponto é que apenas altero a imagem de fundo quando o valor muda:
Eu decidi resolver isso através de um comportamento. Ele usa uma Hintpropriedade para definir o texto a ser exibido (também pode ser um objeto, se você preferir) e uma Valuepropriedade para avaliar se a dica deve estar visível ou não.
O comportamento é declarado da seguinte maneira:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Interactivity;
using System.Windows.Media;publicclassHintBehavior:Behavior<ContentControl>{publicstaticreadonlyDependencyPropertyHintProperty=DependencyProperty.Register("Hint",typeof(string),typeof(HintBehavior)//, new FrameworkPropertyMetadata(null, OnHintChanged));publicstringHint{get{return(string)GetValue(HintProperty);}set{SetValue(HintProperty,value);}}publicstaticreadonlyDependencyPropertyValueProperty=DependencyProperty.Register("Value",typeof(object),typeof(HintBehavior),newFrameworkPropertyMetadata(null,OnValueChanged));privatestaticvoidOnValueChanged(DependencyObject d,DependencyPropertyChangedEventArgs e){var visible = e.NewValue==null;
d.SetValue(VisibilityProperty, visible ?Visibility.Visible:Visibility.Collapsed);}publicobjectValue{get{returnGetValue(ValueProperty);}set{SetValue(ValueProperty,value);}}publicstaticreadonlyDependencyPropertyVisibilityProperty=DependencyProperty.Register("Visibility",typeof(Visibility),typeof(HintBehavior),newFrameworkPropertyMetadata(Visibility.Visible//, new PropertyChangedCallback(OnVisibilityChanged)));publicVisibilityVisibility{get{return(Visibility)GetValue(VisibilityProperty);}set{SetValue(VisibilityProperty,value);}}publicstaticreadonlyDependencyPropertyForegroundProperty=DependencyProperty.Register("Foreground",typeof(Brush),typeof(HintBehavior),newFrameworkPropertyMetadata(newSolidColorBrush(Colors.DarkGray)//, new PropertyChangedCallback(OnForegroundChanged)));publicBrushForeground{get{return(Brush)GetValue(ForegroundProperty);}set{SetValue(ForegroundProperty,value);}}publicstaticreadonlyDependencyPropertyMarginProperty=DependencyProperty.Register("Margin",typeof(Thickness),typeof(HintBehavior),newFrameworkPropertyMetadata(newThickness(4,5,0,0)//, new PropertyChangedCallback(OnMarginChanged)));publicThicknessMargin{get{return(Thickness)GetValue(MarginProperty);}set{SetValue(MarginProperty,value);}}privatestaticResourceDictionary _hintBehaviorResources;publicstaticResourceDictionaryHintBehaviorResources{get{if(_hintBehaviorResources ==null){var res =newResourceDictionary{Source=newUri("/Mayflower.Client.Core;component/Behaviors/HintBehaviorResources.xaml",UriKind.RelativeOrAbsolute)};
_hintBehaviorResources = res;}return _hintBehaviorResources;}}protectedoverridevoidOnAttached(){base.OnAttached();var t =(ControlTemplate)HintBehaviorResources["HintBehaviorWrapper"];AssociatedObject.Template= t;AssociatedObject.Loaded+=OnLoaded;}privatevoidOnLoaded(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,newBinding("Visibility"){Source=this,Mode=BindingMode.OneWay});
label.SetBinding(ContentControl.ContentProperty,newBinding("Hint"){Source=this,Mode=BindingMode.OneWay});
label.SetBinding(Control.ForegroundProperty,newBinding("Foreground"){Source=this,Mode=BindingMode.OneWay});
label.SetBinding(FrameworkElement.MarginProperty,newBinding("Margin"){Source=this,Mode=BindingMode.OneWay});}}
Ele envolve o destino com seu próprio modelo, adicionando a ele um rótulo:
Gostaria muito de receber feedback se isso for considerado uma solução limpa. Não requer dicionários estáticos e, portanto, não possui vazamento de memória.
Respostas:
Este é um exemplo que demonstra como criar uma caixa de texto de marca d'água no WPF:
TextInputToVisibilityConverter é definido como:
Nota: Este não é o meu código. Encontrei aqui , mas acho que essa é a melhor abordagem.
fonte
Você pode criar uma marca d'água que possa ser adicionada a qualquer
TextBox
uma com uma propriedade anexada. Aqui está a fonte da propriedade anexada:A propriedade anexada usa uma classe chamada
WatermarkAdorner
, aqui está essa fonte:Agora você pode colocar uma marca d'água em qualquer TextBox como este:
A marca d'água pode ser o que você quiser (texto, imagens ...). Além de trabalhar para TextBoxes, essa marca d'água também funciona para ComboBoxes e ItemControls.
Este código foi adaptado desta postagem do blog .
fonte
TextBlock.Text
propriedade para o meu modelo de vista (colocar isso noWatermarkAdorner
construtor):FrameworkElement feWatermark = watermark as FrameworkElement;
if(feWatermark != null && feWatermark.DataContext == null)
{feWatermark.DataContext = this.Control.DataContext;
}Apenas usando XAML, sem extensões, sem conversores:
fonte
Padding="6,3,0,0"
aoTextBlock
.Não acredito que ninguém postou o óbvio Extended WPF Toolkit - WatermarkTextBox do Xceed. Funciona muito bem e é de código aberto, caso você queira personalizar.
fonte
Há um artigo no CodeProject sobre como fazer isso em "3 linhas de XAML".
Ok, pode não haver 3 linhas de XAML formatadas, mas é bem simples.
Uma coisa a observar, porém, é que ele usa um método de extensão não padrão na propriedade Text, chamada "IsEmpty". Você precisa implementar isso sozinho, no entanto, o artigo não parece mencionar isso.
fonte
IsHitTestVisible="False"
. Além disso, ele deve vir após o TextBox, caso contrário, poderá não estar visível se o TextBox tiver plano de fundo.Text.IsEmpty
funciona: IsEmpty está sendo resolvido a partir de CollectionView.IsEmptyEu vi a solução de John Myczek e seus comentários sobre Compatibilidade
ComboBox
ePasswordBox
, então, aprimorei a solução de John Myczek, e aqui está:Agora, a
ComboBox
pode ser tambémEditable
e tambémPasswordBox
pode adicionar uma marca d'água. Não se esqueça de usar o comentário de JoanComasFdz acima para resolver o problema de margem.E, claro, todo o crédito é para John Myczek.
fonte
Solução simples usando estilo:
Ótima solução:
https://code.msdn.microsoft.com/windowsdesktop/In-place-hit-messages-for-18db3a6c
fonte
Esta biblioteca possui uma marca d'água.
Pacote de pepitas
Uso da amostra:
fonte
Eu criei uma implementação apenas de código simples, que também funciona bem para WPF e Silverlight:
Uso:
fonte
BindingOperations.ClearBinding(this, TextProperty)
vez deSetBinding(TextProperty, new Binding())
nos dois lugares.Text
para a marca d'água. Não funcionaria para mim.Eu tive um pouco de dificuldade ao usar o código de @ john-myczek com um TextBox vinculado. Como o TextBox não gera um evento de foco quando é atualizado, a marca d'água permanece visível embaixo do novo texto. Para corrigir isso, simplesmente adicionei outro manipulador de eventos:
fonte
@ Veton - Eu realmente gosto da simplicidade da sua solução, mas minha reputação ainda não é alta o suficiente para bater em você.
@ Tim Murphy - O erro "Ligação bidirecional requer Caminho ou XPath" foi uma solução fácil ... código atualizado, incluindo alguns outros pequenos ajustes (apenas WPF testado):
fonte
você pode usar
GetFocus()
eLostFocus()
eventos para fazer issoaqui está o exemplo:
fonte
Maneira mais simples de WaterMark Of TextBox
e adicione o estilo StaticResource da caixa de texto
fonte
Isso pode ajudar a verificar o código. Quando aplicado à caixa de senha, ele mostra a Senha, que desaparece quando o usuário digita.
fonte
Bem, aqui está o meu: não necessariamente o melhor, mas, como é simples, é fácil editar ao seu gosto.
O conversor, como está escrito agora, não é necessário que seja um MultiConverter, mas neste wasy pode ser estendido facilmente
e finalmente o código por trás:
fonte
fonte
O MahApps.Metro para WPF possui um controle de marca d'água incorporado, se você preferir não usar o seu. É bastante simples de usar.
fonte
Configure a caixa de texto com o espaço reservado em uma cor suave ...
Quando a caixa de texto receber o foco, limpe-a e altere a cor do texto
fonte
Aqui está a solução mais simples:
Esta é uma caixa de texto com fundo transparente sobrepondo um rótulo. O texto cinza do rótulo fica transparente por um gatilho de dados que é acionado sempre que o texto encadernado for algo diferente de uma sequência vazia.
fonte
Além disso, veja esta resposta . Você pode fazer isso muito mais facilmente com um VisualBrush e alguns gatilhos em um estilo:
Para aumentar a reutilização desse estilo, você também pode criar um conjunto de propriedades anexadas para controlar o texto, a cor, a orientação, etc.
fonte
oi eu coloquei esta tarefa em um comportamento. então você só precisa adicionar algo assim à sua caixa de texto
você pode encontrar meu post aqui
fonte
Minha solução é bastante simples.
Na minha janela de login. o xaml é assim.
o código é assim.
Basta decidir ocultar ou mostrar que a caixa de texto de marca d'água é suficiente. Embora não seja bonito, mas funciona bem.
fonte
Essa técnica usa a propriedade Background para mostrar / ocultar a caixa de texto do espaço reservado.
O espaço reservado é mostrado quando a caixa de texto tem o foco
Como funciona:
Aqui está um exemplo básico. Para meus próprios propósitos, transformei isso em um UserControl.
Aqui está o ValueConverter para detectar seqüências de caracteres não vazias no DataTrigger.
fonte
Você pode manter um valor separado para o texto digitado e pode defini-lo junto com o campo "Texto" da caixa de texto nos eventos "GotFocus" e "LostFocus". Quando você obtiver o foco, desmarque a caixa de texto se não houver valor. E quando você perder o foco, defina o valor "Texto" na caixa de texto e redefina o valor "Texto" da caixa de texto para o espaço reservado, se estiver vazio.
Então você só precisa garantir que o valor "Texto" da caixa de texto seja inicializado no texto do espaço reservado.
Você pode extrair ainda mais isso em uma classe que estende a classe "TextBox" e depois reutilizá-la no projeto.
E então isso pode ser adicionado diretamente no xaml.
fonte
Se, em vez de ter a visibilidade da marca d'água, depender do estado de foco do controle, você deseja que ele dependa se o usuário inseriu algum texto, atualize a resposta de John Myczek (de
OnWatermarkChanged
baixo) paraIsso faz mais sentido se sua caixa de texto obtiver foco automaticamente ao exibir o formulário ou ao vincular à propriedade Text.
Além disso, se sua marca d'água for sempre apenas uma string e você precisar do estilo da marca d'água para corresponder ao estilo da caixa de texto, no Adorner, faça:
fonte
Aqui está minha abordagem É ótimo para o MVVM, onde também verifico se a caixa de texto tem foco, você também pode usar um gatilho regular apenas para o valor do texto, e o ponto é que apenas altero a imagem de fundo quando o valor muda:
fonte
Eu decidi resolver isso através de um comportamento. Ele usa uma
Hint
propriedade para definir o texto a ser exibido (também pode ser um objeto, se você preferir) e umaValue
propriedade para avaliar se a dica deve estar visível ou não.O comportamento é declarado da seguinte maneira:
Ele envolve o destino com seu próprio modelo, adicionando a ele um rótulo:
Para usá-lo, basta adicioná-lo como um comportamento e vincular seus valores (no meu caso, eu o adiciono em um ControlTemplate, daí a ligação):
Gostaria muito de receber feedback se isso for considerado uma solução limpa. Não requer dicionários estáticos e, portanto, não possui vazamento de memória.
fonte
Eu encontrei essa maneira de fazê-lo de uma maneira muito rápida e fácil
Talvez possa ajudar qualquer pessoa que tente fazer isso
Fonte: http://www.admindiaries.com/displaying-a-please-select-watermark-type-text-in-a-wpf-combobox/
fonte
fonte
Adicione mahapps.metro ao seu projeto. Adicione caixa de texto com o código acima à janela.
fonte