• 模板中的 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 的设置:

     由于时间的关系,有时间再去查相应文档。如果有朋友不幸阅读了本文,并且知道原因,希望能指点一下。

     修改后的工程代码下载

  • 相关阅读:
    SecureCRT 安装及初始化配置
    企业生产环境中linux系统分区的几种方案
    Django之验证码 + session 认证
    Django之上传文件
    Django之Cookie与Session
    Django之CSRF 跨站请求伪造
    web前端之 DOM
    c++ 之 字符和字符串
    web前端
    调用线程无法访问此对象,因为另一个线程拥有该对象
  • 原文地址:https://www.cnblogs.com/hebeiDGL/p/3670667.html
Copyright © 2020-2023  润新知