模板中的 TemplateBinding 問題


 

昨天一個朋友向我求助一個自定義水印控件綁定的問題,問題出在文本框中輸入的文本,不能綁定到

相應的依賴屬性上(自定義的依賴屬性 PassText),他糾結了很久找不出問題所在。問題幫他解決后,這里稍

做總結。

 

朋友有問題的文本框代碼下載

問題描述:

1)默認顯示效果:

2)在“水印密碼框”中輸入四個 ‘a’:

3)單擊按鈕,打印出密碼框中的字符串,但是密碼框中的文字並沒有顯示,顯示的還是定義依賴屬性時

的默認值:

 

他的部分源代碼:

1)密碼框控件類繼承自 TextBox ,其中依賴屬性 PassText 為綁定到密碼框的屬性: 

public class WaterPasswordBox : TextBox
    {
        public static readonly DependencyProperty PassTextProperty = DependencyProperty.Register(
          "PassText", typeof( string ), typeof( WaterPasswordBox ), new PropertyMetadata( "" ) );

        public string PassText
        {
            get { return (string)GetValue( PassTextProperty ); }
            set { SetValue( PassTextProperty, value ); }
        }

        public static DependencyProperty WaterContentProprty = DependencyProperty.Register("WaterContent", typeof(object), typeof(WaterPasswordBox), new PropertyMetadata("水印密碼框"));

        public object WaterContent
        {
            get
            {
                return GetValue(WaterContentProprty);
            }

            set
            {
                SetValue(WaterContentProprty, value);
            }
        }

        public static DependencyProperty WaterForegroundProprty = DependencyProperty.Register("WaterForeground", typeof(Brush), typeof(WaterPasswordBox), new PropertyMetadata(new SolidColorBrush(Colors.Gray)));

        public Brush WaterForeground
        {
            get
            {
                return (Brush)GetValue(WaterForegroundProprty);
            }

            set
            {
                SetValue(WaterForegroundProprty, value);
            }
        }

        public WaterPasswordBox()
        {
            DefaultStyleKey = typeof(WaterPasswordBox);
        }

        ContentControl WaterContentElement = null;
        PasswordBox PasswordBoxElement = null;

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            WaterContentElement = this.GetTemplateChild("WaterCoElement") as ContentControl;
            PasswordBoxElement = this.GetTemplateChild("ContentElement") as PasswordBox;
            if (WaterContentElement != null && PasswordBoxElement != null)
            {
                if (string.IsNullOrEmpty(PasswordBoxElement.Password))
                    WaterContentElement.Visibility = System.Windows.Visibility.Visible;
                  
                else
                    WaterContentElement.Visibility = System.Windows.Visibility.Collapsed;
            }
        }

        protected override void OnGotFocus(RoutedEventArgs e)
        {
            if (WaterContentElement != null && string.IsNullOrEmpty(PasswordBoxElement.Password))
                WaterContentElement.Visibility = Visibility.Collapsed;
            base.OnGotFocus(e);
        }
        
     //   public event TextChangedEventHandler TextChanged +=   ;
        protected override void OnLostFocus(RoutedEventArgs e)
        {
          
            if (WaterContentElement != null && string.IsNullOrEmpty(PasswordBoxElement.Password))
                WaterContentElement.Visibility = Visibility.Visible;
            base.OnLostFocus(e);
        }
    }
View Code


