📜 ⬆️ ⬇️

WPF, Prompt Input Box

Sometimes it is useful to create an effect for an input field that displays a hint while text is missing.
For example, like this:
Sample input field with a hint

This is useful in those moments when you need to save space, select a special field, or just once again give a hint to completely unaware users.

What do we need?
First, the property that is responsible for the hint text. Without it, it will be quite difficult to display something.
Create a class blank.
public class WatermarkedTextBox : DependencyObject
{
#region Fields

private const string _defaultWatermark = "None" ;

public static readonly DependencyProperty WatermarkTextProperty = DependencyProperty.Register( "WatermarkText" , typeof ( string ), typeof (WatermarkedTextBox), new UIPropertyMetadata( string .Empty, OnWatermarkTextChanged));

#endregion

#region Constructor(s)

/// <summary>
/// Initializes a new instance of the <see cref="WatermarkedTextBox"/> class with default watermark text.
/// </summary>
public WatermarkedTextBox()
: this (_defaultWatermark)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="WatermarkedTextBox"/> class.
/// </summary>
/// <param name="watermark">The watermark to show when value is <c>null</c> or empty.</param>
public WatermarkedTextBox( string watermark)
{
WatermarkText = watermark;
}

#endregion

#region Properties

public string WatermarkText
{
get { return ( string )GetValue(WatermarkTextProperty); }
set { SetValue(WatermarkTextProperty, value ); }
}

#endregion

#region Methods

public static void OnWatermarkTextChanged(DependencyObject box, DependencyPropertyChangedEventArgs e)
{
//Add changed functionality here
}

#endregion
}


* This source code was highlighted with Source Code Highlighter .
Now that the procurement has been created and we have the necessary properties and methods of operating with a hint, we can proceed directly to the implementation. On the move you can come up with many options for implementation:
For example, you can hang your handlers to set-get text and display a hint as plain text (I have seen it in different html forms more than once).
You can aggregate TextBox , write logic and make your own data mapping.
But we will use the third, most correct method in the context of WPF. We will use styles to override the display of the control, namely, override the Control Template.

No sooner said than done. To begin with, we will inherit our class from TextBox (instead of DependencyObject ).
If you look here , you can see the following text.
The ControlTemplate for a TextBox must contain the content element; this element will be used to render the contents of the textbox. Assign it to the special name PART_ContentHost. The ScrollViewer or An AdornerDecorator. The host element may not be any child elements.
This means that in the template you will need to create a ScrollViewer with the name PART_ContentHost.
So, the cunning plan is this: in those moments when the text inside the TextBox missing - we will show the prepared inscription from a separate TextBlock , otherwise we will pretend to be a normal TextBox .

That is, somewhere inside our style will be:
< TextBlock x:Name ="WatermarkText" Text ="{TemplateBinding WatermarkText}" Foreground ="Gray" Margin ="5,0,0,0" HorizontalAlignment ="Left" VerticalAlignment ="Center" Visibility ="Collapsed" IsHitTestVisible ="False" />

* This source code was highlighted with Source Code Highlighter .
I added a few beauties in the form of indents and colors to make the effort more noticeable.
')
And for him it will be possible to write the following triggers:
< MultiTrigger.Conditions >
< Condition Property ="IsKeyboardFocusWithin" Value ="False" />
< Condition Property ="Text" Value ="" />
</ MultiTrigger.Conditions >
< Setter Property ="Visibility" TargetName ="WatermarkText" Value ="Visible" />
</ MultiTrigger >
< MultiTrigger >
< MultiTrigger.Conditions >
< Condition Property ="IsKeyboardFocusWithin" Value ="False" />
< Condition Property ="Text" Value ="{x:Null}" />
</ MultiTrigger.Conditions >
< Setter Property ="Visibility" TargetName ="WatermarkText" Value ="Visible" />
</ MultiTrigger >


