Vladimir Pak Vladimir Pak - 3 months ago 36
C# Question

Display names of dynamic DataGrid columns are not shown

Prerequisites: .Net Framework 4.5.1

I'm using

ITypedList
and
ICustomTypeDescriptor
to make
DataGrid
generate columns dynamically. To provide user-friendly column names I create property descriptors providing
DisplayNameAttribute
to its constructor as shown in the example below. Though debugger shows that
PropertyDescriptor.DisplayName
property gets what I provided in the attribute
DataGrid
does not take this value into account and still display property name instead of property display name. Any ideas what I'm doing wrong?

Example WDataGridTest.xaml

<Window x:Class="Local.WGridViewTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:Local"
Title="WGridViewTest" Height="300" Width="300">
<Window.Resources>
<l:DataTable x:Key="DataTable"/>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{StaticResource ResourceKey=DataTable}"/>
</Grid>
</Window>


Code behind WDataGridTest.xaml.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows;
using System.ComponentModel;

namespace Local {
/// <summary>Interaction logic for WGridViewTest.xaml</summary>
public partial class WGridViewTest : Window {
public WGridViewTest() {
InitializeComponent();
}
}

public class DataTable : BindingList<DataRow>, ITypedList {
private PropertyDescriptorCollection _PropertyDescriptors;

public DataTable() :
base() {
AllowNew = false;
AllowRemove = false;
AllowEdit = true;
_PropertyDescriptors = new PropertyDescriptorCollection(new PropertyDescriptor[0], false);
_PropertyDescriptors.Add(new DataValuePropertyDescriptor("Column1"));
_PropertyDescriptors.Add(new DataValuePropertyDescriptor("Column2"));
_PropertyDescriptors.Add(new DataValuePropertyDescriptor("Column3"));
Items.Add(new DataRow(this));
Items.Add(new DataRow(this));
Items.Add(new DataRow(this));
Items.Add(new DataRow(this));
Items.Add(new DataRow(this));
}

#region ITypedList implementation
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] ListAccessors) {
return _PropertyDescriptors;
}
public string GetListName(PropertyDescriptor[] ListAccessors) {
return "Data Table";
}
#endregion ITypedList implementation
}

public class DataRow : ICustomTypeDescriptor {
public DataRow(DataTable DataTable) {
this.DataTable = DataTable;
}

public DataTable DataTable {
get;
private set;
}

public object GetValue(string ColumnName) {
return String.Concat(ColumnName, "@", GetHashCode());
}

public void SetValue(string ColumnName, object Value) {
}

#region ICustomTypeDescriptor implementation
public AttributeCollection GetAttributes() { return AttributeCollection.Empty; }
public string GetClassName() { return GetType().FullName; }
public string GetComponentName() { return GetType().Name; }
public TypeConverter GetConverter() { return null; }
public EventDescriptor GetDefaultEvent() { return null; }
public PropertyDescriptor GetDefaultProperty() { return null; }
public object GetEditor(Type EditorBaseType) { return null; }
public EventDescriptorCollection GetEvents(Attribute[] Attributes) { return EventDescriptorCollection.Empty; }
public EventDescriptorCollection GetEvents() { return EventDescriptorCollection.Empty; }
public PropertyDescriptorCollection GetProperties(Attribute[] Attributes) { return DataTable.GetItemProperties(null); }
public PropertyDescriptorCollection GetProperties() { return DataTable.GetItemProperties(null); }
public object GetPropertyOwner(PropertyDescriptor PropertyDescriptor) { return this; }
#endregion Property value tracking
}

