• 【UWP】实现 FindAncestor 绑定


    在 WPF 里,我们是可以在 RelativeSource 上面实现的,举个例子:

    <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,那就查询不到了。

    但是,在 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 项目也是通过这种方式来解决。

    但是,在前段时间我开发我自己的图片缓存控件(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 的方式来解决问题了。

    俗话说,不行就包一层。XAML 里包一层的话,那就是 ContentControl 了,这里我们创建一个叫 AncestorBindingAssist 的模板控件,继承自 ContentControl。

    cs 代码如下:

    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。

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

  • 相关阅读:
    并不对劲的CF1236D&E:Alice&Doll&UnfairGame
    并不对劲的CF1194E:Count The Rectangles
    并不对劲的CF1239B&C&D Programming Task in the Train to Catowice City
    并不对劲的初赛蒙猜凑思路
    并不对劲的CF1237D&E:Balanced Playlist and Binary Search Trees
    并不对劲的???
    并不对劲的P5589
    (简单) POJ 1511 Invitation Cards,SPFA。
    (简单) POJ 3159 Candies,Dijkstra+差分约束。
    (简单) POJ 2502 Subway,Dijkstra。
  • 原文地址:https://www.cnblogs.com/h82258652/p/11095165.html
Copyright © 2020-2023  润新知