是否有* clean *方法使只读依赖项属性反映另一个属性的值?

时间:2009-08-19 17:39:04

标签: c# .net wpf data-binding dependency-properties

以下代码是我目前的解决方案。

如果您花了几分钟时间弄清楚这段代码在做什么,我听到了

如果有的话,这是一个丑陋的混乱。我会杀死看到另一种选择,(但不要让那些阻止你回应...... :-)。哦,jeez,我甚至通过删除转换器代码来缩小它(基本上),当我查看这段代码时,我仍感到诵读困难。

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

从开发人员的角度来看,它看起来就像数据绑定一样勤奋 但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 { }
    }
}

3 个答案:

答案 0 :(得分:4)

不幸的是,你需要你拥有的大部分东西。在这种情况下不需要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);
         }
    }
}

答案 1 :(得分:1)

它没有你的建议那么糟糕,恕我直言......

你可以摆脱转换器:IValueConverter用于绑定,你不需要它用于代码隐藏中的转换。除此之外,我看不出你怎么能让它更简洁......

答案 2 :(得分:1)

是的,有一种干净的方式可以“让只读 DependencyProperty反映另一个属性的值”,但这可能需要在整体属性编程中进行相当根本的转变你的应用程序的模型。简而言之,不是将DependencyPropertyKey 推送 值用于属性,而是每个只读DependencyProperty都可以有 CoerceValue < / strong>回调,它通过 所依赖的所有源值来构建自己的值。

在这种方法中,忽略了传递给CoerceValue的'value'参数。相反,每个DP的CoerceValue函数通过直接从传递到DependencyObject的{​​{1}}实例中获取所需的任何值,从头开始重新计算其值(您可以使用CoerceValue这个,如果你想避免强制转换为所有者实例类型)。

尽量避免怀疑忽略提供给dobj.GetValue(...)的值可能会浪费某些东西。如果您遵守此模型,那么这些值将永远不会有用,并且整体工作与“推送”模型相同或更小,因为DP系统一如既往地缓存未更改的源值。所有改变的是谁负责计算以及完成的工作。这里的好处是每个DP值的计算总是集中在一个地方,并且特别与该DP相关联,而不是遍布应用程序。

你可以扔掉这个模型中的CoerceValue因为你永远不需要它。相反,要更新任何只读DP的值,只需在所有者实例上调用DependencyPropertyKeyCoerceValue,即可指示所需的DP。这是因为这两个函数不需要DP ,它们使用公共InvalidateValue标识符,而它们是公共函数,所以任何代码可以从任何地方打电话给他们。

关于何时何地进行这些DependencyProperty / CoerceValue来电,有两种选择:

  • 渴望: 在每个(来源)DP的InvalidateValue中对(目标)DP进行InvalidateValue调用 (目标)DP的PropertyChangedCallback功能中提及 - 或 -
  • 懒惰: 在获取其值之前,请务必立即在DP上调用CoerceValueCallback

这种方法确实不是XAML友好的,但这不是OP问题的要求。但是,考虑到在这种方法中你甚至根本不需要获取或保留CoerceValue,如果你能够重新认识你的app,它似乎可能是最简单的方法之一。围绕“拉”语义。

在完全独立的情况下,还有另一种解决方案可能更简单:

DependencyPropertyKey上公开INotifyPropertyChanged,并将CLR属性用于只读属性,这些属性现在将具有一个简单的支持字段。是的, WPF 绑定系统将在同一个类实例上正确检测和监控这两种机制 - DependencyObjectDependencyProperty。建议使用私有或其他方式将set更改推送到此只读属性,此setter应检查支持字段以检测空白(冗余)更改,否则会引发旧式CLR INotifyPropertyChanged事件。 / p>

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

当然,对于非DP属性或非DependencyPropertyDescriptor实例,您只需订阅其AddValueChanged事件即可监控可能影响只读属性的更改。在任何情况下,无论您将更改推送到只读属性的方式,其setter引发的事件都可确保对只读属性的更改正确传播到任何其他依赖属性,无论是WPF / DP,CLR还是数据 - 或其他。