UWP附加属性未触发属性变更回调及XAML构造后对象访问问题
关于自定义附加属性默认值与构造后访问的问题解答
嘿,这个问题我之前踩过坑,咱们一步步说清楚:
一、为什么默认值设为new List<object>()时OnBindingsChanged不触发?
这里有两个核心原因:
- 属性变更回调的触发逻辑:
OnBindingsChanged这类属性变更回调,只有当属性值从一个值变成另一个不同的值时才会被调用。当你在注册附加属性时把new List<object>()作为默认值,元素初始化时只是直接获取这个预定义的默认值,并没有发生“值变更”(从无到有或者从A到B),所以回调不会触发。 - 引用类型默认值的共享陷阱:更坑的是,这个
new List<object>()是一个静态实例——所有使用该附加属性的元素都会共享同一个列表!这几乎肯定不是你想要的效果,比如给两个不同的元素用这个附加属性,往一个里加数据另一个也会同步收到,完全不符合预期。
正确的写法应该是这样:
把默认值设为null,然后在Get方法里做延迟初始化,这样既避免共享实例,又能触发变更回调:
public static class MyAttachedProperties { public static readonly DependencyProperty BindingsProperty = DependencyProperty.RegisterAttached( "Bindings", typeof(List<object>), typeof(MyAttachedProperties), new PropertyMetadata(null, OnBindingsChanged)); public static List<object> GetBindings(DependencyObject obj) { // 第一次获取时如果为null,创建新列表并设置回去 var bindings = (List<object>)obj.GetValue(BindingsProperty); if (bindings == null) { bindings = new List<object>(); SetBindings(obj, bindings); // 这一步会触发OnBindingsChanged } return bindings; } public static void SetBindings(DependencyObject obj, List<object> value) { obj.SetValue(BindingsProperty, value); } private static void OnBindingsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { // 现在第一次初始化时也会触发这里了 var oldList = e.OldValue as List<object>; var newList = e.NewValue as List<object>; // 你的逻辑处理 } }
当第一次调用GetBindings时,因为默认值是null,我们创建新列表并调用SetBindings,这时候属性值从null变成了新的列表,OnBindingsChanged就会被正常触发。
二、如何在XAML目标对象构造完成后访问它?
元素的构造函数执行完成后,XAML的属性设置、可视化树构建等还在进行,要确保能安全访问元素,推荐这几种方式:
1. 使用Loaded事件
这是最常用的方式,Loaded事件在元素完全加载到可视化树、所有属性都设置完成后触发:
<!-- XAML中给目标元素绑定Loaded事件 --> <Grid x:Name="MyTargetGrid" local:MyAttachedProperties.Bindings="{Binding MyDataList}" Loaded="MyTargetGrid_Loaded"> </Grid>
后台代码处理:
private void MyTargetGrid_Loaded(object sender, RoutedEventArgs e) { var targetGrid = sender as Grid; if (targetGrid == null) return; // 这里可以安全访问元素和它的附加属性了 var bindings = MyAttachedProperties.GetBindings(targetGrid); // 执行你的操作,比如往列表加数据、绑定事件等 }
2. 使用Dispatcher延迟执行
如果你想在窗口/页面的构造函数里直接处理,可以把操作放到Dispatcher的Loaded优先级队列,确保在元素构造完成后执行:
public MainWindow() { InitializeComponent(); Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() => { var bindings = MyAttachedProperties.GetBindings(MyTargetGrid); // 访问元素和属性的逻辑 })); }
3. 自定义控件重写OnInitialized
如果你的目标对象是自定义控件,可以重写OnInitialized方法,这个方法在控件构造完成、所有属性初始化后调用:
public class MyCustomControl : Control { protected override void OnInitialized(EventArgs e) { base.OnInitialized(e); // 这里可以访问控件自身的附加属性或者控件状态 var bindings = MyAttachedProperties.GetBindings(this); } }
内容的提问来源于stack exchange,提问作者Andriy Shevchenko
相关产品推荐
相关产品推荐

