1. 程式人生 > >【轉】WPF自定義控制元件與樣式(3)-TextBox & RichTextBox & PasswordBox樣式、水印、Label標籤、功能擴充套件

【轉】WPF自定義控制元件與樣式(3)-TextBox & RichTextBox & PasswordBox樣式、水印、Label標籤、功能擴充套件

一.前言.預覽

  申明:WPF自定義控制元件與樣式是一個系列文章,前後是有些關聯的,但大多是按照由簡到繁的順序逐步釋出的等。

本文主要是對文字輸入控制元件進行樣式開發,及相關擴充套件功能開發,主要內容包括:

  • 基本文字框TextBox控制元件樣式及擴充套件功能,實現了樣式、水印、Label標籤、功能擴充套件;
  • 富文字框RichTextBox控制元件樣式;
  • 密碼輸入框PasswordBox控制元件樣式及擴充套件功能;

效果圖:

二.基本文字框TextBox控制元件樣式及擴充套件功能

2.1 TextBox基本樣式

樣式程式碼如下:  

<!--
TextBox預設樣式--> <Style TargetType="{x:Type TextBox}" x:Key="DefaultTextBox"> <Setter Property="ContextMenu" Value="{DynamicResource TextBoxContextMenu}" /> <Setter Property="SelectionBrush" Value="{StaticResource TextSelectionBrush}" /> <Setter Property="FontFamily" Value="
{StaticResource FontFamily}" /> <Setter Property="FontSize" Value="{StaticResource FontSize}" /> <Setter Property="BorderThickness" Value="1" /> <Setter Property="MinHeight" Value="26" /> <Setter Property="Width" Value="100" /> <Setter Property="Background" Value="
{StaticResource TextBackground}" /> <Setter Property="Foreground" Value="{StaticResource TextForeground}" /> <Setter Property="Padding" Value="0" /> <Setter Property="BorderBrush" Value="{StaticResource ControlBorderBrush}" /> <Setter Property="local:ControlAttachProperty.FocusBorderBrush" Value="{StaticResource FocusBorderBrush}" /> <Setter Property="local:ControlAttachProperty.MouseOverBorderBrush" Value="{StaticResource MouseOverBorderBrush}" /> <Setter Property="VerticalContentAlignment" Value="Center" /> <!-- change SnapsToDevicePixels to True to view a better border and validation error --> <Setter Property="SnapsToDevicePixels" Value="True" /> <!--英 ['kærət] 美 ['kærət] 插入符號--> <Setter Property="CaretBrush" Value="{StaticResource TextForeground}" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TextBox}"> <Grid x:Name="PART_Root"> <Border x:Name="Bg" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" CornerRadius="{TemplateBinding local:ControlAttachProperty.CornerRadius}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" /> <Grid x:Name="PART_InnerGrid"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <!--Label區域--> <ContentControl x:Name="Label" Margin="1" Template="{TemplateBinding local:ControlAttachProperty.LabelTemplate}" Content="{TemplateBinding local:ControlAttachProperty.Label}"/> <!--內容區域--> <ScrollViewer x:Name="PART_ContentHost" BorderThickness="0" Grid.Column="1" IsTabStop="False" Margin="2" VerticalAlignment="Stretch" Background="{x:Null}" /> <!--水印--> <TextBlock x:Name="Message" Padding="{TemplateBinding Padding}" Visibility="Collapsed" Text="{TemplateBinding local:ControlAttachProperty.Watermark}" Grid.Column="1" Foreground="{TemplateBinding Foreground}" IsHitTestVisible="False" Opacity="{StaticResource WatermarkOpacity}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Margin="5,2,5,2" /> <!--附加內容區域--> <Border x:Name="PART_AttachContent" Grid.Column="2" Margin="2" VerticalAlignment="Center" HorizontalAlignment="Center" > <ContentControl VerticalAlignment="Center" VerticalContentAlignment="Center" Template="{TemplateBinding local:ControlAttachProperty.AttachContent}" /> </Border> </Grid> </Grid> <ControlTemplate.Triggers> <!--顯示水印--> <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value=""> <Setter TargetName="Message" Property="Visibility" Value="Visible" /> </DataTrigger> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="BorderBrush" Value="{Binding Path=(local:ControlAttachProperty.MouseOverBorderBrush),RelativeSource={RelativeSource Self}}"/> </Trigger> <Trigger Property="IsFocused" Value="True"> <Setter Property="BorderBrush" Value="{Binding Path=(local:ControlAttachProperty.FocusBorderBrush),RelativeSource={RelativeSource Self}}"/> </Trigger> <!--不可用--> <Trigger Property="IsEnabled" Value="False"> <Setter TargetName="PART_Root" Property="Opacity" Value="{StaticResource DisableOpacity}" /> </Trigger> <!--只讀時,禁用PART_AttachContent--> <Trigger Property="IsReadOnly" Value="True"> <Setter TargetName="PART_AttachContent" Property="IsEnabled" Value="False" /> <Setter TargetName="Bg" Property="Opacity" Value="{StaticResource ReadonlyOpacity}" /> <Setter TargetName="PART_ContentHost" Property="Opacity" Value="{StaticResource ReadonlyOpacity}" /> <Setter TargetName="Label" Property="Opacity" Value="{StaticResource ReadonlyOpacity}" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>

