zoukankan      html  css  js  c++  java
  • WPF自定义控件之水印文本(密码)框

    首先来讲讲创建这个控件的初衷,一个让我很郁闷的问题。

    公司的客户端项目采用WPF+MVVM技术实现,在近期地推客户端的过程中遇到了一个很奇葩的问题:在登录界面点击密码框就会直接闪退,没有任何提示

    密码框是WPF原生的PasswordBox,这似乎没有什么不对。出现这个情况的一般是在xp系统(ghost的雨林木风版本或番茄花园),某些xp系统又不会出现。

    出现这个问题的原因是因为客户的系统缺少PasswordBox使用的某种默认的字体,网上有人说是times new roman,又或者其它某种字体。其实只要找到了这个字体,并在程序启动的时候安装这种字体可以解决。

    但我觉得,这个解决方案太麻烦。与其依赖PasswordBox使用的默认字体,不如重写PasswordBox,避免密码框字体的依赖。

    现在让我们来重写一个自己的带水印的文本(密码)框吧

    1.首先创建一个类,继承TextBox

     public class SJTextBox : TextBox
    

    2.指定依赖属性的实例重写基类型的元数据

     static SJTextBox()
     {
         DefaultStyleKeyProperty.OverrideMetadata(typeof(SJTextBox), new FrameworkPropertyMetadata(typeof(SJTextBox)));
     }
    

    3.定义依赖属性  

    public static DependencyProperty WaterRemarkProperty =
                DependencyProperty.Register("WaterRemark", typeof(string), typeof(SJTextBox));
    
            /// <summary>
            /// 水印文字 
            /// </summary>
            public string WaterRemark
            {
                get { return GetValue(WaterRemarkProperty).ToString(); }
                set { SetValue(WaterRemarkProperty, value); }
            }
    
            public static DependencyProperty BorderCornerRadiusProperty =
                DependencyProperty.Register("BorderCornerRadius", typeof(CornerRadius), typeof(SJTextBox));
    
            /// <summary>
            /// 边框角度
            /// </summary>
            public CornerRadius BorderCornerRadius
            {
                get { return (CornerRadius)GetValue(BorderCornerRadiusProperty); }
                set { SetValue(BorderCornerRadiusProperty, value); }
            }
    
            public static DependencyProperty IsPasswordBoxProperty =
                DependencyProperty.Register("IsPasswordBox", typeof(bool), typeof(SJTextBox), new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsPasswordBoxChnage)));
            
            /// <summary>
            /// 是否为密码框
            /// </summary>
            public bool IsPasswordBox
            {
                get { return (bool)GetValue(IsPasswordBoxProperty); }
                set { SetValue(IsPasswordBoxProperty, value); }
            }
    
            public static DependencyProperty PasswordCharProperty =
                DependencyProperty.Register("PasswordChar", typeof(char), typeof(SJTextBox), new FrameworkPropertyMetadata('●'));
    
            /// <summary>
            /// 替换明文的单个密码字符
            /// </summary>
            public char PasswordChar
            {
                get { return (char)GetValue(PasswordCharProperty); }
                set { SetValue(PasswordCharProperty, value); }
            }
    
            public static DependencyProperty PasswordStrProperty =
                DependencyProperty.Register("PasswordStr", typeof(string), typeof(SJTextBox), new FrameworkPropertyMetadata(string.Empty));
            /// <summary>
            /// 密码字符串
            /// </summary>
            public string PasswordStr
            {    
                get { return GetValue(PasswordStrProperty).ToString(); }
                set { SetValue(PasswordStrProperty, value); }
            }
    

    4.当设置为密码框时,监听TextChange事件,处理Text的变化,这是密码框的核心功能

            private static void OnIsPasswordBoxChnage(DependencyObject sender, DependencyPropertyChangedEventArgs e)
            {
                (sender as SJTextBox).SetEvent();
            }
    
            /// <summary>
            /// 定义TextChange事件
            /// </summary>
            private void SetEvent()
            {
                if (IsPasswordBox)
                    this.TextChanged += SJTextBox_TextChanged;
                else
                    this.TextChanged -= SJTextBox_TextChanged;
            }
    

    5.在TextChange事件中,处理Text为密码文,并将原字符记录给PasswordStr予以存储

     private void SJTextBox_TextChanged(object sender, TextChangedEventArgs e)
            {
                if (!IsResponseChange) //响应事件标识,替换字符时,不处理后续逻辑
                    return;
                Console.WriteLine(string.Format("------{0}------", e.Changes.Count));
                foreach (TextChange c in e.Changes)
                {
                    Console.WriteLine(string.Format("addLength:{0} removeLenth:{1} offSet:{2}", c.AddedLength, c.RemovedLength, c.Offset));
                    PasswordStr = PasswordStr.Remove(c.Offset, c.RemovedLength); //从密码文中根据本次Change对象的索引和长度删除对应个数的字符
                    PasswordStr = PasswordStr.Insert(c.Offset, Text.Substring(c.Offset, c.AddedLength));   //将Text新增的部分记录给密码文
                    lastOffset = c.Offset;
                }
                Console.WriteLine(PasswordStr);
                /*将文本转换为密码字符*/
                IsResponseChange = false;  //设置响应标识为不响应
                this.Text = ConvertToPasswordChar(Text.Length);  //将输入的字符替换为密码字符
                IsResponseChange = true;   //回复响应标识
                this.SelectionStart = lastOffset + 1; //设置光标索引
                Console.WriteLine(string.Format("SelectionStar:{0}", this.SelectionStart));
            }
    

     

         /// <summary>
            /// 按照指定的长度生成密码字符
            /// </summary>
            /// <param name="length"></param>
            /// <returns></returns>
            private string ConvertToPasswordChar(int length)
            {
                if (PasswordBuilder != null)
                    PasswordBuilder.Clear();
                else
                    PasswordBuilder = new StringBuilder();
                for (var i = 0; i < length; i++)
                    PasswordBuilder.Append(PasswordChar);
                return PasswordBuilder.ToString();
            }
    

     ConvertToPasswordChar()方法用于返回指定个数的密码字符,替换Text为密码文就是调用此方法传递Text的长度完成的

     6.如果用户设置了记住密码,密码文(PasswordStr)一开始就有值的话,别忘了在Load事件里事先替换一次明文

     private void SJTextBox_Loaded(object sender, RoutedEventArgs e)
            {
                if (IsPasswordBox)
                {
                    IsResponseChange = false;
                    this.Text = ConvertToPasswordChar(PasswordStr.Length);
                    IsResponseChange = true;
                }
            }
    

    7.代码逻辑部分已经完成,替换明文为密码字符的功能已经实现了。自定义边框角度,水印功能,需要借助Style来完成,让我们来为它写一个Style

    <Style TargetType="{x:Type local:SJTextBox}">
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="VerticalContentAlignment" Value="Center"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="BorderBrush" Value="Gray"/>
            <Setter Property="Cursor" Value="IBeam"/>
            <Setter Property="Padding" Value="3,0,0,0"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type local:SJTextBox}">
                        <Border x:Name="border" 
                                    BorderBrush="{TemplateBinding BorderBrush}"
                                    BorderThickness="{TemplateBinding BorderThickness}" 
                                    Background="{TemplateBinding Background}" 
                                    CornerRadius="{TemplateBinding BorderCornerRadius}"  <!--绑定自定义边框角度-->
                                    SnapsToDevicePixels="True">
                            <Grid>
                                <ScrollViewer x:Name="PART_ContentHost" Focusable="False" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
                                <TextBlock x:Name="txtRemark" Text="{TemplateBinding WaterRemark}"  <!--绑定水印文字-->
                                               Foreground="Gray" VerticalAlignment="Center"
                                               Margin="{TemplateBinding Padding}"
                                               Visibility="Collapsed"/> <!--默认水印文字隐藏-->
                            </Grid>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="Text" Value="">  <!--使用触发器来控制水印的隐藏显示:当文本框没有字符时显示水印文字-->
                                <Setter Property="Visibility" Value="Visible" TargetName="txtRemark"/> 
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    

    8.自定义水印文本(密码)框的调用

            <local:SJTextBox  Height="30" 
                    BorderCornerRadius="3" 
                    Margin="10,10" 
                    Background="White" 
                    WaterRemark="This is a TextBox"/>  <!--水印文本框-->
    
                <local:SJTextBox Height="30" 
                    BorderCornerRadius="3" 
                    Margin="10,0" 
                    Background="White" 
                    WaterRemark="This is a PassworBox"
                    IsPasswordBox="True"
                    PasswordStr="{Binding Password}"/>  <!--水印密码框-->
    

      

    自定义的水印文本(密码)框已经完成了,它避免了对密码字体的依赖,同时密码文属性PasswordStr也支持数据绑定,非常方便。

    效果图:

  • 相关阅读:
    [蓝桥杯2017初赛]青蛙跳杯子 BFS
    第十一章 进程和信号
    第七章 数据管理
    特殊符号大全
    第四章 Linux环境
    (十六)异常
    (十五)代理
    (十四)内部类
    第三章 文件操作
    (十三)对象克隆
  • 原文地址:https://www.cnblogs.com/ShenNan/p/4937924.html
Copyright © 2011-2022 走看看