簡體   English   中英

WPF - 綁定到只讀附加屬性。 需要解決方案說明

[英]WPF - binding to read-only attached property. Need solution clarification

我試圖將我的視圖模型屬性綁定到文本框的 Validation.HasErrors 附加屬性(這是只讀的)。 我在 Johan Larsson 的這個答案中找到了一個很好的工作解決方案: https ://stackoverflow.com/a/39392158 但我不是 WPF 專家,所以我很難理解它的工作原理和原因。 我真的很困惑,因為我不知道 WPF 和 XAML 引擎的所有隱含規則。 我了解附加屬性、綁定和 XAML 標記的基礎知識,但我不明白它們最終是如何結合在一起的。 有人可以澄清這里發生了什么嗎? 這是解決方案中的代碼:

public static class OneWayToSource
{
    public static readonly DependencyProperty BindingsProperty = DependencyProperty.RegisterAttached(
        "Bindings",
        typeof(OneWayToSourceBindings),
        typeof(OneWayToSource),
        new PropertyMetadata(default(OneWayToSourceBindings), OnBinidngsChanged));

    public static void SetBindings(this FrameworkElement element, OneWayToSourceBindings value)
    {
        element.SetValue(BindingsProperty, value);
    }

    [AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
    [AttachedPropertyBrowsableForType(typeof(FrameworkElement))]
    public static OneWayToSourceBindings GetBindings(this FrameworkElement element)
    {
        return (OneWayToSourceBindings)element.GetValue(BindingsProperty);
    }

    private static void OnBinidngsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((OneWayToSourceBindings)e.OldValue)?.ClearValue(OneWayToSourceBindings.ElementProperty);
        ((OneWayToSourceBindings)e.NewValue)?.SetValue(OneWayToSourceBindings.ElementProperty, d);
    }
}

public class OneWayToSourceBindings : FrameworkElement
{
    private static readonly PropertyPath DataContextPath = new PropertyPath(nameof(DataContext));
    private static readonly PropertyPath HasErrorPath = new PropertyPath($"({typeof(Validation).Name}.{Validation.HasErrorProperty.Name})");
    public static readonly DependencyProperty HasErrorProperty = DependencyProperty.Register(
        nameof(HasError),
        typeof(bool),
        typeof(OneWayToSourceBindings),
        new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

    internal static readonly DependencyProperty ElementProperty = DependencyProperty.Register(
        "Element",
        typeof(UIElement),
        typeof(OneWayToSourceBindings),
        new PropertyMetadata(default(UIElement), OnElementChanged));

    private static readonly DependencyProperty HasErrorProxyProperty = DependencyProperty.RegisterAttached(
        "HasErrorProxy",
        typeof(bool),
        typeof(OneWayToSourceBindings),
        new PropertyMetadata(default(bool), OnHasErrorProxyChanged));

    public bool HasError
    {
        get { return (bool)this.GetValue(HasErrorProperty); }
        set { this.SetValue(HasErrorProperty, value); }
    }

    private static void OnHasErrorProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        d.SetCurrentValue(HasErrorProperty, e.NewValue);
    }

    private static void OnElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (e.NewValue == null)
        {
            BindingOperations.ClearBinding(d, DataContextProperty);
            BindingOperations.ClearBinding(d, HasErrorProxyProperty);
        }
        else
        {
            var dataContextBinding = new Binding
                                         {
                                             Path = DataContextPath,
                                             Mode = BindingMode.OneWay,
                                             Source = e.NewValue
                                         };
            BindingOperations.SetBinding(d, DataContextProperty, dataContextBinding);

            var hasErrorBinding = new Binding
                                      {
                                          Path = HasErrorPath,
                                          Mode = BindingMode.OneWay,
                                          Source = e.NewValue
                                      };
            BindingOperations.SetBinding(d, HasErrorProxyProperty, hasErrorBinding);
        }
    }
}

Xaml 部分:

<StackPanel>
    <TextBox Text="{Binding ValueInVM, UpdateSourceTrigger=PropertyChanged}">
        <local:OneWayToSource.Bindings>
            <local:OneWayToSourceBindings HasError="{Binding ValueInVM}" />
        </local:OneWayToSource.Bindings>
    </TextBox>
    <CheckBox IsChecked="{Binding ValueInVM, Mode=OneWay}" />
</StackPanel>

您可能會發現這個問題/答案更有幫助。 最受好評的答案(但不是公認的答案......)有一篇相關的博客文章,其中詳細介紹了一些細節。

那篇博文包括PushBindingPushBindingManager - 您需要讓這一切正常工作的兩個主要部分(加上其他幾個小類,但它們並不是什么大事)。

這些比您在帖子中引用的代碼做得更好(在我看來)。 這是我在自己的代碼中使用的(或者,至少是我從中派生出來的)。


首先,要考慮的最關鍵的一點:

只讀依賴屬性沒有公共屬性設置器。 查看Microsoft 文檔中的示例FishCount屬性:

    internal static readonly DependencyPropertyKey FishCountPropertyKey =
        DependencyProperty.RegisterReadOnly(
          name: "FishCount",
          propertyType: typeof(int),
          ownerType: typeof(Aquarium),
          typeMetadata: new FrameworkPropertyMetadata());

    // Declare a public get accessor.
    public int FishCount =>
        (int)GetValue(FishCountPropertyKey.DependencyProperty);

您可以看到只有一個公共getter - 沒有setter 現在,讓我們添加只讀附加屬性狀態的文檔(強調我的)

只讀附加屬性是一種罕見的方案,因為附加屬性的主要方案是它在 XAML 中的使用。 如果沒有公共設置器,則無法在XAML 語法中設置附加屬性。


所以 - 如果我們不能在 XAML 中設置它 - 也許我們可以在 C# 中設置它。

這就是您引用的代碼的作用。

解決這個問題的一般方法是:

  • 提供支持通過 XAML進行綁定的方法(通常是附加屬性)(即,讀寫屬性)
  • 掛鈎到上面的👆,以便在進行更改時,在 C# 中執行一些邏輯,這些邏輯將:
    • 只讀目標屬性(例如Validation.HasErrors )和特定於實現的屬性(我們稱之為ListenerProperty )之間創建OneWay綁定
    • 訂閱ListenerProperty的屬性更改事件。
    • ListenerProperty更改時,更新第二個特定於實現的屬性,例如... MirrorProperty到相同的值。
    • MirrorProperty和您的視圖模型之間創建一個OneWayToSource綁定,以將值推送回您的視圖模型

我已經說明了PushBinding的工作原理(我鏈接的博客文章中的示例)

PushBinding 如何工作的插圖

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM