【WPF实用教程3】支持水印带清空功能的输入框

1. 前言:

本篇内容基于上一篇文章【WPF实用教程2】(点此跳转)创建的解决方案进行演示。

这一篇章通过自定义一个具有清空功能的输入框,带大家进入WPF自定义控件开发的内容,重在演示创建方法,其中涉及到的一些理论知识会简单提及,不会重点解释,后续会在进阶篇中讲解。

工程最后效果图:

2. 工程相关内容清理

在进行演示前,先把工程内的内容清理一下,如下图

将MainWindow.xaml中的第4行、第9行到12删掉,删掉后如下图

将MicroUI.Wpf.Toolkit项目中的CustomControl.cs删除

将Themes\Generic.xaml文件中的第5到17行删掉

清理后的MicroUI.Wpf.Toolkit项目内容如下:

好了,这下清理的很干净了,可以重新生成解决方案并运行一下,看看此时的解决方案有没有问题:

我的没有问题,下面可以进行演示了。

3. 自定义控件工程

3.1 新建自定义控件

鼠标右击MicroUI.Wpf.Toolkit项目,依次选择“添加”\“新建项”

按照箭头顺序依次选择“WPF”\“自定义控件(WPF)”,名字为ClearTextBox.cs,然后点击“添加”按钮。

此时,在项目中增加了ClearTextBox.cs和 Generic.xaml中增加了其默认的样式:

为了方便管理控件,我以后把每个控件的内容都放在各自的文件夹里,所以再新建一个ClearTextBox文件夹:

然后把ClearTextBox.cs拖拽到新建的ClearTextBox文件夹:

然后,在ClearTextBox文件夹内新建一个资源文件:

资源文件命名为Themes.xaml

添加后如下:

将ClearTextBox\Themes.xaml内容修改如下:


<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MicroUI.Wpf.Toolkit">

    <Style TargetType="{x:Type local:ClearTextBox}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ClearTextBox}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
</Style>
    
</ResourceDictionary>

将Themes\Generic.xaml代码修改如下:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MicroUI.Wpf.Toolkit">


    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/MicroUI.Wpf.Toolkit;component/ClearTextBox/Themes.xaml" />

    </ResourceDictionary.MergedDictionaries>

</ResourceDictionary>

然后修改MicroUI.Wpf.Samples项目中的MainWindow.xaml内容如下:


<Window x:Class="MicroUI.Wpf.Samples.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:microui="clr-namespace:MicroUI.Wpf.Toolkit;assembly=MicroUI.Wpf.Toolkit"
        Title="MainWindow" Height="350" Width="525">

    <Grid>
        <microui:ClearTextBox Width="100" Height="30" 
                          VerticalAlignment="Center"
                          HorizontalAlignment="Center" />
    </Grid>

</Window>

运行:

啥都没有。下面开始修改控件了。

3.2 修改自定义控件

将ClearTextBox继承的Control换成TextBox,如下图:

然后修改ClearTextBox\Themes.xaml内容如下:


<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MicroUI.Wpf.Toolkit">

    <Style TargetType="{x:Type local:ClearTextBox}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ClearTextBox}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <Grid>
                            <!--文本内容区域-->
                            <ScrollViewer x:Name="PART_ContentHost"/>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
</Style>
    
</ResourceDictionary>

可以看到实际上只是加入了以下内容:

然后修改MainWindow.xaml内容如下:

由于ClearTextBox继承了TextBox,所以可以设置Text属性了,运行:

可以看到运行有效果。注意我在ClearBox\Themes.xaml中加的这句话:

这句话看似很简单,其实其中的PART_***这个名字就有很多的信息量,但是我这里一笔带过,即:

ScrollViewer的“PART_ContentHost”名称,是WPF内置的特殊名称,在本控件中,表示这个ScrollViewer是用于装载TextBox的文本内容的。PART_ContentHost这个名称只能用于ScrollViewer或者Adorner、Decorator控件。

接下来,我们要加一个清除的按钮:

3.3 添加清除按钮

重新修改一下ClearBox\Themes.xaml文件内容如下:

然后生成解决方案,重新运行:

可以看到已经有了一个按钮。样式还是比较丑的,先不管样式,实现功能再说。

3.4 给按钮添加清除功能

可以看到我已经在ClearBox\Themes.xaml中给清除按钮赋了一个名字PART_ClearButtonHost

接下来我就再修改一下ClearTextBox.cs,修改的内容如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace MicroUI.Wpf.Toolkit
{

    [TemplatePart(Name = "PART_ClearButtonHost", Type = typeof(ButtonBase))]
    public class ClearTextBox : TextBox
    {
        private ButtonBase clearButtonHost;
        static ClearTextBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ClearTextBox), new FrameworkPropertyMetadata(typeof(ClearTextBox)));
        }

        #region  继承事件
        //模板化控件在加载ControlTemplate后会调用OnApplyTemplate
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            if (clearButtonHost != null)
            {
                clearButtonHost.Click -= HandleClearButtonClick;
            }
            clearButtonHost = GetTemplateChild("PART_ClearButtonHost") as ButtonBase;
            if (clearButtonHost != null)
            {
                clearButtonHost.Click += HandleClearButtonClick;
            }

        }

        #endregion

        #region 方法
        //清除事件
        private void HandleClearButtonClick(object sender, RoutedEventArgs e)
        {
            Reset();
        }

        public void Reset()
        {
            Text = String.Empty;

        }
        #endregion

    }

}

