我有一个行为,它采用显示名称属性,并在自动生成时设置数据网格的列标题。当网格绑定到一个特定类型的集合时,我工作正常。如果我有一些基类型的集合它将无法工作,虽然如果我不使用我的行为,它将没有问题从基类自动生成派生类的列。
当集合类型是基类时,找到的唯一属性来自基类,我希望能够显示实现集合中的属性。
想法?
问题是:
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);
}
答案 0 :(得分:2)
当DataGrid
自动填充列时,它会使用IItemProperties
来查询其Items
集合视图,了解哪些属性可用。你看到的是如何解决这些属性的结果。相关逻辑由CollectionView
类提供,步骤如下:
ITypedList
,它将使用ITypedList.GetItemProperties()
。IEnumerable<T>
之外的任何T
实施System.Object
,则会使用TypeDescriptor.GetProperties(typeof(T))
。ICustomTypeProvider
,则使用ICustomTypeProvider.GetCustomType().GetProperties()
。TypeDescriptor.GetProperties(representativeItem)
。 如果流程在步骤(3)中失败,因为没有可用于检查的项目,DataGrid
将推迟列生成,直到添加项目为止,此时它将再次尝试。
如果您的ItemsSource
是IEnumerable<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; }
}
对于Foo
,e.PropertyDescriptor.DisplayName
是“Base Foo”。
如果我将集合类型更改为ObservableCollection<Object>
(仅用于测试目的 - object
的集合通常不是很好的做法),e.PropertyDescriptor.DisplayName
是“Sub Foo”。如果我然后将集合中 first 项的类型更改为ItemBase
,我会得到“Base Foo”。
所以你最好的举动可能是从事件args获取属性名称,但是转到AssociatedObject.Items
以获取集合中实际项目的运行时类型,并使用该类型的属性。