【问题标题】:Replacing non-trivial getters with dependency properties用依赖属性替换非平凡的 getter
【发布时间】:2012-09-09 16:26:56
【问题描述】:

我最近开始试验 DataBinding 并为我的自定义类实现 DependencyProperties。这一切都很好,而且可能性令人兴奋,但是,我遇到了一个可能只能通过稍微修改整体类设计来解决的问题。而且我想确保这是唯一的选择,而且我没有遗漏任何东西。

因此,我的类存储有关用户导入应用程序的视频文件的信息。在其他属性中,它包含:

public class VideoFile {

    public string FilePath { get; protected set; }
    public uint ID { get; protected set; ]
    public string Extension { get { return Path.GetExtension(FilePath); } }
    public string FileName { get { return Path.GetFilename(FilePath); } }

}

所以,我已经成功地将 FilePath 替换为 DependencyProperty。但是,在 UI 中,我主要只想显示文件名,它使用一些逻辑来提供其值。据我所知,这是我的选择:

  1. 我可以简单地为 FileName 和 Extension 创建 DependencyProperties,并在构造函数中设置它们的值,但这是多余的;我已经在 FilePath 中拥有该信息,因此我想避免使用此选项。
  2. 创建 ValueConverters,一个用于显示文件名,一个用于显示扩展名,并在我的绑定中使用它们。

我只是简单地遇到了 ValueConverters,所以我不确定。我可以将它们用于此目的吗?或者,我是否刚刚遇到了它们存在的主要原因之一? :)

最后但并非最不重要的一点是,当 ValueConverter 不是正确的方法时,谁能想到类似的情况?我想避免直接跳入它们,只是意识到它不起作用,因为“那个”属性不能以这种方式表达。

【问题讨论】:

  • 为什么要将这些转换为DependencyProperty?我会从发布的代码中怀疑您不需要。
  • @DanPuzey 因为我想在 UI 中显示它们。更具体地说,一个列表框,其中每个项目代表此类的一个实例。带有缩略图、文件名、以 MB 为单位的大小和以秒为单位的持续时间。虽然这些属性预计不会改变,但我想在不同的地方显示它们,我厌倦了编写 for 循环和在代码中创建控件。我想要一个漂亮、干净的 XAML 和一个摇摆不定的 DataTemplate :)
  • 您不需要 DependencyProperty 就可以在 UI 中显示它们。事实上,我建议您应该避免这种不必要的开销。 (请参阅我的答案以获取替代方案。)

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


【解决方案1】:

您不需要DependencyProperties。当您要使用MarkupExtension 将属性设置为时,您只需要DependencyProperty,我怀疑您是否使用模型类来执行此操作(因为您不会声明Xaml 中的此类!)。

更轻量级的方法是使用INotifyPropertyChanged。这是一个 .NET 3.5 风格的实现:

public class VideoFile : INotifyPropertyChanged
{
    private string _filePath;

    public string FilePath
    {
        get
        {
            return _filePath;
        }
        protected set
        {
            _filePath = value;
            OnPropertyChanged("FilePath");
            OnPropertyChanged("Extension");
            OnPropertyChanged("FileName");
        }
    }

    public uint ID { get; protected set; }
    public string Extension { get { return Path.GetExtension(FilePath); } }
    public string FileName { get { return Path.GetFileName(FilePath); } }

