WPF自定義控件之水印文本(密碼)框

首先來說講建立這個控件的初衷,一個讓我很鬱悶的問題。html

公司的客戶端項目採用WPF+MVVM技術實現,在近期地推客戶端的過程當中遇到了一個很奇葩的問題:在登陸界面點擊密碼框就會直接閃退,沒有任何提示ide

密碼框是WPF原生的PasswordBox,這彷佛沒有什麼不對。出現這個狀況的通常是在xp系統(ghost的雨林木風版本或番茄花園),某些xp系統又不會出現。字體

出現這個問題的緣由是由於客戶的系統缺乏PasswordBox使用的某種默認的字體,網上有人說是times new roman,又或者其它某種字體。其實只要找到了這個字體,並在程序啓動的時候安裝這種字體能夠解決。ui

但我以爲,這個解決方案太麻煩。與其依賴PasswordBox使用的默認字體,不如重寫PasswordBox,避免密碼框字體的依賴。this

 

如今讓咱們來重寫一個本身的帶水印的文本(密碼)框吧spa

1.首先建立一個類,繼承TextBoxorm

 public class SJTextBox : TextBox

2.指定依賴屬性的實例重寫基類型的元數據htm

 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的變化,這是密碼框的核心功能blog

        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也支持數據綁定,很是方便。

效果圖:

相關文章
相關標籤/搜索