【UWP】实现 FindAncestor 绑定

在 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 所作不到的。

相关文章
相关标签/搜索