在 WPF 裏,咱們是能夠在 RelativeSource 上面實現的,舉個例子:git
<Grid Tag="2"> <Button> <Grid Tag="1"> <TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid, AncestorLevel=2}, Path=Tag, Mode=OneWay}" /> </Grid> </Button> </Grid>
將 RelativeSource 的 Mode 設置爲 FindAncestor 就能夠了。AncestorType 表明綁定的類型,AncestorLevel 表明查詢第幾個,默認是 1。因此在上面的例子裏,因爲 AncestorLevel 是 2,因此查詢出來的是 Tag 等於 2 的那個 Grid。假如設置成 3,那就查詢不到了。github
可是,在 UWP 裏,微軟出於性能考慮,把 FindAncestor 給去掉了,RelativeSource 的 Mode 只剩下了 Self 和 TemplateParent。可是需求永遠是存在的,那麼總得有個解決方案吧,假如你搜索過 Google 或者 StackOverflow,無一不例外就是改爲經過 ElementName 來綁定,也就是上面的例子會變成以下這樣:緩存
<Grid x:Name="TargetGrid" Tag="2"> <Button> <Grid Tag="1"> <TextBlock Text="{Binding ElementName=TargetGrid, Path=Tag, Mode=OneWay}" /> </Grid> </Button> </Grid>
說實話這樣也能用,並且性能更好了,一直以來,我本身的 UWP 項目也是經過這種方式來解決。ide
可是,在前段時間我開發我本身的圖片緩存控件(https://github.com/h82258652/HN.Controls.ImageEx)時,就發現了一個沒法解決的問題。圖片控件 ImageEx 提供了一個 DownloadProgress 的屬性能夠獲取當前圖片的下載進度。另外該控件還有一個 LoadingTemplate 的屬性,能夠設置一個模板,在圖片加載的過程顯示。如今我想在加載的時候顯示一個進度條,因而乎就有了如下代碼:性能
<controls:ImageEx x:Name="TargetImage"> <controls:ImageEx.LoadingTemplate> <DataTemplate> <ProgressBar Maximum="1" Value="{Binding ElementName=TargetImage, Path=DownloadProgress.Percentage, Mode=OneWay}" /> </DataTemplate> </controls:ImageEx.LoadingTemplate> </controls:ImageEx>
這樣單個 ImageEx 就沒問題了,可是需求再進一步,我須要全部的 ImageEx 都是這樣,LoadingTemplate 是一致的。在此刻,咱們已經沒辦法經過綁定 ElementName 的方式來解決問題了。this
俗話說,不行就包一層。XAML 裏包一層的話,那就是 ContentControl 了,這裏咱們建立一個叫 AncestorBindingAssist 的模板控件,繼承自 ContentControl。spa
cs 代碼以下:code
public class AncestorBindingAssist : ContentControl { public static readonly DependencyProperty AncestorLevelProperty = DependencyProperty.Register(nameof(AncestorLevel), typeof(int), typeof(AncestorBindingAssist), new PropertyMetadata(1, OnAncestorLevelChanged)); public static readonly DependencyProperty AncestorTypeProperty = DependencyProperty.Register(nameof(AncestorType), typeof(Type), typeof(AncestorBindingAssist), new PropertyMetadata(default(Type), OnAncestorTypeChanged)); public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(nameof(Source), typeof(DependencyObject), typeof(AncestorBindingAssist), new PropertyMetadata(default(DependencyObject))); public AncestorBindingAssist() { DefaultStyleKey = typeof(AncestorBindingAssist); } public int AncestorLevel { get => (int)GetValue(AncestorLevelProperty); set => SetValue(AncestorLevelProperty, value); } public Type AncestorType { get => (Type)GetValue(AncestorTypeProperty); set => SetValue(AncestorTypeProperty, value); } public DependencyObject Source { get => (DependencyObject)GetValue(SourceProperty); private set => SetValue(SourceProperty, value); } protected override void OnApplyTemplate() { UpdateSource(); } private static void OnAncestorLevelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var obj = (AncestorBindingAssist)d; var value = (int)e.NewValue; if (value < 1) { throw new ArgumentOutOfRangeException(nameof(AncestorLevel)); } obj.UpdateSource(); } private static void OnAncestorTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var obj = (AncestorBindingAssist)d; obj.UpdateSource(); } private void UpdateSource() { Source = AncestorType == null ? null : this.GetAncestors().Where(temp => AncestorType.IsInstanceOfType(temp)).Skip(AncestorLevel - 1).FirstOrDefault(); } }
AncestorType 和 AncestorLevel 兩個屬性跟 WPF 裏一致,而後提供一個 Source 屬性提供給下級綁定。在 AncestorType 或者 AncestorLevel 發生變化時,則調用 UpdateSource 方法刷新 Source。UpdateSource 方法裏的 GetAncestors 來自 WinRTXamlToolkit。blog
xaml 代碼的話就很簡單了,由於這裏只是包一層。繼承
<Style TargetType="local:AncestorBindingAssist"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:AncestorBindingAssist"> <ContentPresenter /> </ControlTemplate> </Setter.Value> </Setter> </Style>
接下來該怎麼用呢,以文章開頭的例子,就變成了這樣:
<Grid Tag="2"> <Button> <Grid Tag="1"> <local:AncestorBindingAssist x:Name="BindingAssist" AncestorLevel="2" AncestorType="Grid"> <TextBlock Text="{Binding ElementName=BindingAssist, Path=Source.Tag, Mode=OneWay}" /> </local:AncestorBindingAssist> </Grid> </Button> </Grid>
各位看官可能會吐槽,這跟直接綁定 ElementName 好像沒啥區別,並且還更復雜了。可是這裏咱們再舉上面我那個 ImageEx 的例子,如今咱們想全部 ImageEx 複用 LoadingTemplate 就能夠這麼寫了:
<Style TargetType="controls:ImageEx"> <Setter Property="LoadingTemplate"> <Setter.Value> <DataTemplate> <local:AncestorBindingAssist x:Name="BindingAssist" AncestorType="controls:ImageEx"> <ProgressBar Maximum="1" Value="{Binding ElementName=BindingAssist, Path=Source.DownloadProgress.Percentage, Mode=OneWay}" /> </local:AncestorBindingAssist> </DataTemplate> </Setter.Value> </Setter> </Style>
這樣就能全部的 ImageEx 都能複用 LoadingTemplate 了。而這是簡單的綁定 ElementName 所作不到的。