模板內容主要包含四部分:

  • 用於實現Label標籤的預留區域;
  • TextBox本身的文字輸入顯示部分;
  • 水印顯示部分;
  • 功能擴充套件的預留區域;

  其中Label標籤、功能擴充套件,還有輸入框的不同狀態顯示效果都是通過附加屬性來實現的,其實從本質上附加屬性和控制元件上定義的依賴屬性是同一個概念,有些時候附加屬性會更加方便,對於一些可共用的屬性,就比較方便,這一點怎本文是有體現的。上面程式碼使用到的附加屬性程式碼:

#region FocusBorderBrush 焦點邊框色,輸入控制元件

        public static readonly DependencyProperty FocusBorderBrushProperty = DependencyProperty.RegisterAttached(
            "FocusBorderBrush", typeof(Brush), typeof(ControlAttachProperty), new FrameworkPropertyMetadata(null));
        public static void SetFocusBorderBrush(DependencyObject element, Brush value)
        {
            element.SetValue(FocusBorderBrushProperty, value);
        }
        public static Brush GetFocusBorderBrush(DependencyObject element)
        {
            return (Brush)element.GetValue(FocusBorderBrushProperty);
        }

        #endregion

        #region MouseOverBorderBrush 滑鼠進入邊框色,輸入控制元件

        public static readonly DependencyProperty MouseOverBorderBrushProperty =
            DependencyProperty.RegisterAttached("MouseOverBorderBrush", typeof(Brush), typeof(ControlAttachProperty),
                new FrameworkPropertyMetadata(Brushes.Transparent,
                    FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.Inherits));

        /// <summary>
        /// Sets the brush used to draw the mouse over brush.
        /// </summary>
        public static void SetMouseOverBorderBrush(DependencyObject obj, Brush value)
        {
            obj.SetValue(MouseOverBorderBrushProperty, value);
        }

        /// <summary>
        /// Gets the brush used to draw the mouse over brush.
        /// </summary>
        [AttachedPropertyBrowsableForType(typeof(TextBox))]
        [AttachedPropertyBrowsableForType(typeof(CheckBox))]
        [AttachedPropertyBrowsableForType(typeof(RadioButton))]
        [AttachedPropertyBrowsableForType(typeof(DatePicker))]
        [AttachedPropertyBrowsableForType(typeof(ComboBox))]
        [AttachedPropertyBrowsableForType(typeof(RichTextBox))]
        public static Brush GetMouseOverBorderBrush(DependencyObject obj)
        {
            return (Brush)obj.GetValue(MouseOverBorderBrushProperty);
        }

        #endregion

        #region AttachContentProperty 附加元件模板
        /// <summary>
        /// 附加元件模板
        /// </summary>
        public static readonly DependencyProperty AttachContentProperty = DependencyProperty.RegisterAttached(
            "AttachContent", typeof(ControlTemplate), typeof(ControlAttachProperty), new FrameworkPropertyMetadata(null));

        public static ControlTemplate GetAttachContent(DependencyObject d)
        {
            return (ControlTemplate)d.GetValue(AttachContentProperty);
        }

        public static void SetAttachContent(DependencyObject obj, ControlTemplate value)
        {
            obj.SetValue(AttachContentProperty, value);
        }
        #endregion

        #region WatermarkProperty 水印
        /// <summary>
        /// 水印
        /// </summary>
        public static readonly DependencyProperty WatermarkProperty = DependencyProperty.RegisterAttached(
            "Watermark", typeof(string), typeof(ControlAttachProperty), new FrameworkPropertyMetadata(""));

        public static string GetWatermark(DependencyObject d)
        {
            return (string)d.GetValue(WatermarkProperty);
        }

        public static void SetWatermark(DependencyObject obj, string value)
        {
            obj.SetValue(WatermarkProperty, value);
        }
        #endregion

        #region CornerRadiusProperty Border圓角
        /// <summary>
        /// Border圓角
        /// </summary>
        public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.RegisterAttached(
            "CornerRadius", typeof(CornerRadius), typeof(ControlAttachProperty), new FrameworkPropertyMetadata(null));

        public static CornerRadius GetCornerRadius(DependencyObject d)
        {
            return (CornerRadius)d.GetValue(CornerRadiusProperty);
        }

        public static void SetCornerRadius(DependencyObject obj, CornerRadius value)
        {
            obj.SetValue(CornerRadiusProperty, value);
        }
        #endregion

        #region LabelProperty TextBox的頭部Label
        /// <summary>
        /// TextBox的頭部Label
        /// </summary>
        public static readonly DependencyProperty LabelProperty = DependencyProperty.RegisterAttached(
            "Label", typeof(string), typeof(ControlAttachProperty), new FrameworkPropertyMetadata(null));

        [AttachedPropertyBrowsableForType(typeof(TextBox))]
        public static string GetLabel(DependencyObject d)
        {
            return (string)d.GetValue(LabelProperty);
        }

        public static void SetLabel(DependencyObject obj, string value)
        {
            obj.SetValue(LabelProperty, value);
        }
        #endregion

        #region LabelTemplateProperty TextBox的頭部Label模板
        /// <summary>
        /// TextBox的頭部Label模板
        /// </summary>
        public static readonly DependencyProperty LabelTemplateProperty = DependencyProperty.RegisterAttached(
            "LabelTemplate", typeof(ControlTemplate), typeof(ControlAttachProperty), new FrameworkPropertyMetadata(null));

        [AttachedPropertyBrowsableForType(typeof(TextBox))]
        public static ControlTemplate GetLabelTemplate(DependencyObject d)
        {
            return (ControlTemplate)d.GetValue(LabelTemplateProperty);
        }

        public static void SetLabelTemplate(DependencyObject obj, ControlTemplate value)
        {
            obj.SetValue(LabelTemplateProperty, value);
        }
        #endregion

