• TemplateBinding与Binding区别,以及WPF自定义控件开发的遭遇


    在上一次的文章WPF OnApplyTemplate 不执行 或者执行滞后的疑惑谈到怎么正确的开发自定义控件,我们控件的样式中,属性的绑定一般都是用TemplateBinding来完成,如下一个基本的按钮样式:

    <Style x:Key="SimpleButton" TargetType="{x:Type Button}" BasedOn="{x:Null}">
        <Setter Property="FocusVisualStyle" Value="{DynamicResource SimpleButtonFocusVisual}"/>
        <Setter Property="Background" Value="{DynamicResource NormalBrush}"/>
        <Setter Property="BorderBrush" Value="{DynamicResource NormalBorderBrush}"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Button}">
                    
                    <!-- We use Grid as a root because it is easy to add more elements to customize the button -->
                    <Grid x:Name="Grid">
                        <Border x:Name="Border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}"/>
                        
                        <!-- Content Presenter is where the text content etc is placed by the control -->
                        <!-- The bindings are useful so that the control can be parameterized without editing the template -->
                        <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True"/>
                    </Grid>
                    
                    <!--Each state sets a brush on the Border in the template -->
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsKeyboardFocused" Value="true">
                            <Setter Property="BorderBrush" Value="{DynamicResource DefaultedBorderBrush}" TargetName="Border"/>
                        </Trigger>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter Property="Background" Value="{DynamicResource MouseOverBrush}" TargetName="Border"/>
                        </Trigger>
                        <Trigger Property="IsPressed" Value="true">
                            <Setter Property="Background" Value="{DynamicResource PressedBrush}" TargetName="Border"/>
                            <Setter Property="BorderBrush" Value="{DynamicResource PressedBorderBrush}" TargetName="Border"/>
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="true"/>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Background" Value="{DynamicResource DisabledBackgroundBrush}" TargetName="Border"/>
                            <Setter Property="BorderBrush" Value="{DynamicResource DisabledBorderBrush}" TargetName="Border"/>
                            <Setter Property="Foreground" Value="{DynamicResource DisabledForegroundBrush}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    我们看到,许多属性都是用TemplateBinding来完成的,也就是我们在使用控件和开发自定义控件时,都能够做到数据的展示数据的行为分开,使用数据驱动UI的思想,对于较复杂行为的控件,我们也可以在OnApplyTemplate方法中通过GetTemplateChild方法来获取到,当然,这个方法的执行时机是必须在布局过程中,如果在这之前就使用了内部的控件,那么必然会报Null错误。
    所以一般的样式开发中,都是用TemplateBinding来完成,说说今天的遭遇。我就是开发一个分页控件,点击上一页,下一页的时候,当前的页码要能够跟着变化。显示这个页码的控件那就是TextBlock,TemplateBinding了PageIndex依赖属性。控件的后台代码中,对上一页下一页的事件,就是修改PageIndex的值。运行起来,页码不会跟着变化!好,修改成Binding方式,如下:

    <TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent},Path=PageIndex}"></TextBlock>
    

    这样能够正常工作了。但是WPF自家的控件用的都是TemplateBinding,都没这问题,不甘心,继续网上找资料,发现一篇说是自定义的依赖属性使用TemplateBinding就是有问题的,这种bug微软怎么能不发现呢,并且这都.Net4.5了,内心感觉一定不是这样的,终于啊,找到问题所在了,并且是在一篇排版杂乱无章的小博客中找到的。

    TemplateBinding作为一种性能优化后的Binding使用,据说是Binding比较耗资源,这个没有求证过,但是我的程序中那么多Binding,运行起来也不觉得慢啊,或者说是用在模板中的一种Binding优化方式。既然是优化过的,那么它就会少一些东西,其中一个是数据流动的方向。TemplateBinding是单方向的,即数据源到目标的方向。这也解释了TreeViewItem官方的样式中,那个三角形的小箭头,它对于是否展开(IsExpanded属性)的属性绑定用的就不是TempalteBinding,因为他不能反过去更新数据源啊。

    <Style x:Key="SimpleTreeViewItem" d:IsControlPart="True" TargetType="{x:Type TreeViewItem}">
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="HorizontalContentAlignment" Value="{Binding Path=HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
        <Setter Property="VerticalContentAlignment" Value="{Binding Path=VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
        <Setter Property="Padding" Value="1,0,0,0"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TreeViewItem}">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition MinWidth="19" Width="Auto"/>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition/>
                        </Grid.RowDefinitions>
                        <!--注意这里-->
                        <ToggleButton x:Name="Expander" Style="{DynamicResource SimpleTreeViewItemToggleButton}" IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" ClickMode="Press"/>
                        <Border Grid.Column="1" x:Name="Selection_Border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
                            <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" x:Name="PART_Header" ContentSource="Header"/>
                        </Border>
                        <ItemsPresenter Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="1" x:Name="ItemsHost"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsExpanded" Value="false">
                            <Setter Property="Visibility" Value="Collapsed" TargetName="ItemsHost"/>
                        </Trigger>
                        <Trigger Property="HasItems" Value="false">
                            <Setter Property="Visibility" Value="Hidden" TargetName="Expander"/>
                        </Trigger>
                        <Trigger Property="IsSelected" Value="true">
                            <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" TargetName="Selection_Border"/>
                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
                        </Trigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsSelected" Value="true"/>
                                <Condition Property="IsSelectionActive" Value="false"/>
                            </MultiTrigger.Conditions>
                            <Setter Property="Background" Value="red" TargetName="Selection_Border"/>
                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
                        </MultiTrigger>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    但是在分页控件的这个页码属性上,是不需要反方向更新数据源这个功能的。所以问题也不是这儿,但必须注意这一点,开发自定义控件的时候非常重要。

    另外一个区别就是Converter,WPF中的Binding都是能够通过Converter来转换数据的,所以不管是TemplateBinding还是Binding都是够使用Converter来设置转换器,区别在于没有设置转换器的情况下,例如将int类型的数据绑定到TextBox的Text属性上,Binding会将值转换成字符串来显示,然而TemplateBinding就不会,这就是页码不能显示,也不会变化的原因。我立马弄了一个字符串类型页码依赖属性,TemplateBinding到这个Text属性上,可以工作了。但不会这么傻,再写一个转换器给TemplateBinding,这也是能够工作的。所以当数据源的类型和目标的类型不一致时,TemplateBinding需要自己写转换器来完成

    总结一下TemplateBinding与Binding区别

    (1)TemplateBinding只是单方向的数据绑定
    (2)TemplateBinding不会自动转换数据类型
  • 相关阅读:
    【JZOJ100047】基因变异【BFS】
    【JZOJ100047】基因变异【BFS】
    【JZOJ100046】收集卡片【模拟】
    【JZOJ100046】收集卡片【模拟】
    【NOIP2018】【洛谷P5022】旅行【基环树】
    【NOIP2018】【洛谷P5022】旅行【基环树】
    【POJ3417】Network【LCA】【差分】
    【POJ3417】Network【LCA】【差分】
    初学编程,选C还是选C++,你选对了吗
    TIOBE9月编程语言排行榜:时隔五年,为什么C语言依然排行第一
  • 原文地址:https://www.cnblogs.com/HelloMyWorld/p/6744894.html
Copyright © 2020-2023  润新知