在应用程序启动时附加 属性 回调事件 return 控件 属性 绑定为空

On app startup Attached Property Callback Event return null for a Control property binding

故事与问题:

我创建 IsActivated 属性 作为附加 属性 以便能够将其添加到我想要的 XAML 页面中的任何控件。然后,当任何控件将其设置为 true 时,我希望触发 PropertyChangedCallback 事件并检查该控件的特定 Property 的绑定。该应用程序 运行 没有任何问题,所有将 IsActivated 属性 设置为 true 的控件都将触发该事件并且绑定对象在 UI 中正确显示值也是。

但问题是,当PropertyChangedcallback事件执行时,GetBinding()GetBindingExpression()方法将returnnull 那些 Properties.

的值

(现在我只检查 TextBox 及其 TextProperty)

背景和我已经尝试过的东西

我在生产应用程序中使用了相同的技术,但是当我尝试简化它向朋友演示时,它不起作用!

我发现,如果我 Set/Change IsActivated 属性 对于 TextBoxMainWindow.OnLoaded() 事件,那么在它的回调事件中它会正确 return TextBox.TextProperty 的绑定信息:

private void OnLoaded(object sender, RoutedEventArgs e)
{
    MyTextBox.SetValue(ChangeBehavior.IsActivatedProperty, false);
}

但我没有在我的真实应用程序中这样做,我不喜欢这个解决方案!

源代码

这是 GitHub 完整的示例代码。

但这是我的附件属性 class:

public static class ChangeBehavior
{
    public static readonly DependencyProperty IsActivatedProperty;

    static ChangeBehavior()
    {
        IsActivatedProperty = DependencyProperty.RegisterAttached
            (
                "IsActivated",
                typeof(bool),
                typeof(ChangeBehavior),
                new PropertyMetadata(false, OnIsActivatedPropertyChangedCallback)
            );
    }

    public static bool GetIsActivated(DependencyObject obj)
        => (bool)obj.GetValue(IsActivatedProperty);
    public static void SetIsActivated(DependencyObject obj, bool value)
        => obj.SetValue(IsActivatedProperty, value);

    private static void OnIsActivatedPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (!(d is TextBox)) return;
        
        // ===> Here I get null value
        var binding = BindingOperations.GetBinding(d, TextBox.TextProperty);
        if (binding == null) return;

        if ((bool)e.NewValue)
        {
            var sourceObject = binding.Path.Path;
            //Doing some stuff...
        }
        else
        {
            //Doing some stuff...
        }
    }
}

这是我的 UI XAML:

<Grid>
    <TextBox Name="MyTextBox"
             Text="{Binding Model.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
             local:ChangeBehavior.IsActivated="True"/>
</Grid>

主窗口代码隐藏:

public partial class MainWindow : Window
{
    private readonly ViewModel _viewModel;

    public MainWindow(ViewModel viewModel)
    {
        InitializeComponent();

        _viewModel = viewModel;
        DataContext = _viewModel;
    }
}

ViewModel class:(省略 Notify属性... 使其更短的部分)

public class ViewModel : INotifyPropertyChanged
{
    private Model _model;

    public ViewModel()
    {
        Model = new Model {Name = "Model One"};
    }

    public Model Model
    {
        get => _model;
        set
        {
            _model = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEvent...
}

型号class:(省略通知属性...部分以使其更短)

public class Model : INotifyPropertyChanged
{
    private string _name;

    public string Name
    {
        get => _name;
        set
        {
            if (value == _name) return;
            _name = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEvent...
    }
}

我的代码有什么问题!

我希望当 InitializeComponent(); 从我的 MainWindow 构造函数执行时(就像我的真实应用程序一样)在 XAML 中将 local:ChangeBehavior.IsActivated 设置为 true 的任何控件,我在 OnIsActivatedPropertyChangedCallback 方法中获得了他们 Properties 的正确绑定信息!

为什么几乎相同的技术(和几乎相同的代码!)在我的实际应用程序中有效,但在这个简单的代码中却不起作用!?

我在这里缺少什么!?

更新:解决我的问题的第二种方法是:

与其直接在元素上使用我的 IsAttached 属性,我应该在 ResourceDictionary 中使用它。 (另一种方式是我标记为答案)

我想看看它在您的实际项目中的使用情况。在这一个中,它是在 Text 上设置绑定之前设置的。永远不要让附加属性依赖于初始化顺序,因为那完全不在你的控制范围内。

我们想做的是:能行就行。如果没有,请在加载控件后执行,除非它已经加载,在这种情况下几乎肯定不会有任何事情要做。可以在此处的 Loaded 处理程序中执行此操作,因为 a) class 的消费者不必查看它,并且 b) 您别无选择。

我们理想情况下希望在 Text 上设置绑定时执行此操作,但没有相关事件,并且在运行时替换绑定相对不常见。

private static void OnIsActivatedPropertyChangedCallback(DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    if (d is TextBox textBox)
    {
        if (!tryGetBinding() && !textBox.IsLoaded)
        {
            void onLoaded(object sender, RoutedEventArgs e2)
            {
                tryGetBinding();

                textBox.Loaded -= onLoaded;
            };

            textBox.Loaded += onLoaded;
        }

        bool tryGetBinding()
        {
            // ===> Here I get null value
            var binding = BindingOperations.GetBinding(textBox, TextBox.TextProperty);

            if (binding == null)
                return false;

            if (GetIsActivated(textBox))
            {
                var sourceObject = binding.Path.Path;
                //Doing some stuff...
            }
            else
            {
                //Doing some stuff...
            }

            return true;
        };
    }
}