2.2 水印效果實現

  通過2.1的程式碼示例,可以看出,水印是內建了一個TextBlock,用附加屬性ControlAttachProperty.Watermark設定水印內容,在觸發器中檢測,當TextBox中有輸入值,則隱藏水印的TextBlock,使用示例:  

<StackPanel>
            <TextBox Width="140" Height="40" Margin="3" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible">333333333333333</TextBox>
            <TextBox Width="150" Height="30" Margin="3" core:ControlAttachProperty.Watermark="我是水印" core:ControlAttachProperty.CornerRadius="2"></TextBox>
            <TextBox Width="150" Height="30" Margin="3" IsReadOnly="True" core:ControlAttachProperty.CornerRadius="15" SnapsToDevicePixels="True" >我是隻讀的</TextBox>
            <TextBox Width="150" Height="30" Margin="3" IsEnabled="False">IsEnabled="False"</TextBox>
            <TextBox Width="150" Height="30" core:ControlAttachProperty.Watermark="我是水印"></TextBox>
        </StackPanel>

 效果:

  

2.3 Label標籤實現

  參考2.1的程式碼,預留了Label的區域,通過設定附加屬性local:ControlAttachProperty.Label設定標籤文字,local:ControlAttachProperty.LabelTemplate設定Label標籤的模板樣式,即可自定義實現Label標籤,自定義樣式:

<!--TextBox包含附加屬性Label的樣式-->
    <Style TargetType="{x:Type TextBox}" x:Key="LabelTextBox" BasedOn="{StaticResource DefaultTextBox}">
        <Setter Property="local:ControlAttachProperty.LabelTemplate" >
            <Setter.Value>
                <ControlTemplate TargetType="ContentControl">
                    <Border Width="60" Background="{StaticResource TextLabelBackground}">
                        <TextBlock VerticalAlignment="Center" HorizontalAlignment="Right" Margin="3" Text="{TemplateBinding Content}"></TextBlock>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

使用示例及效果: 

<TextBox Width="200" Height="30" Margin="3" core:ControlAttachProperty.Watermark="請輸入姓名" 
                         Style="{StaticResource LabelTextBox}" core:ControlAttachProperty.Label="姓名:"></TextBox>

2.4 擴充套件功能及自定義擴充套件

  思路和2.3的Label標籤實現相似,清除文字框內的內容是一個常用需求,我們就線擴充套件一個這麼一個功能的TextBox,通過附加屬性ControlAttachProperty.AttachContent定義擴充套件功能的模板,模板內定義的是一個按鈕FButton(可參考上一篇,本文末尾附錄中有連結)  

<!--TextBox包含清除Text按鈕的樣式-->
    <Style TargetType="{x:Type TextBox}" x:Key="ClearButtonTextBox" BasedOn="{StaticResource DefaultTextBox}">
        <Setter Property="local:ControlAttachProperty.AttachContent">
            <Setter.Value>
                <ControlTemplate>
                    <local:FButton FIcon="&#xe60a;" Style="{StaticResource FButton_Transparency}" IsTabStop="False" FIconMargin="0"
                                   local:ControlAttachProperty.IsClearTextButtonBehaviorEnabled="True" Command="local:ControlAttachProperty.ClearTextCommand" 
                                   CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type TextBox}}}"
                               Margin="1,3,1,4" FIconSize="14" Foreground="{StaticResource TextForeground}" Cursor="Arrow"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

這裡定義的是顯示效果,清除TextBox內容的邏輯程式碼如何實現的呢?還是附加屬性:

  • ControlAttachProperty.IsClearTextButtonBehaviorEnabled="True" :注入事件到當前Button
  • Command="local:ControlAttachProperty.ClearTextCommand":定義Fbutton的命令物件例項Command
  • CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type TextBox}}}":把TextBox作為引數傳入

  邏輯程式碼如下,從程式碼不難看出,它是支援多種輸入控制元件的內容清除的,也就是說該擴充套件功能可以輕鬆支援其他輸入控制元件,第四節密碼資料的清除也是這樣使用的。

#region IsClearTextButtonBehaviorEnabledProperty 清除輸入框Text值按鈕行為開關(設為ture時才會繫結事件)
        /// <summary>
        /// 清除輸入框Text值按鈕行為開關
        /// </summary>
        public static readonly DependencyProperty IsClearTextButtonBehaviorEnabledProperty = DependencyProperty.RegisterAttached("IsClearTextButtonBehaviorEnabled"
            , typeof(bool), typeof(ControlAttachProperty), new FrameworkPropertyMetadata(false, IsClearTextButtonBehaviorEnabledChanged));

        [AttachedPropertyBrowsableForType(typeof(TextBox))]
        public static bool GetIsClearTextButtonBehaviorEnabled(DependencyObject d)
        {
            return (bool)d.GetValue(IsClearTextButtonBehaviorEnabledProperty);
        }

        public static void SetIsClearTextButtonBehaviorEnabled(DependencyObject obj, bool value)
        {
            obj.SetValue(IsClearTextButtonBehaviorEnabledProperty, value);
        }

        /// <summary>
        /// 繫結清除Text操作的按鈕事件
        /// </summary>
        private static void IsClearTextButtonBehaviorEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var button = d as FButton;
            if (e.OldValue != e.NewValue && button != null)
            {
                button.CommandBindings.Add(ClearTextCommandBinding);
            }
        }

        #endregion

        #region ClearTextCommand 清除輸入框Text事件命令

        /// <summary>
        /// 清除輸入框Text事件命令,需要使用IsClearTextButtonBehaviorEnabledChanged繫結命令
        /// </summary>
        public static RoutedUICommand ClearTextCommand { get; private set; }

        /// <summary>
        /// ClearTextCommand繫結事件
        /// </summary>
        private static readonly CommandBinding ClearTextCommandBinding;

        /// <summary>
        /// 清除輸入框文字值
        /// </summary>
        private static void ClearButtonClick(object sender, ExecutedRoutedEventArgs e)
        {
            var tbox = e.Parameter as FrameworkElement;
            if (tbox == null) return;
            if (tbox is TextBox)
            {
                ((TextBox)tbox).Clear();
            }
            if (tbox is PasswordBox)
            {
                ((PasswordBox)tbox).Clear();
            }
            if (tbox is ComboBox)
            {
                var cb = tbox as ComboBox;
                cb.SelectedItem = null;
                cb.Text = string.Empty;
            }
            if (tbox is MultiComboBox)
            {
                var cb = tbox as MultiComboBox;
                cb.SelectedItem = null;
                cb.UnselectAll();
                cb.Text = string.Empty;
            }
            if (tbox is DatePicker)
            {
                var dp = tbox as DatePicker;
                dp.SelectedDate = null;
                dp.Text = string.Empty;
            }
            tbox.Focus();
        }

        #endregion

        /// <summary>
        /// 靜態建構函式
        /// </summary>
        static ControlAttachProperty()
        {
            //ClearTextCommand
            ClearTextCommand = new RoutedUICommand();
            ClearTextCommandBinding = new CommandBinding(ClearTextCommand);
            ClearTextCommandBinding.Executed += ClearButtonClick;
            //OpenFileCommand
            OpenFileCommand = new RoutedUICommand();
            OpenFileCommandBinding = new CommandBinding(OpenFileCommand);
            OpenFileCommandBinding.Executed += OpenFileButtonClick;
            //OpenFolderCommand
            OpenFolderCommand = new RoutedUICommand();
            OpenFolderCommandBinding = new CommandBinding(OpenFolderCommand);
            OpenFolderCommandBinding.Executed += OpenFolderButtonClick;

            SaveFileCommand = new RoutedUICommand();
            SaveFileCommandBinding = new CommandBinding(SaveFileCommand);
            SaveFileCommandBinding.Executed += SaveFileButtonClick;
        }

效果:

  

