在 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(, 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 < )
{
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 - ).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 所做不到的。

【UWP】实现 FindAncestor 绑定的更多相关文章

  1. 【UWP】FlipView绑定ItemsSource,Selectedindex的问题

    最近在做列表头部的Carousel展示,Carousel使用的是FlipView展示,另外使用ListBox显示当前页,如下图 我们先设置一个绑定的数据源 public class GlobalRes ...

  2. WPF系列——简单绑定学习

    1. 绑定到元素对象.(实际项目中用处不大) 界面上两个关联的控件之间绑定,比如一个TextBlock 的FontSize和一个Slider 的Value绑定: <Slider Name=&qu ...

  3. WPF进阶技巧和实战08-依赖属性与绑定02

    将元素绑定在一起 数据绑定最简单的形式是:源对象是WPF元素而且源属性是依赖项属性.依赖项属性内置了更改通知支持,当源对象中改变依赖项属性时,会立即更新目标对象的绑定属性. 元素绑定到元素也是经常使用 ...

  4. 2018-8-10-win10-uwp-绑定-OneWay-无法使用

    title author date CreateTime categories win10 uwp 绑定 OneWay 无法使用 lindexi 2018-08-10 19:16:50 +0800 2 ...

  5. [WPF 自定义控件]自定义一个“传统”的 Validation.ErrorTemplate

    1. 什么是Validaion.ErrorTemplate 数据绑定模型允许您将与您Binding的对象相关联ValidationRules. 如果用户输入的值无效,你可能希望在应用程序 用户界面 ( ...

  6. UWP: 掌握编译型绑定 x:Bind

    在 UWP 开发中,我们在进行数据绑定时,除了可以使用传统的绑定 Binding,也可以使用全新的 x:Bind,由于后者是在程序编译时进行初始化操作(不同于 Binding,它是在运行时创建.初始化 ...

  7. win10 uwp 绑定密码

    win10 下,密码框无法绑定到ViewModel,Password是不可以绑定. 我们可以自己使用简单方法去绑定 我们之前在WPF 使用绑定密码框,我写了一篇,关于如何绑定,我提供一个我自己试了可以 ...

  8. 使用FindAncestor查找方式绑定且不需要使用datacontext

    原文:使用FindAncestor查找方式绑定且不需要使用datacontext <UserControl x:Class="QuJiao.AnimationVideoPlayer&q ...

  9. WPF/UWP 绑定中的 UpdateSourceTrigger

    在开发 markdown-mail 时遇到了一些诡异的情况.代码是这么写的: <TextBox Text="{Binding Text, Mode=TwoWay}"/> ...

随机推荐

  1. Python爬取620首虾米歌曲,揭秘五月天为什么狂吸粉?!

    前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者: CDA数据分析师 PS:如有需要Python学习资料的小伙伴可以加点 ...

  2. TCP协议如何保证可靠传输?

    一.TCP的可靠传输如何保证? 在TCP连接中,数据流必须以正确的顺序传送给对方.TCP的可靠性是通过顺序编号和确认(ACK)实现的.TCP在开始传送一个段时,为准备重传而首先将该段插入到发送队列中, ...

  3. 点击事件的坐标计算(client || offset) +(X || Width || Left) 各种排列组合别绕晕

    结论: 1,X,Y的都是属于点击位置的,width.height.left.top都是属于DOM的. 2,涉及的所有位置只与document或DOM内部有关,与DOM如何定位,周围有没有其他占位HTM ...

  4. Unity Pixel 人物设计(1)

    草稿: 目前进度:

  5. SmobilerService 开发者日志(一):什么是 SmobilerService ,它为开发者带来了什么价值?

    初见 SmobilerService 你会发现几个版本,以及一些价格. 所以,"Smobiler 是要收费了吗?" 这是开发团队在幕后悄悄观察 Service 推广开始后,用户向运 ...

  6. [PHP] PHP PDO与mysql的连接单例防止超时情况处理

    这个数据库类主要处理了单例模式下创建数据库对象时,如果有两次较长时间的间隔去执行sql操作,再次处理会出现连接失败的问题,利用一个cache数组存放pdo对象与时间戳,把两次执行之间的时间进行了比较, ...

  7. Lnmp架构部署动态网站环境.2019-7-3-1.3

    Php安装 一.安装准备 1.Php依赖包 [root@Lnmp tools]# yum install -y zlib libxml libjpeg freetype libpng gd curl ...

  8. AtCoder - 2140 (思维)

    题意 https://vjudge.net/problem/AtCoder-2140 每次告诉你新的a:b,计算最后最小的a+b. 思路 如 3 2 3 1 1 3 2 先令a=2,b=3,发现新的为 ...

  9. pycharm添加断点,分段运行,以及继续运行;

    1.打断点,只需在代码的前面单机左键即可:2.运行,点击Debug...(如图,像贝壳一样的图标),只会运行第一个红点前的代码: 3.此时若想将第2个红点前的代码运行完毕:点击Resume Progr ...

  10. Jmeter获取数据库查询结果某一字段的值

    1.首先进行连接数据库 2.添加JDBC Request 3.添加BeanShell PostProcessor 4.注意点:如果获取的是INT数字类型的,结尾需要添加toString()