2)在 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:WaterTextBox" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    mc:Ignorable="d">

    <ControlTemplate x:Key="PhoneDisabledTextBoxTemplate" TargetType="TextBox">
        <ContentControl x:Name="ContentElement" BorderThickness="0" HorizontalContentAlignment="Stretch" Margin="{StaticResource PhoneTextBoxInnerMargin}" Padding="{TemplateBinding Padding}" VerticalContentAlignment="Stretch"/>
    </ControlTemplate>

    <Style TargetType="local:WaterPasswordBox">
        <Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilyNormal}"/>
        <Setter Property="FontSize" Value="{StaticResource PhoneFontSizeMediumLarge}"/>
        <Setter Property="Background" Value="{StaticResource PhoneTextBoxBrush}"/>
        <Setter Property="Foreground" Value="{StaticResource PhoneTextBoxForegroundBrush}"/>
        <Setter Property="BorderBrush" Value="{StaticResource PhoneTextBoxBrush}"/>
        <Setter Property="SelectionBackground" Value="{StaticResource PhoneAccentBrush}"/>
        <Setter Property="SelectionForeground" Value="{StaticResource PhoneTextBoxSelectionForegroundBrush}"/>
        <Setter Property="BorderThickness" Value="{StaticResource PhoneBorderThickness}"/>
        <Setter Property="Padding" Value="2"/>
       
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:WaterPasswordBox">
                    <Grid Background="Transparent">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal"/>
                                <VisualState x:Name="MouseOver"/>
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="EnabledBorder">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Collapsed</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="DisabledOrReadonlyBorder">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Visible</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="ReadOnly">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="EnabledBorder">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Collapsed</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="DisabledOrReadonlyBorder">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Visible</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="DisabledOrReadonlyBorder">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneTextBoxBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="DisabledOrReadonlyBorder">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneTextBoxBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="DisabledOrReadonlyContent">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneTextBoxReadOnlyBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="FocusStates">
                                <VisualState x:Name="Focused">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="EnabledBorder">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneTextBoxEditBackgroundBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="EnabledBorder">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneTextBoxEditBorderBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Unfocused"/>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Border x:Name="EnabledBorder" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Margin="{StaticResource PhoneTouchTargetOverhang}">
                            <Grid>
                                <ContentControl x:Name="WaterCoElement" Content="{TemplateBinding WaterContent}" FontStyle="Normal" Foreground="{TemplateBinding WaterForeground}" Margin="{StaticResource PhoneTextBoxInnerMargin}" d:LayoutOverrides="Height" Padding="{TemplateBinding Padding}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"/>
                                <!--重點這里的 Password="{Binding PassText}"綁定不了-->
                                <PasswordBox x:Name="ContentElement"  Password="{Binding PassText}" Background="{Binding Background}" BorderThickness="0" HorizontalContentAlignment="Stretch" Margin="-12" Padding="{TemplateBinding Padding}" VerticalContentAlignment="Stretch"/>
                            </Grid>
                        </Border>
                        <Border x:Name="DisabledOrReadonlyBorder" BorderBrush="{StaticResource PhoneDisabledBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="Transparent" Margin="{StaticResource PhoneTouchTargetOverhang}" Visibility="Collapsed">
                            <TextBox x:Name="DisabledOrReadonlyContent" Background="Transparent" Foreground="{StaticResource PhoneDisabledBrush}" FontWeight="{TemplateBinding FontWeight}" FontStyle="{TemplateBinding FontStyle}" FontSize="{TemplateBinding FontSize}" FontFamily="{TemplateBinding FontFamily}" IsReadOnly="True" SelectionForeground="{TemplateBinding SelectionForeground}" SelectionBackground="{TemplateBinding SelectionBackground}" TextAlignment="{TemplateBinding TextAlignment}" TextWrapping="{TemplateBinding TextWrapping}" Text="{TemplateBinding Text}" Template="{StaticResource PhoneDisabledTextBoxTemplate}"/>
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>
View Code

 

 3)在引用該控件的頁面中,添加該控件:

<Border BorderBrush="Blue" BorderThickness="5" Margin="0,10">
    <StackPanel>
        <TextBlock FontSize="30" Text="水印密碼框" Margin="12,0"/>
        <my:WaterPasswordBox Name="WaterPasswordBox" />
    </StackPanel>
</Border>

<Button BorderBrush="Blue" Tap="UIElement_OnTap" BorderThickness="5" Margin="0,10" Height="100">
 點擊查看斷點測試
</Button>


4)按鈕的 Tap 路由事件:

private void UIElement_OnTap(object sender, GestureEventArgs e)
{
    string temp = WaterPasswordBox.PassText.ToString();

    MessageBox.Show(temp);
}