當然我們也可以自定義擴充套件其他功能,如:  

<TextBox Width="200" Height="30" Margin="3" core:ControlAttachProperty.Watermark="查詢關鍵詞" IsEnabled="True">
                <core:ControlAttachProperty.AttachContent>
                    <ControlTemplate>
                        <StackPanel Orientation="Horizontal">
                            <core:FButton FIcon="&#xe60b;"  Style="{StaticResource FButton_Transparency}" IsTabStop="False" FIconMargin="0"
                                            FIconSize="18" Margin="1,1,2,3" Foreground="{StaticResource TextForeground}" Cursor="Arrow"/>
                            <core:FButton FIcon="&#xe628;"  Style="{StaticResource FButton_Transparency}" IsTabStop="False" FIconMargin="0"
                                            FIconSize="22" Foreground="{StaticResource TextForeground}" Cursor="Arrow"/>
                        </StackPanel>
                    </ControlTemplate>
                </core:ControlAttachProperty.AttachContent>
            </TextBox>

效果:

由上不難同時實現Label標籤和清除文字內容的樣式:

<!--TextBox包含附加屬性Label,以及ClearText按鈕的樣式-->
    <Style TargetType="{x:Type TextBox}" x:Key="LabelClearButtonTextBox" BasedOn="{StaticResource DefaultTextBox}">
        <Setter Property="local:ControlAttachProperty.LabelTemplate" >
            <Setter.Value>
                <ControlTemplate TargetType="ContentControl">
                    <Border Width="60" Background="{StaticResource TextLabelBackground}">
                        <TextBlock VerticalAlignment="Center" HorizontalAlignment="Right" Margin="3" Text="{TemplateBinding Content}"></TextBlock>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="local:ControlAttachProperty.AttachContent">
            <Setter.Value>
                <ControlTemplate>
                    <local:FButton FIcon="&#xe60a;" Style="{StaticResource FButton_Transparency}" IsTabStop="False" FIconMargin="0"
                               local:ControlAttachProperty.IsClearTextButtonBehaviorEnabled="True" Command="local:ControlAttachProperty.ClearTextCommand" 
                               CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type TextBox}}}"
                               Margin="0,3,1,4" FIconSize="14" Foreground="{StaticResource TextForeground}" Cursor="Arrow"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

2.6 檔案選擇輸入相關擴充套件

  先看看效果,就明白了。

   

具體實現原理和上面2.4差不多 ,實現了三個檔案、資料夾選擇相關的功能擴充套件,樣式程式碼:

<!--LabelOpenFileTextBox-->
    <Style TargetType="{x:Type TextBox}" x:Key="LabelOpenFileTextBox" BasedOn="{StaticResource LabelClearButtonTextBox}">
        <Setter Property="local:ControlAttachProperty.Label" Value="檔案路徑"/>
        <Setter Property="local:ControlAttachProperty.Watermark" Value="選擇檔案路徑"/>
        <Setter Property="local:ControlAttachProperty.AttachContent">
            <Setter.Value>
                <ControlTemplate>
                    <local:FButton FIcon="&#xe64e;" Style="{StaticResource FButton_Transparency}" IsTabStop="False" FIconMargin="0"
                               local:ControlAttachProperty.IsOpenFileButtonBehaviorEnabled="True" Command="local:ControlAttachProperty.OpenFileCommand" 
                               CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type TextBox}}}"
                               Margin="0,1,0,1"  FIconSize="22" Foreground="{StaticResource TextForeground}" Cursor="Arrow"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <!--LabelOpenFolderTextBox-->
    <Style TargetType="{x:Type TextBox}" x:Key="LabelOpenFolderTextBox" BasedOn="{StaticResource LabelClearButtonTextBox}">
        <Setter Property="local:ControlAttachProperty.Label" Value="設定路徑"/>
        <Setter Property="local:ControlAttachProperty.Watermark" Value="選擇資料夾路徑"/>
        <Setter Property="local:ControlAttachProperty.AttachContent">
            <Setter.Value>
                <ControlTemplate>
                    <local:FButton FIcon="&#xe636;" Style="{StaticResource FButton_Transparency}" IsTabStop="False" FIconMargin="0"
                               local:ControlAttachProperty.IsOpenFolderButtonBehaviorEnabled="True" Command="local:ControlAttachProperty.OpenFolderCommand" 
                               CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type TextBox}}}"
                               Margin="0,1,0,1"  FIconSize="22" Foreground="{StaticResource TextForeground}" Cursor="Arrow"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <!--LabelSaveFileTextBox-->
    <Style TargetType="{x:Type TextBox}" x:Key="LabelSaveFileTextBox" BasedOn="{StaticResource LabelClearButtonTextBox}">
        <Setter Property="local:ControlAttachProperty.Label" Value="儲存路徑"/>
        <Setter Property="local:ControlAttachProperty.Watermark" Value="選擇檔案儲存路徑"/>
        <Setter Property="local:ControlAttachProperty.AttachContent">
            <Setter.Value>
                <ControlTemplate>
                    <local:FButton FIcon="&#xe61a;" Style="{StaticResource FButton_Transparency}" IsTabStop="False" FIconMargin="0"
                               local:ControlAttachProperty.IsSaveFileButtonBehaviorEnabled="True" Command="local:ControlAttachProperty.SaveFileCommand" 
                               CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type TextBox}}}"
                               Margin="0,1,0,1"  FIconSize="20" Foreground="{StaticResource TextForeground}" Cursor="Arrow"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

