是否有一种*干净的*方法可以使只读依赖属性反映另一个属性的值?

发布于 2024-08-02 12:07:14 字数 2016 浏览 3 评论 0原文

下面的代码是我当前的解决方案。

我试图模仿的一个很好的例子是 FrameworkElement.ActualWidth 属性。您知道每当 Width 属性更改或重新绘制控件时,如何计算和重新分配 ActualWidth 属性,或者还有什么时候? ------

从开发人员的角度来看,它看起来就像是在努力进行数据绑定。
但 ActualWidth 是只读依赖属性。微软真的需要经历这个巨大的代码垃圾坑才能实现这一点吗?或者是否有更简单的方法来利用数据绑定系统的现有功能?

public class foo : FrameworkElement
{
    [ValueConversion(typeof(string), typeof(int))]
    public class fooConverter : IValueConverter
    {   public object Convert(  object value, Type targetType,
                                object parameter, CultureInfo culture)
        { ... }
        public object ConvertBack(  object value, Type targetType,
                                    object parameter, CultureInfo culture)
        { ... }
    }

    private static readonly fooConverter fooConv = new fooConverter();



    private static readonly DependencyPropertyKey ReadOnlyIntPropertyKey =
        DependencyProperty.RegisterReadOnly( "ReadOnlyInt", typeof(int),
                                             typeof(foo), null);
    public int ReadOnlyInt
    {   get { return (int)GetValue(ReadOnlyIntPropertyKey.DependencyProperty); }
    }



    public static readonly DependencyProperty ReadWriteStrProperty =
        DependencyProperty.Register( "ReadWriteStr", typeof(string), typeof(foo),
                                     new PropertyMetadata(ReadWriteStr_Changed));
    public string ReadWriteStr
    {   get { return (string)GetValue(ReadWriteStrProperty); }
        set { SetValue(ReadWriteStrProperty, value); }
    }



    private static void ReadWriteStr_Changed(   DependencyObject d,
                                            DependencyPropertyChangedEventArgs e)
    {   try
        {   if (d is foo)
            {   foo f = d as foo;
                f.SetValue( ReadOnlyIntPropertyKey,
                            fooConv.Convert(f.ReadWriteStr, typeof(int), null,
                                            CultureInfo.CurrentCulture));
            }
        }
        catch { }
    }
}

The code below is my current solution.

A great example of what I am trying to mimic would be the FrameworkElement.ActualWidth property. You know how the ActualWidth property is calculated and reassigned, whenever the Width property changes, or whenever the control is redrawn, or whenever else? ------

From the developer's perspective, it just looks like data-binding hard-at-work.
But ActualWidth is a read-only dependency-property. Does Microsoft really have to go through this gigantic trash-hole of code to make that work? Or is there a simpler way that utilizes the existing functionality of the data-binding system?

public class foo : FrameworkElement
{
    [ValueConversion(typeof(string), typeof(int))]
    public class fooConverter : IValueConverter
    {   public object Convert(  object value, Type targetType,
                                object parameter, CultureInfo culture)
        { ... }
        public object ConvertBack(  object value, Type targetType,
                                    object parameter, CultureInfo culture)
        { ... }
    }

    private static readonly fooConverter fooConv = new fooConverter();



    private static readonly DependencyPropertyKey ReadOnlyIntPropertyKey =
        DependencyProperty.RegisterReadOnly( "ReadOnlyInt", typeof(int),
                                             typeof(foo), null);
    public int ReadOnlyInt
    {   get { return (int)GetValue(ReadOnlyIntPropertyKey.DependencyProperty); }
    }



    public static readonly DependencyProperty ReadWriteStrProperty =
        DependencyProperty.Register( "ReadWriteStr", typeof(string), typeof(foo),
                                     new PropertyMetadata(ReadWriteStr_Changed));
    public string ReadWriteStr
    {   get { return (string)GetValue(ReadWriteStrProperty); }
        set { SetValue(ReadWriteStrProperty, value); }
    }