之所以在密碼框中輸入的文本沒有最終傳遞到 PassText 這個依賴屬性上,是因為 TemplateBinding

為 Binding 的單向綁定形式,也就是 依賴屬性 PassText 的默認值 “空” 可以綁定到 樣式控件里面的

PasswordBox 控件的 Password 屬性上,但是 不能反向綁定。

截圖:

其中 msdn 對 TemplateBinding 的描述:

 

“您在模板中使用 TemplateBinding 綁定到模板所應用到的控件的值。     

TemplateBinding   Binding 有效,但較少功能。  使用 TemplateBinding 使用與 RelativeSource 屬性的 Binding 等效設置為

RelativeSource.TemplatedParent  ”

 

 “TemplateBindingBinding的一個輕量級版本,它失去了成熟版本Binding的很多功能,比如繼承內容引用(inheritence context referencing),

RelativeSource引用,還有通過IValueConverter/TypeConverter機制的動態類型轉換。它僅支持由模板產生的FrameworkElements,它的數據源引

用會指向模板中的父級元素。TemplateBinding最主要的用途是內置在模板中綁定模板化元素的屬性,在這種情況下,比起成熟Binding效率要高得多。”

 

也就是:

<PasswordBox  Password="{TemplateBinding PassText}"/>

等價於:

<PasswordBox  Password="{Binding Path=PassText, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"/>

 

這時,把自定義密碼框樣式中的 PasswordBox 控件的綁定中,把 Binding 的設置 Mode=OneWay 改成 Mode=TwoWay,就可以實現雙向綁定了,

截圖:

 

此時雖然完成了雙向綁定,但是遇到另一個問題,如果在自定義密碼框中輸入文本后,立即點擊按鈕時,彈出框

中顯示的還是自定義密碼框中之前綁定屬性值;如果在填寫完成自定義密碼框后,單擊屏幕其它地方,讓密碼框

失去焦點,然后再單擊按鈕,就能顯示正確的內容了。

 

造成這個問題的原因,是因為 這里注冊的是按鈕的 Tap 這個路由事件,而它的執行時間要早於文本框 LostFocus

事件。這里再為按鈕添加一個 Click 事件,為 自定義密碼框控件添加一個 LostFocus 事件,同是在 C# 頁面打印出

按鈕的執行信息:

XAML:

<my:WaterPasswordBox  Name="WaterPasswordBox" LostFocus="WaterPasswordBox_LostFocus"/>

 <Button BorderBrush="Blue" Tap="UIElement_OnTap" BorderThickness="5" Margin="0,10" Height="100" Click="Button_Click">
      點擊查看斷點測試
 </Button>

 

C# :

 private void UIElement_OnTap(object sender, GestureEventArgs e)
 {
     Debug.WriteLine("UIElement_OnTap");

     //WaterPasswordBox.PassText = "asd";
     //string temp = WaterPasswordBox.PassText.ToString();

     //MessageBox.Show(temp);
 }

 private void Button_Click(object sender, RoutedEventArgs e)
 {
     Debug.WriteLine("Button_Click");

     string temp = WaterPasswordBox.PassText.ToString();

     MessageBox.Show(temp);
 }

 private void WaterPasswordBox_LostFocus(object sender, RoutedEventArgs e)
 {
     Debug.WriteLine("WaterPasswordBox_LostFocus");
 }


在 Debug 輸出窗口中輸出:

UIElement_OnTap
WaterPasswordBox_LostFocus
Button_Click

可以看出,Tap 路由事件的觸發要早於 LostFocus 事件,最后觸發 Click 事件。

 

此時再在 文本框中輸入4個 ‘a’ ,從按鈕的 Click 事件中把密碼框中的文本打印出來:

 

雖然實現了雙向綁定,但是從 PasswordBox 的 Password 屬性值反向同步並不是及時的,參考了一下

下面的其它屬性,暫時還沒有找到在 TextChanged 事件里就能觸發的 update source 的設置:

 

 由於時間的關系,有時間再去查相應文檔。如果有朋友不幸閱讀了本文,並且知道原因,希望能指點一下。

 

 修改后的工程代碼下載

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM