Implementation of TextBox with empty button (WPF)

Keywords: Attribute Windows

Abstract:
This blog post is for the crowd: WPF novice.
Blog content: make TextBox Style template including empty Button through Style, and empty TextBox content by introducing additional attributes of custom class into Style.
Purpose of the blog: to help new WPF developers or those in need to quickly understand the implementation of this style. Due to my limited level, there are inevitably deficiencies. Please correct and make progress together!
Topic:
The implementation of TextBox with empty button (WPF) is divided into two parts: Style part and TextBoxHelper part.

1, Style template
Let's define Style first. Target type: TextBox

The Template part of TextBox is described in detail as follows:

The key code is as follows (only the core property settings are kept)

I'm sure you have a general understanding of ControlTemplate. Here are some additional explanations for the error prone places:

1. For the outermost Border, for its borders brush, BorderThickness, SnapsToDevicePixels, and Background, it's better not to use TemplateBinding to bind to the TextBox of the application template. The reason is, of course, to leave users as much autonomy as possible on the appearance of TextBox. It can't be said that the back frame and Background of this Style can't be set.

2. For DockPanel, its attribute LastChildFill = "True", which is the default value of "True", can be omitted, but it must be known.

3. For the layout of ScrollViewer and Button in dockchannel, it is necessary to define Button first and then ScrollViewer. Reason: because of the LastChildFill="True" attribute in Item 2, if you define the ScrollViewer first and then the Button, you can try it yourself.

4. For ScrollViewer, its name must be "part" contenthost. Otherwise, Windows will not recognize it and the text will not be displayed.

5. For Button, in order to display the square of Button, width "{binding ActualHeight, relativesource = {relativesource self}" binds its width to the actual display height. It is best to set Focusable to False. Cause: to prevent ScrollViewer from losing focus when clicking the clear Button during input, specifically, clicking the clear Button does not blink the cursor of the input box. FontSize try to use TemplateBinding on the target control where the template is applied.

Extract the Style refactoring and put it into the resource dictionary for reuse.

Refined Style

<!--Get focus back border color-->
<SolidColorBrush x:Key="FocusedBorderBrush" Color="Black"/>
<!--Background color on mouse up-->
<SolidColorBrush x:Key="MouseOverBackground" Color="LightGray"/>

<!--Empty button template style-->
<ControlTemplate x:Key="ClearButtonTemplate" TargetType="Button">
        <Grid>
            <Rectangle x:Name="rctButton" Fill="Transparent" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></Rectangle>
            <ContentPresenter Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}" 
                              HorizontalAlignment="Center" 
                              VerticalAlignment="Center">
            </ContentPresenter>
        </Grid>
        <ControlTemplate.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter TargetName="rctButton" Property="Fill" Value="{DynamicResource MouseOverBackground}"/>
            </Trigger>
        </ControlTemplate.Triggers>
</ControlTemplate>

<!--With clear button TextBox style-->
<Style x:Key="ClearButtonTextBoxStyle" TargetType="{x:Type TextBox}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TextBox}">
                <Border x:Name="bdRoot" 
                        BorderBrush="{TemplateBinding BorderBrush}" 
                        BorderThickness="{TemplateBinding BorderThickness}" 
                        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
                        Background="{TemplateBinding Background}">
                    <DockPanel LastChildFill="True">
                        <Button x:Name="Part_ClearButton" 
                                UC:TextBoxHelper.IsClearButton="True"
                                Content="X" 
                                DockPanel.Dock="Right" 
                                Focusable="False"
                                Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}" 
                                Template="{DynamicResource ClearButtonTemplate}"
                                FontSize="{TemplateBinding FontSize}">
                        </Button>
                        <ScrollViewer x:Name="PART_ContentHost" DockPanel.Dock="Left" Background="{TemplateBinding Background}"/>
                    </DockPanel>
                </Border>
                <ControlTemplate.Triggers>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="">
                        <Setter TargetName="Part_ClearButton" Property="Visibility" Value="Collapsed" />
                    </DataTrigger>
                    <Trigger Property="IsFocused" Value="True">
                        <Setter TargetName="bdRoot" Property="BorderBrush" Value="{DynamicResource FocusedBorderBrush}"/>
                    </Trigger>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="bdRoot" Property="BorderBrush" Value="{DynamicResource FocusedBorderBrush}"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

2, Additional attribute class
Why not use Trigger empty Text instead of additional attribute classes?
For the following reasons, don't care can skip
Because the Text property of TextBox is set to "" through Trigger in Style, there is a temporary defect:
When TextBox is defined, setting the Text property will cause Trigger to empty the Text, which is described in detail as follows
Using Trigger null Text principle, we add data Trigger to template Trigger


Triggered when the clear button is pressed

But if you define a TextBox


or


At this time, the Trigger in the style will fail and the Text content cannot be cleared. After an attempt, the Trigger and DataTrigger will fail, and the "Text" property does not support StoryBorad and cannot be cleared through the storyboard.

Empty text content with additional properties
Principle, through the clear button in the ControlTemplate of Style
In addition to the custom additional attributes, the dependent object is obtained in the callback function with the change of the additional attributes, which is the part ﹣ clearbutton defined by us
, after getting the button, hang our custom event for its Click, so that when you Click the clear button, you can execute our custom event, just get the TextBox of the application template through VisualTreeHelper.GetParent in the event, and then clear the content through TextBox.Clear()
Here is the code, which is relatively simple
public class TextBoxHelper
{
#region additional attribute IsClearButton
///
///Additional attribute, with clear button or not
///
public static readonly DependencyProperty IsClearButtonProperty =
DependencyProperty.RegisterAttached("IsClearButton", typeof(bool), typeof(TextBoxHelper), new PropertyMetadata(false, ClearText));

    public static bool GetIsClearButton(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsClearButtonProperty);
    }

    public static void SetIsClearButton(DependencyObject obj, bool value)
    {
        obj.SetValue(IsClearButtonProperty, value);
    }

    #endregion

    #The implementation of region callback function and clearing the content of input box
    /// <summary>
    ///Callback function if the value of the additional property IsClearButton is True, the function to empty TextBox content will be mounted
    /// </summary>
    ///< param name = "d" > the dependent object to which the attribute belongs < / param >
    ///< param name = "e" > attribute change event parameter < / param >
    private static void ClearText(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Button btn = d as Button;
        if (d != null && e.OldValue != e.NewValue)
        {
            btn.Click -= ClearTextClicked;
            if ((bool)e.NewValue)
            {
                btn.Click += ClearTextClicked;
            }
        }
    }

    /// <summary>
    ///Clear the parent TextBox content function to which this additional attribute is applied
    /// </summary>
    ///< param name = "sender" > send object < / param >
    ///< param name = "e" > route event parameters < / param >
    public static void ClearTextClicked(object sender, RoutedEventArgs e)
    {
        Button btn = sender as Button;
        if (btn != null)
        {
            var parent = VisualTreeHelper.GetParent(btn);
            while (!(parent is TextBox))
            {
                parent = VisualTreeHelper.GetParent(parent);
            }
            TextBox txt = parent as TextBox;
            if (txt != null)
            {
                txt.Clear();
            }
        }
    }

    #endregion
}

Finally, we introduce the TextBoxHelper namespace where the Style is located, and add the button in the ControlTemplate of the Style and change the additional attribute

Published 2 original articles, won praise 28, visited 40000+
Private letter follow

Posted by aldernon on Tue, 14 Jan 2020 21:35:21 -0800