注意:不要在Loaded事件中尝试调用GetTemplateChild,因为Loaded在OnApplyTemplate前调用,而且Loaded更容易被多次触发。由于Template可能多次加载,或者不能正确获取TemplatePart,所以使用TemplatePart前应该先判断是否为空;如果要订阅事件,应该先取消订阅。

完整的GetTemplateChild的步骤如下:

  • 取消订阅TemplatePart事件

  • 将TemplatePart存储到私有字段

  • 订阅TemplatePart事件

更详细的关于TemplatePart的意义暂时不解释,先会用,然后再去深究意义。

重新生成解决方案,运行程序:

可以看到,已经运行了。

到这里还没有完事,我们希望如果有数据了就显示清除按钮,没有数据就隐藏清除按钮,接下来我们就实现这个需求。

3.5 判断数据是否为空

接下来,我们再次修改ClearTextBox.cs,给其增加一个IsHasText的属性。

然后重载OnTextChanged()事件,

此时完整的代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace MicroUI.Wpf.Toolkit
{

    [TemplatePart(Name = "PART_ClearButtonHost", Type = typeof(ButtonBase))]
    public class ClearTextBox : TextBox
    {
        private ButtonBase clearButtonHost;
        static ClearTextBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ClearTextBox), new FrameworkPropertyMetadata(typeof(ClearTextBox)));
        }

        #region 输入框内是否有数据
        [Browsable(false)]
        public bool IsHasText
        {
            get { return (bool)GetValue(IsHasTextProperty); }
            private set { SetValue(IsHasTextPropertyKey, value); }
        }
        private static readonly DependencyPropertyKey IsHasTextPropertyKey =
            DependencyProperty.RegisterReadOnly(
                "IsHasText",
                typeof(Boolean),
                typeof(ClearTextBox),
                new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
        public static readonly DependencyProperty IsHasTextProperty =
            IsHasTextPropertyKey.DependencyProperty;


        #endregion

        #region  继承事件
        protected override void OnTextChanged(TextChangedEventArgs e)
        {
            base.OnTextChanged(e);
            IsHasText = !String.IsNullOrEmpty(Text);
        }

        //模板化控件在加载ControlTemplate后会调用OnApplyTemplate
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            if (clearButtonHost != null)
            {
                clearButtonHost.Click -= HandleClearButtonClick;
            }
            clearButtonHost = GetTemplateChild("PART_ClearButtonHost") as ButtonBase;
            if (clearButtonHost != null)
            {
                clearButtonHost.Click += HandleClearButtonClick;
            }

        }

        #endregion

        #region 方法
        //清除事件
        private void HandleClearButtonClick(object sender, RoutedEventArgs e)
        {
            Reset();
        }

        public void Reset()
        {
            Text = String.Empty;

        }
        #endregion

    }

}

然后我们要根据IsHasText属性,使用样式的触发来完成清除按钮的显示与否。修改ClearTextBox\Themes.xaml内容如下:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MicroUI.Wpf.Toolkit">

    <Style TargetType="{x:Type local:ClearTextBox}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ClearTextBox}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto"/>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="Auto"/>
                            </Grid.ColumnDefinitions>
                            
                            <!--文本内容区域-->
                            <ScrollViewer x:Name="PART_ContentHost" Grid.Column="1" VerticalAlignment="Center"/>
                            <!--清除按钮-->
                            <Button x:Name="PART_ClearButtonHost" Grid.Column="2" 
                                    Padding="2,0" IsHitTestVisible="False" Visibility="Collapsed"
                                    Content="x"                                     
                                    />
                        </Grid>
                    </Border>
                    <ControlTemplate.Triggers>
                        
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="UIElement.IsEnabled" Value="True"/>
                                <Condition Property="IsHasText" Value="True"/>
                            </MultiTrigger.Conditions>
                            <MultiTrigger.Setters>
                                <Setter TargetName="PART_ClearButtonHost" Property="IsHitTestVisible" Value="True"/>
                                <Setter TargetName="PART_ClearButtonHost" Property="Visibility" Value="Visible"/>
                            </MultiTrigger.Setters>
                        </MultiTrigger>
                        
                    </ControlTemplate.Triggers>

                    
                </ControlTemplate>
            </Setter.Value>
        </Setter>
</Style>
    
</ResourceDictionary>

可以看到,我首先设置PART_ClearButtonHost初始状态不可见:

然后给模板添加了一个多条件的触发:

触发条件为:控件使能且IsHastText为True时,清除按钮显示且可点击。

