带清空按钮TextBox的实现(WPF)

摘要:

本博文针对人群:WPF新手。
博文内容:通过Style制定包含清空Button的TextBox样式模板,通过在Style中引入自定义类的附加属性完成对TextBox的内容清空。
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">博文目的:帮助刚入门的WPF开发人员或有需要的人快速了解该样式的实现。由于本人水平有限,难免有不足的地方,欢迎请指正,共同进步!</span>

正题:

带清空按钮TextBox的实现(WPF)分为两部分:样式模板(Style)部分和附加属性类(TextBoxHelper)部分。

一、样式模板(Style)

我们先定义Style,目标类型:TextBox

下面针对TextBox的Template部分详细说明:

关键代码如下(只保留核心属性设置)

<ControlTemplate TargetType="{x:Type TextBox}">
                    <Border >
                        <DockPanel LastChildFill="True">
                            <Button x:Name="Part_ClearButton" DockPanel.Dock="Right"  Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}" />
                            <ScrollViewer x:Name="PART_ContentHost" DockPanel.Dock="Left" />
                        </DockPanel>
                    </Border>
                    <ControlTemplate.Triggers>
                        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="">
                            <Setter TargetName="Part_ClearButton" Property="Visibility" Value="Collapsed" />
                        </DataTrigger>
                        ................................省略
                    </ControlTemplate.Triggers>
                </ControlTemplate>

为了方便直观理解,下图为样式模板(ControlTemplate)的示意图。


关于ControlTemplate相信大家都有个大概了解了,下面针对其中容易出错的地方补充几点说明:

1.关于最外层的Border对于它的 BorderBrush、BorderThickness、 SnapsToDevicePixels、 Background最好都不要自己设定尽量都使用TemplateBinding绑定到应用模板的TextBox上。原因嘛,当然是为了留给用户使用时尽量多的对于TextBox外观自主权,总不能说用了这个Style后边框、背景都不能设定吧。

2.对于DockPanel,其属性LastChildFill="True",本身默认值即为“True”,可以省略不写,但是一定要知道。

3.关于DockPanel中ScrollViewer和Button的布局,一定要先定义Button,后定义ScrollViewer。原因:就是因为第2条的LastChildFill="True"属性,如果先定义ScrollViewer,后定义Button会出现什么效果,大家可以自己试试看。

4.对于ScrollViewer 它的名字一定要是"PART_ContentHost" 否则将Windows将无法识别导致不能显示文字。

5.对于Button为了是Button显示正方形所以Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}" 将自己的宽绑定到了实际显示高度上。最好将Focusable设为False。原因:防止输入时点击清空按钮导致ScrollViewer失去焦点,具体表现为点击清空按钮,输入框的光标不闪烁了。FontSize尽量TemplateBinding到应用模板的目标控件上。

将Style重构提取,最后放入资源词典以供重用。

提炼后的Style

    <!--获得焦点后边框颜色-->
    <SolidColorBrush x:Key="FocusedBorderBrush" Color="Black"/>
    <!--鼠标移上时背景色-->
    <SolidColorBrush x:Key="MouseOverBackground" Color="LightGray"/>
    
    <!--清空按钮模板样式-->
    <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>
    
    <!--带有清空按钮的TextBox风格-->
    <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>

二、附加属性类

为什么不采用Trigger置空Text实现而要采用附加属性类?
以下为原因,不关心的可以跳过...
由于在Style中通过Trigger来设置TextBox的Text属性为“”暂时存在缺陷:
当TextBox定义时如果设置Text属性将导致Trigger将Text置空失效,详细描述如下
使用Trigger置空Text原理,我们在模板触发器中添加数据触发器
<DataTrigger Binding="{Binding IsPressed,ElementName=Part_ClearButton}" Value="True">
                        <Setter Property="Text" Value=""/>
                    </DataTrigger>

当清空按钮按下时,触发
<Setter Property="Text" Value=""/>

但是如果定义TextBox时
<TextBox Text="{Binding xxx}"
                 Visibility="Collapsed"
                 Height="24"
                 Width="250"
                 Style="{DynamicResource ClearButtonTextBoxStyle}"
                 >
        </TextBox>
<TextBox Text="xxx"
                 Visibility="Collapsed"
                 Height="24"
                 Width="250"
                 Style="{DynamicResource ClearButtonTextBoxStyle}"
                 >
        </TextBox>
此时,风格中 的触发器将失效,无法清空文本内容,经尝试,Trigger、DataTrigger均会失效,而且"Text"属性不支持StoryBorad也无法通过故事板清空。

使用附加属性清空文本内容
原理,通过在Style的ControlTemplate中 的清空按钮 Part_ClearButton
加上自定义的附加属性,在附加属性改变的回调函数中获得依赖对象,该对象即为我们定义的 Part_ClearButton
,获得按钮后为它的Click挂上我们的自定义事件,这样当点击清空按钮时,即可执行我们的自定义事件,只需要在事件里通过VisualTreeHelper.GetParent向上获取到应用模板的TextBox,然后通过TextBox.Clear()清空内容即可.
下面为代码,比较简单
public class TextBoxHelper
    {
        #region 附加属性 IsClearButton
        /// <summary>
        /// 附加属性,是否带清空按钮
        /// </summary>
        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

        #region 回调函数和清空输入框内容的实现
        /// <summary>
        /// 回调函数若附加属性IsClearButton值为True则挂载清空TextBox内容的函数
        /// </summary>
        /// <param name="d">属性所属依赖对象</param>
        /// <param name="e">属性改变事件参数</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>
        /// 清空应用该附加属性的父TextBox内容函数
        /// </summary>
        /// <param name="sender">发送对象</param>
        /// <param name="e">路由事件参数</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
    }
最后我们在Style所在地引入TextBoxHelper命名空间,在Style的ControlTemplate里的按钮加上改附加属性即可
<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>

总结

经过以上博文,相信大家都理解带清空按钮TextBox的实现,当然这只是一个开始!最后把源代码上传,分享给大家,有什么不足的地方欢迎指正,谢谢!
我们的目标,帮助自己,帮助别人。
  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值