當然實現原理和2.4一樣,都是依賴屬性來實現事件的注入和繫結的,所以就不多廢話了:

#region IsOpenFileButtonBehaviorEnabledProperty 選擇檔案命令列為開關
        /// <summary>
        /// 選擇檔案命令列為開關
        /// </summary>
        public static readonly DependencyProperty IsOpenFileButtonBehaviorEnabledProperty = DependencyProperty.RegisterAttached("IsOpenFileButtonBehaviorEnabled"
            , typeof(bool), typeof(ControlAttachProperty), new FrameworkPropertyMetadata(false, IsOpenFileButtonBehaviorEnabledChanged));

        [AttachedPropertyBrowsableForType(typeof(TextBox))]
        public static bool GetIsOpenFileButtonBehaviorEnabled(DependencyObject d)
        {
            return (bool)d.GetValue(IsOpenFileButtonBehaviorEnabledProperty);
        }

        public static void SetIsOpenFileButtonBehaviorEnabled(DependencyObject obj, bool value)
        {
            obj.SetValue(IsOpenFileButtonBehaviorEnabledProperty, value);
        }

        private static void IsOpenFileButtonBehaviorEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var button = d as FButton;
            if (e.OldValue != e.NewValue && button != null)
            {
                button.CommandBindings.Add(OpenFileCommandBinding);
            }
        }

        #endregion

        #region IsOpenFolderButtonBehaviorEnabledProperty 選擇資料夾命令列為開關
        /// <summary>
        /// 選擇資料夾命令列為開關
        /// </summary>
        public static readonly DependencyProperty IsOpenFolderButtonBehaviorEnabledProperty = DependencyProperty.RegisterAttached("IsOpenFolderButtonBehaviorEnabled"
            , typeof(bool), typeof(ControlAttachProperty), new FrameworkPropertyMetadata(false, IsOpenFolderButtonBehaviorEnabledChanged));

        [AttachedPropertyBrowsableForType(typeof(TextBox))]
        public static bool GetIsOpenFolderButtonBehaviorEnabled(DependencyObject d)
        {
            return (bool)d.GetValue(IsOpenFolderButtonBehaviorEnabledProperty);
        }

        public static void SetIsOpenFolderButtonBehaviorEnabled(DependencyObject obj, bool value)
        {
            obj.SetValue(IsOpenFolderButtonBehaviorEnabledProperty, value);
        }

        private static void IsOpenFolderButtonBehaviorEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var button = d as FButton;
            if (e.OldValue != e.NewValue && button != null)
            {
                button.CommandBindings.Add(OpenFolderCommandBinding);
            }
        }

        #endregion

        #region IsSaveFileButtonBehaviorEnabledProperty 選擇檔案儲存路徑及名稱
        /// <summary>
        /// 選擇檔案儲存路徑及名稱
        /// </summary>
        public static readonly DependencyProperty IsSaveFileButtonBehaviorEnabledProperty = DependencyProperty.RegisterAttached("IsSaveFileButtonBehaviorEnabled"
            , typeof(bool), typeof(ControlAttachProperty), new FrameworkPropertyMetadata(false, IsSaveFileButtonBehaviorEnabledChanged));

        [AttachedPropertyBrowsableForType(typeof(TextBox))]
        public static bool GetIsSaveFileButtonBehaviorEnabled(DependencyObject d)
        {
            return (bool)d.GetValue(IsSaveFileButtonBehaviorEnabledProperty);
        }

        public static void SetIsSaveFileButtonBehaviorEnabled(DependencyObject obj, bool value)
        {
            obj.SetValue(IsSaveFileButtonBehaviorEnabledProperty, value);
        }

        private static void IsSaveFileButtonBehaviorEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var button = d as FButton;
            if (e.OldValue != e.NewValue && button != null)
            {
                button.CommandBindings.Add(SaveFileCommandBinding);
            }
        }

        #endregion

        #region OpenFileCommand 選擇檔案命令

        /// <summary>
        /// 選擇檔案命令,需要使用IsClearTextButtonBehaviorEnabledChanged繫結命令
        /// </summary>
        public static RoutedUICommand OpenFileCommand { get; private set; }

        /// <summary>
        /// OpenFileCommand繫結事件
        /// </summary>
        private static readonly CommandBinding OpenFileCommandBinding;

        /// <summary>
        /// 執行OpenFileCommand
        /// </summary>
        private static void OpenFileButtonClick(object sender, ExecutedRoutedEventArgs e)
        {
            var tbox = e.Parameter as FrameworkElement;
            var txt = tbox as TextBox;
            string filter = txt.Tag == null ? "所有檔案(*.*)|*.*" : txt.Tag.ToString();
            if (filter.Contains(".bin"))
            {
                filter += "|所有檔案(*.*)|*.*";
            }
            if (txt == null) return;
            OpenFileDialog fd = new OpenFileDialog();
            fd.Title = "請選擇檔案";
            //“影象檔案(*.bmp, *.jpg)|*.bmp;*.jpg|所有檔案(*.*)|*.*”
            fd.Filter = filter;
            fd.FileName = txt.Text.Trim();
            if (fd.ShowDialog() == true)
            {
                txt.Text = fd.FileName;
            }
            tbox.Focus();
        }

        #endregion

        #region OpenFolderCommand 選擇資料夾命令

        /// <summary>
        /// 選擇資料夾命令
        /// </summary>
        public static RoutedUICommand OpenFolderCommand { get; private set; }

        /// <summary>
        /// OpenFolderCommand繫結事件
        /// </summary>
        private static readonly CommandBinding OpenFolderCommandBinding;

        /// <summary>
        /// 執行OpenFolderCommand
        /// </summary>
        private static void OpenFolderButtonClick(object sender, ExecutedRoutedEventArgs e)
        {
            var tbox = e.Parameter as FrameworkElement;
            var txt = tbox as TextBox;
            if (txt == null) return;
            FolderBrowserDialog fd = new FolderBrowserDialog();
            fd.Description = "請選擇檔案路徑";
            fd.SelectedPath = txt.Text.Trim();
            if (fd.ShowDialog() == DialogResult.OK)
            {
                txt.Text = fd.SelectedPath;
            }
            tbox.Focus();
        }

        #endregion

        #region SaveFileCommand 選擇檔案儲存路徑及名稱

        /// <summary>
        /// 選擇檔案儲存路徑及名稱
        /// </summary>
        public static RoutedUICommand SaveFileCommand { get; private set; }

        /// <summary>
        /// SaveFileCommand繫結事件
        /// </summary>
        private static readonly CommandBinding SaveFileCommandBinding;

        /// <summary>
        /// 執行OpenFileCommand
        /// </summary>
        private static void SaveFileButtonClick(object sender, ExecutedRoutedEventArgs e)
        {
            var tbox = e.Parameter as FrameworkElement;
            var txt = tbox as TextBox;
            if (txt == null) return;
            SaveFileDialog fd = new SaveFileDialog();
            fd.Title = "檔案儲存路徑";
            fd.Filter = "所有檔案(*.*)|*.*";
            fd.FileName = txt.Text.Trim();
            if (fd.ShowDialog() == DialogResult.OK)
            {
                txt.Text = fd.FileName;
            }
            tbox.Focus();
        }

        #endregion

        /// <summary>
        /// 靜態建構函式
        /// </summary>
        static ControlAttachProperty()
        {
            //ClearTextCommand
            ClearTextCommand = new RoutedUICommand();
            ClearTextCommandBinding = new CommandBinding(ClearTextCommand);
            ClearTextCommandBinding.Executed += ClearButtonClick;
            //OpenFileCommand
            OpenFileCommand = new RoutedUICommand();
            OpenFileCommandBinding = new CommandBinding(OpenFileCommand);
            OpenFileCommandBinding.Executed += OpenFileButtonClick;
            //OpenFolderCommand
            OpenFolderCommand = new RoutedUICommand();
            OpenFolderCommandBinding = new CommandBinding(OpenFolderCommand);
            OpenFolderCommandBinding.Executed += OpenFolderButtonClick;

            SaveFileCommand = new RoutedUICommand();
            SaveFileCommandBinding = new CommandBinding(SaveFileCommand);
            SaveFileCommandBinding.Executed += SaveFileButtonClick;
        }

 三.富文字框RichTextBox控制元件樣式

  RichTextBox的樣式比較簡單:  

