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不會自動轉換數據類型


免責聲明!

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



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