然后我们重新生成解决方案,运行:

基本清除功能已经完成了,但是这个样式呢,略微丑一点,接下来我们美化一下清除按钮的样式。

3.6 使用iconfont美化按钮样式

这一节内容涉及到文章WPF实用教程1-使用Iconfont图标字体,建议先去看下这篇内容

下载项目文件,提取出iconfont.ttf到Res目录

MicroUI.Wpf.Toolkit项目新增Fonts目录,如下图:

将iconfont.ttf导入到Fonts目录下:

MicroUI.Wpf.Toolkit项目新增Style目录,如下图:

在Style目录下新增资源字典,文件名为MicroIcon.xaml:

修改MicroIcon.xaml内容如下:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Style x:Key="MicroBtnIcon" TargetType="{x:Type Button}">
        <Setter Property="FontFamily" Value="/MicroUI.Wpf.Toolkit;component/Fonts/#iconfont"></Setter>
        <Setter Property="HorizontalAlignment" Value="Center"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="Margin" Value="3" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Border BorderBrush="{TemplateBinding Control.BorderBrush}" BorderThickness="0">
                        <ContentPresenter Content="{TemplateBinding ContentControl.Content}" 
                                          HorizontalAlignment="Center" VerticalAlignment="Center" />
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter Property="Opacity" Value="0.8" />
                        </Trigger>
                        <Trigger Property="IsPressed" Value="true">
                            <Setter Property="Opacity" Value="0.7" />
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>

            </Setter.Value>
        </Setter>
</Style>
    <Style x:Key="MicroBtnIcon_Close" TargetType="{x:Type Button}" BasedOn="{StaticResource MicroBtnIcon}" >
        <Setter Property="Content" Value="&#xe677;" />
</Style>

</ResourceDictionary>

然后在ClearTextBox\Themes.xaml中添加如下三句话:

同时修改清除按钮内容如下:

重新生成解决方案,再次运行:

可以看到此时样式已经不错了。

3.7 添加水印

接下来,我准备再加上一个水印。编辑ClearTextBox.cs,在其中添加如下几句话:

#region 水印
[Category("Extend Properties")]
public String WaterMark
{
  get { return (String)GetValue(WaterMarkProperty); }
   set { SetValue(WaterMarkProperty, value); }
}

public static readonly DependencyProperty WaterMarkProperty =
  DependencyProperty.Register("WaterMark", typeof(String), typeof(ClearTextBox), new PropertyMetadata(String.Empty));
#endregion

在ClearTextBox\Themes.xaml中添加如下几句话:


 <!--水印区域-->
 <Label x:Name="watermark" Grid.Column="1"
    Content="{TemplateBinding WaterMark}"
    VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
    HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
    IsHitTestVisible="False"
    Foreground="#CCCCCC"
    Margin="{TemplateBinding Padding}"
    Visibility="Collapsed"  />

以及添加如下几句话:

<MultiTrigger>
  <MultiTrigger.Conditions>
    <Condition Property="UIElement.IsEnabled" Value="True"/>
    <Condition Property="UIElement.IsFocused" Value="False"/>
    <Condition Property="TextBox.Text" Value=""/>
  </MultiTrigger.Conditions>
  <MultiTrigger.Setters>
    <Setter TargetName="watermark" Property="Visibility" Value="Visible"/>
  </MultiTrigger.Setters>
</MultiTrigger>

修改MicroUI.Wpf.Samples项目的MainWindow.xaml内容如下:

<Window x:Class="MicroUI.Wpf.Samples.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:microui="clr-namespace:MicroUI.Wpf.Toolkit;assembly=MicroUI.Wpf.Toolkit"
        Title="MainWindow" Height="350" Width="525">

    <Grid>
        <StackPanel Orientation="Vertical" VerticalAlignment="Center">
            <microui:ClearTextBox Width="200" Height="30" 
                          VerticalAlignment="Center"
                          HorizontalAlignment="Center"
                          BorderBrush="#dddddd"
                          BorderThickness="1"
                          WaterMark="这里是水印"
                          Text="公众号:BigBearIT"/>
            <TextBox Text="这里是普通的TextBox" 
                     HorizontalAlignment="Center" 
                     VerticalContentAlignment="Center"
                     Width="200" Height="30" Margin="0,10" />
        </StackPanel>
    </Grid>
</Window>

重新生成解决方案,运行程序:

3.8 小节

本篇通过创建一个带水印支持清除功能的输入框,演示了自定义控件的创建流程,建议大家先自己了解一下依赖属性、TemplatePart,我也会在后续合适的时机重点讲述一下这些。

/////////////////////////////////////////////////////////////////////////////////////////

** 原创文章,转载请附该部分声明

** 来源:https://blog.csdn.net/mybelief321

** 作者:玖零大壮

/////////////////////////////////////////////////////////////////////////////////////////

发布了143 篇原创文章 · 获赞 161 · 访问量 121万+

猜你喜欢

转载自blog.csdn.net/mybelief321/article/details/102741177