Este es un ejemplo que muestra cómo crear un cuadro de texto de marca de agua en 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 se define 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 no es mi código. Lo encontré aquí , pero creo que este es el mejor enfoque.
El mejor enfoque? ¡ciertamente no! ¿Realmente desea escribir tantas líneas de código cada vez que necesita una marca de agua? La solución con una propiedad adjunta es mucho más fácil de reutilizar ...
Thomas Levesque
55
Considere crear un UserControl.
CSharper
66
Aunque realmente aprecio su esfuerzo por ayudar a la comunidad, realmente necesito decir que esto está lejos de ser un enfoque decente.
r41n
2
Este código fue creado por Andy L. Puede encontrarlo en codeproject .
aloisdg se muda a codidact.com el
440
Puede crear una marca de agua que se puede agregar a cualquiera TextBoxcon una propiedad adjunta. Aquí está la fuente de la propiedad adjunta:
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}
La propiedad adjunta utiliza una clase llamada WatermarkAdorner, aquí está esa fuente:
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}
Ahora puede poner una marca de agua en cualquier cuadro de texto como este:
<AdornerDecorator><TextBox x:Name="SearchTextBox"><controls:WatermarkService.Watermark><TextBlock>Type here to search text</TextBlock></controls:WatermarkService.Watermark></TextBox></AdornerDecorator>
La marca de agua puede ser lo que quieras (texto, imágenes ...). Además de trabajar para TextBoxes, esta marca de agua también funciona para ComboBoxes y ItemControls.
Lo resolví modificando la asignación de margen del constructor WatermarkAdorner como: Margen = nuevo espesor (Control.Padding.Left, Control.Padding.Top + 1, Control.Padding.Right, Control.Padding.Bottom)
JoanComasFdz
3
@JohnMyczek Para localizar la marca de agua: ¿cómo puedo vincular TextBox.Text en la declaración xaml de marca de agua a una propiedad de ViewModel?
JoanComasFdz
77
@Matze @JoanComasFdz Así es como puedo vincular la TextBlock.Textpropiedad a mi modelo de vista (poner esto en el WatermarkAdornerconstructor): FrameworkElement feWatermark = watermark as FrameworkElement;if(feWatermark != null && feWatermark.DataContext == null) { feWatermark.DataContext = this.Control.DataContext; }
Sean Hall
9
Posible enlace de memoria aquí. Está agregando controles con marcas de agua al diccionario estático interno pero nunca los elimina. Esto probablemente evitará que sus vistas sean recolectadas de basura una vez que haya terminado con ellas. Consideraría usar una referencia débil aquí.
Jared G
3
Además del diccionario estático de los controles de elementos, el código PropertyDescriptor también pierde memoria. Debe llamar a RemoveValueChanged (). Así que ten cuidado cuando uses este código.
muku
284
Solo usando XAML, sin extensiones, sin convertidores:
<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>
Extremadamente simple, mejor imo también. No sé por qué usarías todos esos otros cuando podrías tener estas 10 líneas de script xaml y eso es todo. Gracias.
dj.lnxss
44
Es posible que desee agregar un Padding="6,3,0,0"a la TextBlock.
aloisdg se muda a codidact.com el
1
Muy agradable, pero no funciona en Windows Phone Silverlight :-(
Andrea Antonangeli
14
¿Cómo podría uno hacer de esto una plantilla de control reutilizable?
Richard
2
@cyrianox Esto se debe a que la propiedad Contraseña en PasswordBox no se puede enlazar por razones de seguridad. Puede hacerlo enlazable usando este ejemplo aquí: wpftutorial.net/PasswordBox.html, sin embargo, probablemente sea más rápido y fácil usar el evento PasswordChanged y el código para establecer la visibilidad en este caso.
En mi máquina win8, todos los controles de WPF Toolkit tienen estilos de Windows 7 (esquinas redondeadas, etc.). Y cualquier control del kit de herramientas WPF parece completamente fuera de lugar cuando se combina con controles estándar.
Gman
1
El enfoque de "Propiedad adjunta" de John Myczek tiene un error por el cual si el cuadro de texto estaba cubierto por otro elemento, la marca de agua se desangraría y aún sería visible. Esta solución no tiene ese problema. (Ojalá hubiera notado esto antes, ya que de todos modos ya estoy usando el kit de herramientas). Merece más votos a favor.
Dax Fohl
La solución de John Myczek también tiene una aparente pérdida de memoria, donde WatermarkService mantendrá una referencia en un diccionario estático a cualquier ItemControl al que se adjunte una marca de agua. Definitivamente podría arreglarse, pero probaré la versión extendida de WPF Toolkit.
Ok, bueno, puede que no tengan 3 líneas de XAML formateadas, pero es bastante simple.
Sin embargo, una cosa a tener en cuenta es que utiliza un método de extensión no estándar en la propiedad Text, llamado "IsEmpty". Debe implementar esto usted mismo, sin embargo, el artículo no parece mencionar eso.
El TextBox debería tener IsHitTestVisible="False". Además, debería aparecer después de TextBox, de lo contrario, podría no ser visible si TextBox tiene fondo.
Vi la solución de John Myczek y sus comentarios sobre Compatibilidad con ComboBoxy PasswordBox, así que mejoré la solución de John Myczek, y aquí 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}
Ahora, a ComboBoxpuede ser también Editable, y también PasswordBoxpuede agregar una marca de agua. No olvides usar el comentario de JoanComasFdz anterior para resolver el problema del margen.
Y, por supuesto, todo el crédito va a John Myczek.
Gran solución ¿Por qué sombrear la propiedad de primer plano? SetBinding (TextProperty, new Binding ()) arroja InvalidOperationException: ¿el enlace bidireccional requiere Path o XPath?
Tim Murphy
Oculto la propiedad de primer plano porque TextBoxWatermarked la usa para sus propios fines. No sé por qué se lanza InvalidOperationException, tal vez si usa WPF (lo usé con Silverlight) necesita pasar nulo en lugar de nuevo Binding ().
Vitaliy Ulantikov
2
Para usar este código en WPF, use en BindingOperations.ClearBinding(this, TextProperty)lugar de SetBinding(TextProperty, new Binding())en ambos lugares.
Sebastian Krysmanski
1
Esto realmente cambia Texta la marca de agua. No funcionaría para mí
langosta
probablemente útil para agregar líneas de espacio de nombres a esto, o calificar completamente algunas de estas cosas.
Richard June
6
Me encontré con un poco de dificultad al usar el código de @ john-myczek con un TextBox enlazado. Como TextBox no genera un evento de foco cuando se actualiza, la marca de agua permanecerá visible debajo del nuevo texto. Para solucionar esto, simplemente agregué otro controlador de eventos:
Ojalá hubiera notado esta respuesta antes de hacerlo yo mismo.
langosta
5
@Veton: realmente me gusta la simplicidad de su solución, pero mi reputación aún no es lo suficientemente alta como para golpearlo.
@Tim Murphy: el error "El enlace bidireccional requiere ruta o XPath" fue una solución fácil ... código actualizado que incluye algunos otros pequeños ajustes (solo WPF probado):
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);}}}
El convertidor, como está escrito ahora, no es necesario que sea un MultiConverter, pero en este caso puede ampliarse fácilmente
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}}
y finalmente el código detrá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();}}}
MahApps.Metro para WPF tiene un control de marca de agua incorporado, si prefieres no rodar el tuyo. Es bastante sencillo de usar.
<AdornerDecorator><TextBoxName="txtSomeText"Width="200"HorizontalAlignment="Right"><Controls:TextBoxHelper.Watermark>I'm a watermark!</Controls:TextBoxHelper.Watermark></TextBox></AdornerDecorator>
Este es un cuadro de texto con fondo transparente superpuesto a una etiqueta. El texto gris de la etiqueta se vuelve transparente por un activador de datos que se activa cuando el texto enlazado no es una cadena vacía.
Para aumentar la reutilización de este estilo, también puede crear un conjunto de propiedades adjuntas para controlar el texto, el color, la orientación del banner de referencia real, etc.
Este es un ejemplo perfecto que describe cómo no hacerlo, especialmente con WPF.
Alexandru Dicu
0
Esta técnica utiliza la propiedad Fondo para mostrar / ocultar el cuadro de texto de marcador de posición. El marcador de posición se muestra cuando Textbox tiene el foco
Cómo funciona:
Cuando está vacío, el fondo de TextBox se establece en Transparente para mostrar el texto de PlaceHolder.
Cuando no está vacío, el fondo se configura en Blanco para cubrir el texto de PlaceHolder
Aquí hay un ejemplo básico. Para mis propios fines, convertí esto en un UserControl.
Puede mantener un valor separado para el texto ingresado y puede configurarlo junto con el campo "Texto" del cuadro de texto en los eventos "GotFocus" y "LostFocus". Cuando obtenga el foco, querrá borrar el cuadro de texto si no hay ningún valor. Y cuando pierda el foco, querrá establecer el valor "Texto" del cuadro de texto y luego restablecer el valor "Texto" del cuadro de texto al marcador de posición si está vacío.
Si, en lugar de que la visibilidad de la marca de agua dependa del estado de enfoque del control, desea que dependa de si el usuario ha ingresado algún texto, puede actualizar la respuesta de John Myczek (de OnWatermarkChangedabajo) a
Esto tiene más sentido si su cuadro de texto se enfoca automáticamente cuando muestra el formulario o cuando se enlaza a la propiedad Texto.
Además, si su marca de agua siempre es solo una cadena y necesita que el estilo de la marca de agua coincida con el estilo del cuadro de texto, en el Adorner haga lo siguiente:
Este es mi enfoque. Es genial para MVVM, donde también verifico si el cuadro de texto tiene el foco, también puede usar un disparador regular solo para el valor del texto y el punto es que simplemente cambio la imagen de fondo cuando cambia el valor:
Decidí resolver esto a través de un comportamiento. Utiliza una Hintpropiedad para definir el texto a mostrar (también podría ser un objeto, si lo prefiere) y una Valuepropiedad para evaluar si la sugerencia debe ser visible o no.
El comportamiento se declara de la siguiente manera:
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});}}
Envuelve el objetivo con su propia plantilla y le agrega una etiqueta:
Me encantaría recibir comentarios si se considera una solución limpia. No requiere diccionarios estáticos y, por lo tanto, no tiene pérdida de memoria.
Respuestas:
Este es un ejemplo que muestra cómo crear un cuadro de texto de marca de agua en WPF:
TextInputToVisibilityConverter se define como:
Nota: este no es mi código. Lo encontré aquí , pero creo que este es el mejor enfoque.
fuente
Puede crear una marca de agua que se puede agregar a cualquiera
TextBox
con una propiedad adjunta. Aquí está la fuente de la propiedad adjunta:La propiedad adjunta utiliza una clase llamada
WatermarkAdorner
, aquí está esa fuente:Ahora puede poner una marca de agua en cualquier cuadro de texto como este:
La marca de agua puede ser lo que quieras (texto, imágenes ...). Además de trabajar para TextBoxes, esta marca de agua también funciona para ComboBoxes y ItemControls.
Este código fue adaptado de esta publicación de blog .
fuente
TextBlock.Text
propiedad a mi modelo de vista (poner esto en elWatermarkAdorner
constructor):FrameworkElement feWatermark = watermark as FrameworkElement;
if(feWatermark != null && feWatermark.DataContext == null)
{feWatermark.DataContext = this.Control.DataContext;
}Solo usando XAML, sin extensiones, sin convertidores:
fuente
Padding="6,3,0,0"
a laTextBlock
.No puedo creer que nadie haya publicado el obvio kit de herramientas extendido de WPF: WatermarkTextBox de Xceed. Funciona bastante bien y es de código abierto en caso de que desee personalizar.
fuente
Hay un artículo sobre CodeProject sobre cómo hacerlo en "3 líneas de XAML".
Ok, bueno, puede que no tengan 3 líneas de XAML formateadas, pero es bastante simple.
Sin embargo, una cosa a tener en cuenta es que utiliza un método de extensión no estándar en la propiedad Text, llamado "IsEmpty". Debe implementar esto usted mismo, sin embargo, el artículo no parece mencionar eso.
fuente
IsHitTestVisible="False"
. Además, debería aparecer después de TextBox, de lo contrario, podría no ser visible si TextBox tiene fondo.Text.IsEmpty
funciona: IsEmpty se está resolviendo desde CollectionView.IsEmptyVi la solución de John Myczek y sus comentarios sobre Compatibilidad con
ComboBox
yPasswordBox
, así que mejoré la solución de John Myczek, y aquí está:Ahora, a
ComboBox
puede ser tambiénEditable
, y tambiénPasswordBox
puede agregar una marca de agua. No olvides usar el comentario de JoanComasFdz anterior para resolver el problema del margen.Y, por supuesto, todo el crédito va a John Myczek.
fuente
Solución simple usando estilo:
Gran solución:
https://code.msdn.microsoft.com/windowsdesktop/In-place-hit-messages-for-18db3a6c
fuente
Esta biblioteca tiene una marca de agua.
Paquete Nuget
Uso de la muestra:
fuente
He creado una implementación de código único siple que también funciona bien para WPF y Silverlight:
Uso:
fuente
BindingOperations.ClearBinding(this, TextProperty)
lugar deSetBinding(TextProperty, new Binding())
en ambos lugares.Text
a la marca de agua. No funcionaría para míMe encontré con un poco de dificultad al usar el código de @ john-myczek con un TextBox enlazado. Como TextBox no genera un evento de foco cuando se actualiza, la marca de agua permanecerá visible debajo del nuevo texto. Para solucionar esto, simplemente agregué otro controlador de eventos:
fuente
@Veton: realmente me gusta la simplicidad de su solución, pero mi reputación aún no es lo suficientemente alta como para golpearlo.
@Tim Murphy: el error "El enlace bidireccional requiere ruta o XPath" fue una solución fácil ... código actualizado que incluye algunos otros pequeños ajustes (solo WPF probado):
fuente
puedes usar
GetFocus()
yLostFocus()
eventos para hacer estoAquí está el ejemplo:
fuente
La forma más simple de marcar el agua de TextBox
y agregue el estilo de StaticResource del cuadro de texto
fuente
Esto puede ayudar a verificarlo con su código. Cuando se aplica a la casilla de contraseña, mostrará Contraseña, que desaparecerá cuando use tipos.
fuente
Bueno, aquí está el mío: no necesariamente el mejor, pero como es simple, es fácil de editar a su gusto.
El convertidor, como está escrito ahora, no es necesario que sea un MultiConverter, pero en este caso puede ampliarse fácilmente
y finalmente el código detrás:
fuente
fuente
MahApps.Metro para WPF tiene un control de marca de agua incorporado, si prefieres no rodar el tuyo. Es bastante sencillo de usar.
fuente
Configure el cuadro de texto con texto de marcador de posición en un color suave ...
Cuando el cuadro de texto obtenga el foco, bórrelo y cambie el color del texto
fuente
Aquí está la solución más simple:
Este es un cuadro de texto con fondo transparente superpuesto a una etiqueta. El texto gris de la etiqueta se vuelve transparente por un activador de datos que se activa cuando el texto enlazado no es una cadena vacía.
fuente
Además, vea esta respuesta . Puede lograr esto mucho más fácilmente con un VisualBrush y algunos desencadenantes en un Estilo:
Para aumentar la reutilización de este estilo, también puede crear un conjunto de propiedades adjuntas para controlar el texto, el color, la orientación del banner de referencia real, etc.
fuente
hola puse esta tarea en un comportamiento. así que solo tienes que agregar algo como esto a tu cuadro de texto
puedes encontrar mi blog aquí
fuente
Mi solución es bastante simple.
En mi ventana de inicio de sesión. El xaml es así.
El código es así.
Simplemente decida ocultar o mostrar que el cuadro de texto de marca de agua es suficiente. Aunque no es hermoso, pero funciona bien.
fuente
Esta técnica utiliza la propiedad Fondo para mostrar / ocultar el cuadro de texto de marcador de posición.
El marcador de posición se muestra cuando Textbox tiene el foco
Cómo funciona:
Aquí hay un ejemplo básico. Para mis propios fines, convertí esto en un UserControl.
Aquí está el ValueConverter para detectar cadenas no vacías en el DataTrigger.
fuente
Puede mantener un valor separado para el texto ingresado y puede configurarlo junto con el campo "Texto" del cuadro de texto en los eventos "GotFocus" y "LostFocus". Cuando obtenga el foco, querrá borrar el cuadro de texto si no hay ningún valor. Y cuando pierda el foco, querrá establecer el valor "Texto" del cuadro de texto y luego restablecer el valor "Texto" del cuadro de texto al marcador de posición si está vacío.
Luego solo debe asegurarse de que el valor "Texto" del cuadro de texto se inicialice en el texto del marcador de posición.
Puede extraer esto en una clase que amplíe la clase "TextBox" y luego reutilizarlo en todo su proyecto.
Y luego esto se puede agregar directamente en el xaml.
fuente
Si, en lugar de que la visibilidad de la marca de agua dependa del estado de enfoque del control, desea que dependa de si el usuario ha ingresado algún texto, puede actualizar la respuesta de John Myczek (de
OnWatermarkChanged
abajo) aEsto tiene más sentido si su cuadro de texto se enfoca automáticamente cuando muestra el formulario o cuando se enlaza a la propiedad Texto.
Además, si su marca de agua siempre es solo una cadena y necesita que el estilo de la marca de agua coincida con el estilo del cuadro de texto, en el Adorner haga lo siguiente:
fuente
Este es mi enfoque. Es genial para MVVM, donde también verifico si el cuadro de texto tiene el foco, también puede usar un disparador regular solo para el valor del texto y el punto es que simplemente cambio la imagen de fondo cuando cambia el valor:
fuente
Decidí resolver esto a través de un comportamiento. Utiliza una
Hint
propiedad para definir el texto a mostrar (también podría ser un objeto, si lo prefiere) y unaValue
propiedad para evaluar si la sugerencia debe ser visible o no.El comportamiento se declara de la siguiente manera:
Envuelve el objetivo con su propia plantilla y le agrega una etiqueta:
Para usarlo, solo agréguelo como comportamiento y vincule sus valores (en mi caso, lo agrego en un ControlTemplate, de ahí el enlace):
Me encantaría recibir comentarios si se considera una solución limpia. No requiere diccionarios estáticos y, por lo tanto, no tiene pérdida de memoria.
fuente
Encontré esta manera de hacerlo de una manera muy rápida y fácil
Tal vez pueda ayudar a cualquiera que intente hacer esto
Fuente: http://www.admindiaries.com/displaying-a-please-select-watermark-type-text-in-a-wpf-combobox/
fuente
fuente
Agregue mahapps.metro a su proyecto. Agregue el cuadro de texto con el código anterior a la ventana.
fuente