    protected void OnPropertyChanged(string propName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

(在 .NET 4.5 中,由于新的 [CallerMemberName] 属性,这可以稍微简化。)

唯一的缺点是您的属性需要支持字段。但是,有一个名为 NotifyPropertyWeaver 的 VS 扩展可以自动完成部分工作,并且也无需显式支持属性。

【讨论】:

  • 这很好,直到您有几个属性在其他属性上引发 PropertyChanged,然后您无法分辨某些设置的头部或尾部,这是调试的噩梦。这就是我反对它的原因。
  • 感谢详细的实现,支持字段不是问题。但是,我仍然认为我需要一个依赖属性。据我了解,以这种方式实现它将保持 UI 自动更新,所以当我说 Label1.Content = VideoFile1.FileName 时,一旦属性更改,标签就会更新。但是我在 XAML 中声明了一个列表框,并且在该列表框中,我想为列表项编写一个自定义 DataTemplate。在该模板中,我想创建标签和图像,并将它们绑定到这些属性......而且我认为这在这种情况下对我没有帮助。我错了吗?
  • 你错了。 INotifyProperyChanged 是您自动更新 UI 所需的全部内容,包括自定义 DataTemplateDependencyProperty 会起作用,但对于这种用途来说太过分了。还有@Baboon:PropertyChangedDependencyProperty 更难调试吗?
  • 我建议不要在一个 setter 中为多个属性提高 PropertyChanged,我从未暗示过你所说的。
  • DependencyProperties 有什么过火之处?在绑定方面,它们比 INotifyPropertyChanged 实现更快。 See this SO thread.
【解决方案2】:

不要重复数据。

首选BindingIValueConverter,因为这样,每当FilePath 更改时,ExtensionFileName 也会在 UI 中更新。

当然,您也可以在FilePath 的设置器中为他们提高PropertyChanged,但这是不好的做法,因为FilePath 不应该关心谁/什么使用它。

然后类看起来像:

public class VideoFile : INotifyPropertyChanged {

    string m_FilePath;
    public string FilePath 
    { 
       get { return m_FilePath; } 
       protected set
       {
          if(value != m_FilePath)
          {
             m_FilePath = value;
             RaisePropertyChanged(() => this.FilePath);
          }
       }
    }
    public uint ID { get; protected set; }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged<T>(Expression<Func<T>> _PropertyExpression)
    {
        RaisePropertyChanged(PropertySupport.ExtractPropertyName(_PropertyExpression));
    }

    protected void RaisePropertyChanged(String _Prop)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(_Prop));
        }
    }

    #endregion
}

请注意,PropertySupport 是 Prism 的一部分,但您可以通过调用 RaisePropertyChanged("FilePath") 来实现它,它只是具有类型安全性,因为如果您更改属性的名称,您将遇到编译时错误。

【讨论】:

  • 计算出来的属性不是重复数据,转换器很贵。我建议使用 PropertyChanged 更有意义,如果这个类是作为一个 UI 绑定模型。特别是,当FilePath 更改时,UI 不会为任何 属性重新绑定,除非您还实现了INotifyPropertyChangedDependencyProperty。在这两者中,NotifyPropertyChanged 更轻量级。
  • 是的,他确实需要实现 INotifyPropertyChanged,但是使 FilePath 成为 DependencyProperty 也可以。并且计算属性肯定是数据重复:相同的数据以不同的方式呈现。
  • 以不同的方式呈现但存储一次:这样的类存在的原因。这就是为什么,例如,FileInfo 有多个属性来返回文件名的一部分。与使用转换器相比,它不再重复数据:它是相同的代码,封装不同。如果你建议他将FilePath 变成DependencyProperty,你应该在你的代码中包含那个实现。
  • @DanPuzey 谢谢,这很有帮助。我将深入研究 INotifyPropertyChanged,看看它在我的场景中是如何工作的。
  • 那你为什么接受了反对它的答案?
【解决方案3】:

据我了解,您只想在 UI 上显示文件名。然后,您可以考虑在 FilePath 依赖属性更改时更新 FileName 属性(OnChangedFilePath 方法)。您还可以在 ValidateFilePath 方法中检查 FilePath 是否正常。请注意,FileName 也必须是依赖属性或支持 IPropertyChanged,否则更改时 UI 不会更新。您不需要为此使用转换器。

public string FilePath
{
    get { return (string)GetValue(FilePathProperty); }
    set { SetValue(FilePathProperty, value); }
}

private static object CoerceFilePath(DependencyObject d, object value)
{
    return value;
}

private static bool ValidateFilePath(object Value)
{
    return true;
}

private static void OnChangedFilePath(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}

public static readonly DependencyProperty FilePathProperty =
    DependencyProperty.Register("FilePath", typeof(string), typeof(ClassName),
    new PropertyMetadata(@"C:\File.avi", OnChangedFilePath, CoerceFilePath),
    new ValidateValueCallback(ClassName.ValidateFilePath));

【讨论】:

  • 这不是我想要的,但它给了我一些与这个问题无关的其他事情的想法,所以无论如何谢谢:)
猜你喜欢
  • 2021-12-30
  • 2012-10-17
  • 2012-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-16
  • 1970-01-01
  • 2019-08-14
相关资源
最近更新 更多