public class DataValuePropertyDescriptor : PropertyDescriptor {
public DataValuePropertyDescriptor(string Name) :
base(Name, new Attribute[] { new DisplayNameAttribute(String.Concat("Display: ", Name)) }) {
}

#region PropertyDescriptor implementation
public override Type ComponentType { get { return typeof(DataRow); } }
public override Type PropertyType { get { return typeof(string); } }
public override bool IsReadOnly { get { return false; } }
public override bool CanResetValue(object DataRow) { return true; }
public override object GetValue(object DataRow) { return ((DataRow)DataRow).GetValue(Name); }
public override void ResetValue(object DataRow) { ((DataRow)DataRow).SetValue(Name, null); }
public override void SetValue(object DataRow, object Value) { ((DataRow)DataRow).SetValue(Name, Value); }
public override bool ShouldSerializeValue(object DataRow) { return false; }
#endregion PropertyDescriptor implementation
}
}


And the result looks like this.

enter image description here

Answer

After trying this example on several other configurations with the same results I ended up overriding default column template as follows below. Just in case if someone would also see this problem this is the workaround, though one should keep in mind that it couples to aero theme because there's no aero2 on windows 7. If you don't need to support windows 7, simply replace aero with aero2 referencing appropriate assembly in your project.

WDataGridTest.xaml

<Window x:Class="Local.WDataGridTest"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Local"
        xmlns:data="clr-namespace:System.Data;assembly=System.Data"
        xmlns:themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero" 
        Title="WGridViewTest" Height="300" Width="300">
    <Window.Resources>
        <local:DataTable x:Key="DataTable"/>
        <local:DataColumnDisplayNameConverter x:Key="DataColumnDisplayNameConverter"/>
        <Style x:Key="DataGridColumnHeaderGripperStyle" TargetType="{x:Type Thumb}">
            <Setter Property="Width" Value="8"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="Cursor" Value="SizeWE"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Thumb}">
                        <Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}"/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Style x:Key="DataGridColumnHeaderStyle" TargetType="{x:Type DataGridColumnHeader}">
            <Setter Property="VerticalContentAlignment" Value="Center"/>
            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
                        <Grid>
                            <themes:DataGridHeaderBorder BorderBrush="{TemplateBinding BorderBrush}" 
                                                         BorderThickness="{TemplateBinding BorderThickness}" 
                                                         Background="{TemplateBinding Background}" 
                                                         IsClickable="{TemplateBinding CanUserSort}" 
                                                         IsPressed="{TemplateBinding IsPressed}" 
                                                         IsHovered="{TemplateBinding IsMouseOver}" 
                                                         Padding="{TemplateBinding Padding}" 
                                                         SortDirection="{TemplateBinding SortDirection}" 
                                                         SeparatorBrush="{TemplateBinding SeparatorBrush}" 
                                                         SeparatorVisibility="{TemplateBinding SeparatorVisibility}">
                                <TextBlock HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                           VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                           TextAlignment="Center"
                                           SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
                                           Text="{Binding Converter={StaticResource ResourceKey=DataColumnDisplayNameConverter}, ConverterParameter={StaticResource ResourceKey=DataTable}}"
                                           TextWrapping="Wrap"/>
                            </themes:DataGridHeaderBorder>
                            <Thumb x:Name="PART_LeftHeaderGripper" HorizontalAlignment="Left" Style="{StaticResource DataGridColumnHeaderGripperStyle}"/>
                            <Thumb x:Name="PART_RightHeaderGripper" HorizontalAlignment="Right" Style="{StaticResource DataGridColumnHeaderGripperStyle}"/>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <Grid>
        <DataGrid ItemsSource="{Binding Source={StaticResource ResourceKey=DataTable}}" ColumnHeaderStyle="{StaticResource ResourceKey=DataGridColumnHeaderStyle}"/>
    </Grid>
</Window>

Code behind WDataGridTest.xaml.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
using System.Globalization;
using System.ComponentModel;

namespace Local {
    /// <summary>Interaction logic for WDataGridTest.xaml</summary>
    public partial class WDataGridTest : Window {
        public WDataGridTest() {
            InitializeComponent();
        }
    }

    public class DataTable : BindingList<DataRow>, ITypedList {
        private PropertyDescriptorCollection _PropertyDescriptors;