* This source code was highlighted with Source Code Highlighter .
They will provide us with a text of the tooltip at a time when the value in the text field is missing and the field is in a state other than the input state. Unfortunately, you have to write two almost identical triggers so that string .Empty and null are processed equally well.
So, all the component parts are available, it remains to integrate them. There is nothing complicated about it.
< Style TargetType ="{x:Type WatermarkedTextBox:WatermarkedTextBox}" BasedOn ="{StaticResource {x:Type TextBox}}" >
< Setter Property ="Template" >
< Setter.Value >
< ControlTemplate TargetType ="{x:Type WatermarkedTextBox:WatermarkedTextBox}" >
< Grid >
< ScrollViewer x:Name ="PART_ContentHost" />
< TextBlock x:Name ="WatermarkText" Text ="{TemplateBinding WatermarkText}" Foreground ="Gray" Margin ="5,0,0,0" HorizontalAlignment ="Left" VerticalAlignment ="Center" Visibility ="Collapsed" IsHitTestVisible ="False" />
</ Grid >
< ControlTemplate.Triggers >
< MultiTrigger >
< MultiTrigger.Conditions >
< Condition Property ="IsKeyboardFocusWithin" Value ="False" />
< Condition Property ="Text" Value ="" />
</ MultiTrigger.Conditions >
< Setter Property ="Visibility" TargetName ="WatermarkText" Value ="Visible" />
</ MultiTrigger >
< MultiTrigger >
< MultiTrigger.Conditions >
< Condition Property ="IsKeyboardFocusWithin" Value ="False" />
< Condition Property ="Text" Value ="{x:Null}" />
</ MultiTrigger.Conditions >
< Setter Property ="Visibility" TargetName ="WatermarkText" Value ="Visible" />
</ MultiTrigger >
</ ControlTemplate.Triggers >
</ ControlTemplate >
</ Setter.Value >
</ Setter >
</ Style >


* This source code was highlighted with Source Code Highlighter .
Neatly connected all the parts, added to the style of the page. Theoretically, you can already shout cheers and stomp your feet in ecstasy, but if you run the application, it turns out that the frame is somewhere gone. We will try to restore this injustice by wrapping the grid.
< Style TargetType ="{x:Type WatermarkedTextBox:WatermarkedTextBox}" BasedOn ="{StaticResource {x:Type TextBox}}" >
< Setter Property ="Template" >
< Setter.Value >
< ControlTemplate TargetType ="{x:Type WatermarkedTextBox:WatermarkedTextBox}" >
< Border Background ="{TemplateBinding Background}" BorderBrush ="{TemplateBinding BorderBrush}" BorderThickness ="{TemplateBinding BorderThickness}" >
< Grid >
< ScrollViewer x:Name ="PART_ContentHost" />
< TextBlock x:Name ="WatermarkText" Text ="{TemplateBinding WatermarkText}" Foreground ="Gray" Margin ="5,0,0,0" HorizontalAlignment ="Left" VerticalAlignment ="Center" Visibility ="Collapsed" IsHitTestVisible ="False" />
</ Grid >
</ Border >
< ControlTemplate.Triggers >
< MultiTrigger >
< MultiTrigger.Conditions >
< Condition Property ="IsKeyboardFocusWithin" Value ="False" />
< Condition Property ="Text" Value ="" />
</ MultiTrigger.Conditions >
< Setter Property ="Visibility" TargetName ="WatermarkText" Value ="Visible" />
</ MultiTrigger >
< MultiTrigger >
< MultiTrigger.Conditions >
< Condition Property ="IsKeyboardFocusWithin" Value ="False" />
< Condition Property ="Text" Value ="{x:Null}" />
</ MultiTrigger.Conditions >
< Setter Property ="Visibility" TargetName ="WatermarkText" Value ="Visible" />
</ MultiTrigger >
</ ControlTemplate.Triggers >
</ ControlTemplate >
</ Setter.Value >
</ Setter >
</ Style >

* This source code was highlighted with Source Code Highlighter .
Now the work is completed. You can enjoy the result. Or download a working example .

Source: https://habr.com/ru/post/71151/


All Articles