无法获取实现类的属性

时间:2017-09-14 13:40:18

标签: c# .net wpf

我有一个行为,它采用显示名称属性,并在自动生成时设置数据网格的列标题。当网格绑定到一个特定类型的集合时,我工作正常。如果我有一些基类型的集合它将无法工作,虽然如果我不使用我的行为,它将没有问题从基类自动生成派生类的列。

当集合类型是基类时,找到的唯一属性来自基类,我希望能够显示实现集合中的属性。

想法?

问题是: DataGridAutoGeneratingColumnEventArgs.PropertyDescriptor给出集合的声明值类型的属性信息,而不是实际的项类型。

行为代码:

        #region Setup

    /// <summary>
    /// Called when [attached].
    /// </summary>
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.AutoGeneratingColumn += HandleAutoGeneratingColumns;
    }

    /// <summary>
    /// Called when [detaching].
    /// </summary>
    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.AutoGeneratingColumn -= HandleAutoGeneratingColumns;
    }

    #endregion

    #region Helpers

    /// <summary>
    /// Handles the automatic generating columns.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="dataGridAutoGeneratingColumnEventArgs">The <see cref="DataGridAutoGeneratingColumnEventArgs"/> instance containing the event data.</param>
    private void HandleAutoGeneratingColumns(object sender, DataGridAutoGeneratingColumnEventArgs dataGridAutoGeneratingColumnEventArgs)
    {
        if (AssociatedObject != null)
        {
            var displayName = GetPropertyDisplayName(dataGridAutoGeneratingColumnEventArgs.PropertyDescriptor);

            if (!string.IsNullOrEmpty(displayName))
            {
                dataGridAutoGeneratingColumnEventArgs.Column.Header = displayName;
                dataGridAutoGeneratingColumnEventArgs.Column.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
            }
            else
            {
                dataGridAutoGeneratingColumnEventArgs.Column.Visibility = Visibility.Collapsed;
            }
        }
    }


    /// <summary>
    /// Gets the display name of the property.
    /// </summary>
    /// <param name="descriptor">The descriptor.</param>
    /// <returns></returns>
    [CanBeNull]
    private static string GetPropertyDisplayName(object descriptor)
    {
        string returnValue = null;

        var propertyDescriptor = descriptor as PropertyDescriptor;
        if (propertyDescriptor != null)
        {
            var displayName = propertyDescriptor.Attributes[typeof(DisplayNameAttribute)] as DisplayNameAttribute;
            if (displayName != null && !Equals(displayName, DisplayNameAttribute.Default))
            {
                returnValue = displayName.DisplayName;
            }
        }
        else
        {
            var propertyInfo = descriptor as PropertyInfo;
            if (propertyInfo != null)
            {
                var attributes = propertyInfo.GetCustomAttributes(typeof(DisplayNameAttribute), true);
                foreach (var attribute in attributes)
                {
                    var displayName = attribute as DisplayNameAttribute;
                    if (displayName != null && !Equals(displayName, DisplayNameAttribute.Default))
                    {
                        returnValue = displayName.DisplayName;
                    }
                }
            }
        }

        return returnValue;
    }

来自一个绑定属性的样本

public class DefaultMatchedItems : ILookupItem
{
    #region Properties

    /// <summary>
    /// Gets or sets the first column value.
    /// </summary>
    [DisplayName("Long Name")]
    [CanBeNull]
    public string FirstColumnValue { get; set; }

    /// <summary>
    /// Gets or sets the second column value.
    /// </summary>
    [DisplayName("Short Name")]
    [CanBeNull]
    public string SecondColumnValue { get; set; }

    /// <summary>
    /// Gets or sets the third column value.
    /// </summary>
    [DisplayName("Abbreviation")]
    [CanBeNull]
    public string ThirdColumnValue { get; set; }

    /// <summary>
    /// Gets or sets the identifier.
    /// </summary>
    [Browsable(false)]
    [NotNull]
    public string Identifier { get; set; }


    public interface ILookupItem
{

    /// <summary>
    /// Determines whether [contains] [the specified value].
    /// </summary>
    /// <param name="value">The value.</param>
    /// <param name="ignoreCase">if set to <c>true</c> [ignore case].</param>
    /// <returns>
    ///   <c>true</c> if [contains] [the specified value]; otherwise, <c>false</c>.
    /// </returns>
    bool Contains(string value, bool ignoreCase = true);

    /// <summary>
    /// Gets the display value.
    /// </summary>
    /// <param name="identifier">The identifier.</param>
    /// <returns>The first non blank section of the matching value</returns>
    string GetDisplayValue(string identifier);

}

2 个答案:

答案 0 :(得分:2)

如何填充列

DataGrid自动填充列时,它会使用IItemProperties来查询其Items集合视图,了解哪些属性可用。你看到的是如何解决这些属性的结果。相关逻辑由CollectionView类提供,步骤如下:

  1. 如果该集合实现ITypedList,它将使用ITypedList.GetItemProperties()
  2. 如果该集合为除 IEnumerable<T>之外的任何T 实施System.Object,则会使用TypeDescriptor.GetProperties(typeof(T))
  3. 如果集合中有项目,它会从集合的开头抓取一个“代表项目”,并且:
    1. 如果代表项目实施ICustomTypeProvider,则使用ICustomTypeProvider.GetCustomType().GetProperties()
    2. 它回退到TypeDescriptor.GetProperties(representativeItem)
  4. 如果流程在步骤(3)中失败,因为没有可用于检查的项目,DataGrid将推迟列生成,直到添加项目为止,此时它将再次尝试。

    这对你有何影响

    如果您的ItemsSourceIEnumerable<ILookupItem>,则只会查看ILookupItem来生成列。

    您可以通过在解析项目属性时强制CollectionView使用其他策略来解决此问题。例如,您可以绑定到IEnumerable<DefaultMatchedItems>IEnumerable<object>;让你的收藏工具ITypedList;或ILookupItem实施ICustomTypeProvider

    有趣的是,虽然DataGrid依靠IItemProperties来解析项属性,但使用此界面来查询其Items集合视图。它将尝试直接探测其ItemsSource,即使它实现了IItemProperties。我一直觉得很奇怪。

答案 1 :(得分:0)

DataGridAutoGeneratingColumnEventArgs.PropertyDescriptor为您提供绑定到AssociatedObject.ItemsSource的集合的已声明项类型的属性信息。

如果我绑定类型ObservableCollection<ItemBase>(或List<ItemBase>的集合),我可以使用以下类重新创建它,并使用ItemSub的实例填充它。

public class ItemBase
{
    [DisplayName("Base Foo")]
    public virtual String Foo { get; set; }
    [DisplayName("All Bar")]
    public virtual String Bar { get; set; }
}

public class ItemSub : Item
{
    [DisplayName("Sub Foo")]
    public override String Foo { get; set; }
}

对于Fooe.PropertyDescriptor.DisplayName是“Base Foo”。

如果我将集合类型更改为ObservableCollection<Object>(仅用于测试目的 - object的集合通常不是很好的做法),e.PropertyDescriptor.DisplayName是“Sub Foo”。如果我然后将集合中 first 项的类型更改为ItemBase,我会得到“Base Foo”。

所以你最好的举动可能是从事件args获取属性名称,但是转到AssociatedObject.Items以获取集合中实际项目的运行时类型,并使用该类型的属性。