<!--***************************DefaultRichTextBox***************************-->

    <Style x:Key="DefaultRichTextBox" TargetType="{x:Type RichTextBox}">
        <Setter Property="ContextMenu" Value="{DynamicResource TextBoxContextMenu}" />
        <Setter Property="SelectionBrush" Value="{StaticResource TextSelectionBrush}" />
        <Setter Property="FontFamily" Value="{StaticResource FontFamily}" />
        <Setter Property="FontSize" Value="{StaticResource FontSize}" />
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="BorderBrush" Value="{StaticResource ControlBorderBrush}" />
        <Setter Property="MinHeight" Value="26" />
        <Setter Property="MinWidth" Value="10" />
        <Setter Property="Background" Value="{StaticResource TextBackground}" />
        <Setter Property="Foreground" Value="{StaticResource TextForeground}" />
        <Setter Property="CaretBrush" Value="{StaticResource TextForeground}" />
        <Setter Property="local:ControlAttachProperty.FocusBorderBrush" Value="{StaticResource FocusBorderBrush}" />
        <Setter Property="local:ControlAttachProperty.MouseOverBorderBrush" Value="{StaticResource MouseOverBorderBrush}" />
        <Setter Property="Padding" Value="1" />
        <Setter Property="AllowDrop" Value="True" />
        <Setter Property="VerticalScrollBarVisibility" Value="Auto" />
        <Setter Property="FocusVisualStyle" Value="{x:Null}" />
        <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst" />
        <!--該值指示是否啟用了筆勢-->
        <Setter Property="Stylus.IsFlicksEnabled" Value="False" />
        <!--SnapsToDevicePixels:該值來確定呈現此元素是否應使用特定於裝置的畫素設定-->
        <Setter Property="SnapsToDevicePixels" Value="True" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TextBoxBase}">
                    <Grid>
                        <Border x:Name="Bd"
                                BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
                                Background="{TemplateBinding Background}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
                            <ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                        </Border>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="BorderBrush" Value="{Binding Path=(local:ControlAttachProperty.MouseOverBorderBrush),RelativeSource={RelativeSource Self}}"/>
                        </Trigger>
                        <Trigger Property="IsFocused" Value="True">
                            <Setter  Property="BorderBrush" Value="{Binding Path=(local:ControlAttachProperty.FocusBorderBrush),RelativeSource={RelativeSource Self}}"/>
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter TargetName="Bd" Property="Opacity" Value="0.5" />
                        </Trigger>
                        <Trigger Property="IsReadOnly" Value="True">
                            <Setter TargetName="Bd" Property="Opacity" Value="0.85" />
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