    private static void ReadWriteStr_Changed(   DependencyObject d,
                                            DependencyPropertyChangedEventArgs e)
    {   try
        {   if (d is foo)
            {   foo f = d as foo;
                f.SetValue( ReadOnlyIntPropertyKey,
                            fooConv.Convert(f.ReadWriteStr, typeof(int), null,
                                            CultureInfo.CurrentCulture));
            }
        }
        catch { }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

围归者 2024-08-09 12:07:14

不幸的是,您将需要您拥有的大部分东西。在这种情况下不需要 IValueConverter,因此您可以将其简化为:

public class foo : FrameworkElement
{
    private static readonly DependencyPropertyKey ReadOnlyIntPropertyKey =
        DependencyProperty.RegisterReadOnly( "ReadOnlyInt", typeof(int),
                                         typeof(foo), null);
    public int ReadOnlyInt
    {
       get { return (int)GetValue(ReadOnlyIntPropertyKey.DependencyProperty); }
    }

    public static readonly DependencyProperty ReadWriteStrProperty =
        DependencyProperty.Register( "ReadWriteStr", typeof(string), typeof(foo),
                                 new PropertyMetadata(ReadWriteStr_Changed));

    public string ReadWriteStr
    {
       get { return (string)GetValue(ReadWriteStrProperty); }
        set { SetValue(ReadWriteStrProperty, value); }
    }

    private static void ReadWriteStr_Changed(DependencyObject d,
                                        DependencyPropertyChangedEventArgs e)
    {
         foo f = d as foo;
         if (f != null)
         {
              int iVal;
              if (int.TryParse(f.ReadWriteStr, out iVal))
                  f.SetValue( ReadOnlyIntPropertyKey, iVal);
         }
    }
}

Unfortunately, you'll need most of what you have. The IValueConverter isn't required in this case, so you could simplify it down to just:

public class foo : FrameworkElement
{
    private static readonly DependencyPropertyKey ReadOnlyIntPropertyKey =
        DependencyProperty.RegisterReadOnly( "ReadOnlyInt", typeof(int),
                                         typeof(foo), null);
    public int ReadOnlyInt
    {
       get { return (int)GetValue(ReadOnlyIntPropertyKey.DependencyProperty); }
    }

    public static readonly DependencyProperty ReadWriteStrProperty =
        DependencyProperty.Register( "ReadWriteStr", typeof(string), typeof(foo),
                                 new PropertyMetadata(ReadWriteStr_Changed));

    public string ReadWriteStr
    {
       get { return (string)GetValue(ReadWriteStrProperty); }
        set { SetValue(ReadWriteStrProperty, value); }
    }

    private static void ReadWriteStr_Changed(DependencyObject d,
                                        DependencyPropertyChangedEventArgs e)
    {
         foo f = d as foo;
         if (f != null)
         {
              int iVal;
              if (int.TryParse(f.ReadWriteStr, out iVal))
                  f.SetValue( ReadOnlyIntPropertyKey, iVal);
         }
    }
}
梦冥 2024-08-09 12:07:14

恕我直言,它并不像您建议的那么糟糕...

您可以摆脱转换器: IValueConverter 用于绑定,您不需要它来进行代码隐藏中的转换。除此之外,我不知道如何使其更简洁......

It's not as bad as you suggest, IMHO...

You could get rid of the converter : IValueConverter is for bindings, you don't need it for conversions in code-behind. Apart from that, I don't see how you could make it more concise...

a√萤火虫的光℡ 2024-08-09 12:07:14

是的,有一种简洁的方法可以“使 只读 DependencyProperty 反映另一个属性的值”,但它可能需要对整体属性编程进行相当根本性的转变您的应用程序的型号。简而言之,每个只读 DependencyProperty 都可以具有一个 CoerceValue 回调,它通过拉取它所依赖的所有源值来构建自己的值。

在此方法中,传递给 CoerceValue 的“value”参数将被忽略。相反,每个 DP 的 CoerceValue 函数都会“从头开始”重新计算其值,方法是直接从传递给 CoerceValueDependencyObject 实例中获取所需的任何值(如果您想避免转换为所有者实例类型,则可以使用 dobj.GetValue(...) 来实现此目的。

尝试抑制任何怀疑,即忽略提供给 CoerceValue 的值可能会浪费某些东西。如果您坚持此模型,这些值将永远不会有用,并且整体工作与“推送”模型相同或更少,因为未更改的源值一如既往地由 DP 系统缓存。所改变的只是谁负责计算以及在哪里完成。这里的好处是,每个 DP 值的计算始终集中在一个地方,并且与该 DP 专门关联,而不是分散在整个应用程序中。

您可以丢弃此模型中的DependencyPropertyKey,因为您永远不需要它。相反,要更新任何只读 DP 的值,您只需在所有者实例上调用 CoerceValueInvalidateValue 来指示所需的 DP。这是可行的,因为这两个函数不需要 DP key,它们使用公共 DependencyProperty 标识符,并且它们是公共函数,因此任何代码都可以从任何地方调用它们。

至于何时何地放置这些 CoerceValue/InvalidateValue 调用,有两个选项:

  • Eager:提到的每个(源)DP 的PropertyChangedCallback中对(目标)DP 进行 InvalidateValue 调用(目标)DP 的 CoerceValueCallback 函数,
      --或--
  • 懒惰:  始终在获取其值之前立即在 DP 上调用 CoerceValue

确实,此方法对 XAML 不太友好,但这不是 OP 问题的要求。然而,考虑到在这种方法中,您甚至根本不需要获取或保留 DependencyPropertyKey,如果您能够围绕“拉”语义重新构思您的应用程序。


以完全独立的方式,还有另一种可能更简单的解决方案:

DependencyObject 上公开 INotifyPropertyChanged 并使用 CLR 属性作为只读属性,该属性现在将具有一个简单的支持字段。是的,WPF 绑定系统将正确检测和监视同一类实例上的两种机制 - DependencyPropertyINotifyPropertyChanged。建议使用私有或其他 setter 来推送对此只读属性的更改,并且此 setter 应检查支持字段以检测空(冗余)更改,否则会引发旧式 CLR PropertyChanged事件。

要绑定到此只读属性,请使用所有者的 OnPropertyChanged 重载(用于自绑定)来推送来自 DP 的更改,或者,要从任意外部属性绑定,请使用 System .ComponentModel.DependencyPropertyDescriptor.FromProperty 获取相关源 DP 的 DependencyPropertyDescriptor,并使用其 AddValueChanged 方法设置推送新值的处理程序。

当然,对于非 DP 属性或非 DependencyObject 实例,您只需订阅其 INotifyPropertyChanged 事件即可监视可能影响只读属性的更改。在任何情况下,无论您以哪种方式将更改推送到只读属性,其 setter 引发的事件都会确保对只读属性的更改正确地向前传播到任何进一步的依赖属性,无论是 WPF/DP、CLR、数据- 绑定或其他方式。

Yes, there is a clean way to "make a read-only DependencyProperty reflect the value of another property," but it may require a pretty fundamental shift in the overall property programming model of your app. In short, instead of using the DependencyPropertyKey to push values into the property, every read-only DependencyProperty can have a CoerceValue callback which builds its own value by pulling all the source values it depends on.

In this approach, the 'value' parameter that's passed in to CoerceValue is ignored. Instead, each DP's CoerceValue function recalculates its value "from scratch" by directly fetching whatever values it needs from the DependencyObject instance passed in to CoerceValue (you can use dobj.GetValue(...) for this if you want to avoid casting to the owner instance type).

Try to suppress any suspicion that ignoring the value supplied to CoerceValue may be wasting something. If you adhere to this model, those values will never be useful and the overall work is the same or less than a "push" model because source values that haven't changed are, as always, cached by the DP system. All that's changed is who's responsible for the calculation and where it's done. What's nice here is that calculation of each DP value is always centralized in one place and specifically associated with that DP, rather than strewn across the app.

You can throw away the DependencyPropertyKey in this model because you'll never need it. Instead, to update the value of any read-only DP you just call CoerceValue or InvalidateValue on the owner instance, indicating the desired DP. This works because those two functions don't require the DP key, they use the public DependencyProperty identifier instead, and they're public functions, so any code can call them from anywhere.

As for when and where to put these CoerceValue/InvalidateValue calls, there are two options:

  • Eager:   Put an InvalidateValue call for the (target) DP in the PropertyChangedCallback of every (source) DP that's mentioned in the (target) DP's CoerceValueCallback function,
      --or--
  • Lazy:   Always call CoerceValue on the DP immediately prior to fetching its value.

It's true that this method is not so XAML-friendly, but that wasn't a requirement of the OPs question. Considering, however, that in this approach you don't ever even need to fetch or retain the DependencyPropertyKey at all, it seems like it might one of the sleekest ways to go, if you're able to reconceive your app around the "pull" semantics.


In a completely separate vein, there's yet another solution that may be even simpler:

Expose INotifyPropertyChanged on your DependencyObject and use CLR properties for the read-only properties, which will now have a simple backing field. Yes, the WPF binding system will correctly detect and monitor both mechanisms--DependencyProperty and INotifyPropertyChanged--on the same class instance. A setter, private or otherwise, is recommended for pushing changes to this read-only property, and this setter should check the backing field to detect vacuous (redundant) changes, otherwise raising the old-style CLR PropertyChanged event.

For binding to this read-only property, either use the owner's OnPropertyChanged overload (for self-binding) to push in the changes from DPs, or, for binding from arbitrary external properties, use System.ComponentModel.DependencyPropertyDescriptor.FromProperty to get a DependencyPropertyDescriptor for the relevant souce DPs, and use its AddValueChanged method to set a handler which pushes in new values.

Of course for non-DP properties or non-DependencyObject instances, you can just subscribe to their INotifyPropertyChanged event to monitor changes that might affect your read-only property. In any case, no matter which way you push changes into the read-only property, the event raised by its setter ensures that changes to the read-only property correctly propagate onwards to any further dependent properties, whether WPF/DP, CLR, data-bound or otherwise.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文