        public DataTable() :
            base() {
            AllowNew = false;
            AllowRemove = false;
            AllowEdit = true;
            _PropertyDescriptors = new PropertyDescriptorCollection(new PropertyDescriptor[0], false);
            _PropertyDescriptors.Add(new DataValuePropertyDescriptor("Column1"));
            _PropertyDescriptors.Add(new DataValuePropertyDescriptor("Column1"));
            _PropertyDescriptors.Add(new DataValuePropertyDescriptor("Column1"));
            Items.Add(new DataRow(this));
            Items.Add(new DataRow(this));
            Items.Add(new DataRow(this));
            Items.Add(new DataRow(this));
            Items.Add(new DataRow(this));
        }

        #region ITypedList implementation
        public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] ListAccessors) {
            return _PropertyDescriptors;
        }
        public string GetListName(PropertyDescriptor[] ListAccessors) {
            return "Data Table";
        }
        #endregion ITypedList implementation
    }

    public class DataRow : ICustomTypeDescriptor {
        public DataRow(DataTable DataTable) {
            this.DataTable = DataTable;
        }

        public DataTable DataTable {
            get;
            private set;
        }

        public object GetValue(string ColumnName) {
            return string.Concat(ColumnName, "@", GetHashCode());
        }

        public void SetValue(string ColumnName, object Value) {
        }

        #region ICustomTypeDescriptor implementation
        public AttributeCollection GetAttributes() { return AttributeCollection.Empty; }
        public string GetClassName() { return GetType().FullName; }
        public string GetComponentName() { return GetType().Name; }
        public TypeConverter GetConverter() { return null; }
        public EventDescriptor GetDefaultEvent() { return null; }
        public PropertyDescriptor GetDefaultProperty() { return null; }
        public object GetEditor(Type EditorBaseType) { return null; }
        public EventDescriptorCollection GetEvents(Attribute[] Attributes) { return EventDescriptorCollection.Empty; }
        public EventDescriptorCollection GetEvents() { return EventDescriptorCollection.Empty; }
        public PropertyDescriptorCollection GetProperties(Attribute[] Attributes) { return DataTable.GetItemProperties(null); }
        public PropertyDescriptorCollection GetProperties() { return DataTable.GetItemProperties(null); }
        public object GetPropertyOwner(PropertyDescriptor PropertyDescriptor) { return this; }
        #endregion Property value tracking
    }

    public class DataValuePropertyDescriptor : PropertyDescriptor {
        public DataValuePropertyDescriptor(string Name) :
            base(Name, new Attribute[] { new DisplayNameAttribute(string.Concat("Display: ", Name)) }) {
        }

        #region PropertyDescriptor implementation
        public override Type ComponentType { get { return typeof(DataRow); } }
        public override Type PropertyType { get { return typeof(string); } }
        public override bool IsReadOnly { get { return false; } }
        public override bool CanResetValue(object DataRow) { return true; }
        public override object GetValue(object DataRow) { return ((DataRow)DataRow).GetValue(Name); }
        public override void ResetValue(object DataRow) { ((DataRow)DataRow).SetValue(Name, null); }
        public override void SetValue(object DataRow, object Value) { ((DataRow)DataRow).SetValue(Name, Value); }
        public override bool ShouldSerializeValue(object DataRow) { return false; }
        #endregion PropertyDescriptor implementation
    }

    public class DataColumnDisplayNameConverter : IValueConverter {
        public DataColumnDisplayNameConverter()
            : base() {
        }

        public object Convert(object Value, Type TargetType, object Parameter, CultureInfo Culture) {
            DataTable table = (DataTable)Parameter;
            string property = (string)Value;
            foreach (PropertyDescriptor descriptor in table.GetItemProperties(null)) {
                if (property == descriptor.Name) {
                    return descriptor.DisplayName;
                }
            }
            return DependencyProperty.UnsetValue;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
            throw new NotImplementedException();
        }
    }
}
Comments