使用實力及效果:  

四.密碼輸入框PasswordBox控制元件樣式及擴充套件功能

  密碼輸入控制元件的樣式和第二節文字框TextBox基本一致,就不做詳細的說明了,直接上樣式的程式碼,相關邏輯(C#) 程式碼和上面是一樣的(複用)。

<!--TextBox預設樣式-->
    <Style TargetType="{x:Type PasswordBox}" x:Key="DefaultPasswordBox">
        <Setter Property="ContextMenu" Value="{DynamicResource TextBoxContextMenu}" />
        <Setter Property="SelectionBrush" Value="{StaticResource TextSelectionBrush}" />
        <Setter Property="FontFamily" Value="{StaticResource FontFamily}" />
        <Setter Property="FontSize" Value="{StaticResource FontSize}" />
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="PasswordChar" Value="●"/>
        <Setter Property="Height" Value="30" />
        <Setter Property="Width" Value="200" />
        <Setter Property="Background" Value="{StaticResource TextBackground}" />
        <Setter Property="Foreground" Value="{StaticResource TextForeground}" />
        <Setter Property="Padding" Value="0" />
        <Setter Property="BorderBrush" Value="{StaticResource ControlBorderBrush}" />
        <Setter Property="local:ControlAttachProperty.FocusBorderBrush" Value="{StaticResource FocusBorderBrush}" />
        <Setter Property="local:ControlAttachProperty.MouseOverBorderBrush" Value="{StaticResource MouseOverBorderBrush}" />
        <Setter Property="VerticalContentAlignment" Value="Center" />
        <!-- change SnapsToDevicePixels to True to view a better border and validation error -->
        <Setter Property="SnapsToDevicePixels" Value="True" />
        <!--英 ['kærət]  美 ['kærət]  插入符號-->
        <Setter Property="CaretBrush" Value="{StaticResource TextForeground}" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type PasswordBox}">
                    <Grid x:Name="PART_Root">
                        <Border x:Name="Bg" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                CornerRadius="{TemplateBinding local:ControlAttachProperty.CornerRadius}"
                                BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" />
                        <Grid x:Name="PART_InnerGrid">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition  Width="Auto" />
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition  Width="Auto" />
                            </Grid.ColumnDefinitions>
                            <!--Label區域-->
                            <ContentControl x:Name="Label" Margin="1" Template="{TemplateBinding local:ControlAttachProperty.LabelTemplate}"
                                            Content="{TemplateBinding local:ControlAttachProperty.Label}"/>
                            <!--內容區域-->
                            <