다음을 통해 공유


The Equivalent to AutoGenerateColumns for a ListView/GridView

In WPF there is no built-in way to make a ListView display generic content.  The DataGrid has this ability but is probably too much for just a display.

To this end I have created an AttachedProperty that will allow the user to use a ListView with that ability.  To use this AttachedProperty simply use the following XAML.

<ListView ItemsSource="{Binding theList}" Grid.Row="2"
          local:DynamicBindingListView.GenerateColumnsGridView="True"
          local:DynamicBindingListView.DateFormatString="MM/dd/yyyy">
    <ListView.View>
        <GridView></GridView>
    </ListView.View>
</ListView>

There are actually two AttachedProperties within the DynamicBindingListView.  The first property GenerateColumnsGridView is the main property.  Setting this property will cause the DynamicBindingListView to monitor changes to the ItemsSource of the ListView.  When the ItemsSource is updated the AttachedProperty will scan the items (actually only the first item) of the ItemsSource and using Reflection get the properties of the Objects within the ItemsSource.  If there is more than one object type within the collection it will only deal with the properties from the first object.

The second Attached Property is the DateFormatString.  This allows the user to define the format for any and all dates displayed by using this AttachedProperty.  If it is not defined for the ListView the default display for a date will be used.  This property is actually there as a sample of how the user can define the Format of columns but as I said above there is no way (at present) to format different columns of the same type with different formats.

The code for the DynamicBindingListView object is as follows:  (VB.NET C# to follow later)

VB.NET Version

Imports System.ComponentModel
Imports System.Collections.Specialized
Imports System.Reflection
 
Public Class  DynamicBindingListView
 
    Public Shared  Function GetGenerateColumnsGridView(ByVal element As DependencyObject) As Boolean
        If element Is Nothing  Then
            Throw New  ArgumentNullException("element")
        End If
 
        Return element.GetValue(GenerateColumnsGridViewProperty)
    End Function
 
    Public Shared  Sub SetGenerateColumnsGridView(ByVal element As DependencyObject, ByVal value As Boolean)
        If element Is Nothing  Then
            Throw New  ArgumentNullException("element")
        End If
 
        element.SetValue(GenerateColumnsGridViewProperty, value)
    End Sub
 
    Public Shared  ReadOnly GenerateColumnsGridViewProperty  As   _
                           DependencyProperty = DependencyProperty.RegisterAttached("GenerateColumnsGridView", _
                           GetType(Boolean?), GetType(DynamicBindingListView), _
                           New FrameworkPropertyMetadata(Nothing, AddressOf  thePropChanged))
 
 
 
    Public Shared  Function GetDateFormatString(ByVal element As DependencyObject) As String
        If element Is Nothing  Then
            Throw New  ArgumentNullException("element")
        End If
 
        Return element.GetValue(DateFormatStringProperty)
    End Function
 
    Public Shared  Sub SetDateFormatString(ByVal element As DependencyObject, ByVal value As String)
        If element Is Nothing  Then
            Throw New  ArgumentNullException("element")
        End If
 
        element.SetValue(DateFormatStringProperty, value)
    End Sub
 
    Public Shared  ReadOnly DateFormatStringProperty As  _
                           DependencyProperty = DependencyProperty.RegisterAttached("DateFormatString", _
                           GetType(String), GetType(DynamicBindingListView), _
                           New FrameworkPropertyMetadata(Nothing))
 
    Public Shared  Sub thePropChanged(ByVal obj As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
        Dim lv As ListView = CType(obj, ListView)
      Dim descriptor As DependencyPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ListView.ItemsSourceProperty, GetType(ListView))
      descriptor.AddValueChanged(lv, New  EventHandler(AddressOf ItemsSourceChanged))
    End Sub
 
    Private Shared  Sub ItemsSourceChanged(sender As Object, e As  EventArgs)
        Dim lv As ListView = CType(sender, ListView)
        Dim its As IEnumerable = lv.ItemsSource
        Dim itsEnumerator As IEnumerator = its.GetEnumerator
        Dim hasItems As Boolean  = itsEnumerator.MoveNext()
        If hasItems Then
            SetUpTheColumns(lv, itsEnumerator.Current)
        End If
    End Sub
 
    Private Shared  Sub SetUpTheColumns(theListView As ListView, firstObject As Object)
        Dim theClassProperties As PropertyInfo() = firstObject.GetType().GetProperties()
        Dim gv As GridView = theListView.View
        For Each  pi As  PropertyInfo In  theClassProperties
            Dim columnName As String  = pi.Name
            Dim grv As New  GridViewColumn With  {.Header = columnName}
 
            If pi.PropertyType Is GetType(DateTime) Then
                Dim bnd As New  Binding(columnName)
                Dim formatString As String  = theListView.GetValue(DateFormatStringProperty)
                If formatString <> String.Empty Then
                    bnd.StringFormat = formatString
                End If
                BindingOperations.SetBinding(grv, TextBlock.TextProperty, bnd)
                grv.DisplayMemberBinding = bnd
            Else
                Dim bnd As New  Binding(columnName)
                BindingOperations.SetBinding(grv, TextBlock.TextProperty, bnd)
                grv.DisplayMemberBinding = bnd
            End If
            gv.Columns.Add(grv)
        Next
    End Sub
 
End Class

C# Version

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Reflection;
using System.Windows.Controls;
using System.Windows.Threading;
using System.Windows;
using System.Collections;
using System.Windows.Data;
 
namespace CSharpVersioon
{
    public class  DynamicBindingListView
    {
 
        public static  bool GetGenerateColumnsGridView(DependencyObject element)
        {
            if (element == null)
            {
                throw new  ArgumentNullException("element");
            }
 
            return (bool)element.GetValue(GenerateColumnsGridViewProperty);
        }
 
        public static  void SetGenerateColumnsGridView(DependencyObject element, bool  value)
        {
            if (element == null)
            {
                throw new  ArgumentNullException("element");
            }
 
            element.SetValue(GenerateColumnsGridViewProperty, value);
        }
 
 
        public static  readonly DependencyProperty GenerateColumnsGridViewProperty = DependencyProperty.RegisterAttached("GenerateColumnsGridView", typeof(bool?), typeof(DynamicBindingListView), new  FrameworkPropertyMetadata(null, thePropChanged));
 
 
        public static  string GetDateFormatString(DependencyObject element)
        {
            if (element == null)
            {
                throw new  ArgumentNullException("element");
            }
 
            return (string)element.GetValue(DateFormatStringProperty);
        }
 
        public static  void SetDateFormatString(DependencyObject element, string  value)
        {
            if (element == null)
            {
                throw new  ArgumentNullException("element");
            }
 
            element.SetValue(DateFormatStringProperty, value);
        }
 
 
        public static  readonly DependencyProperty DateFormatStringProperty = DependencyProperty.RegisterAttached("DateFormatString", typeof(string), typeof(DynamicBindingListView), new  FrameworkPropertyMetadata(null));
        public static  void thePropChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            ListView lv = (ListView)obj;
            DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(ListView.ItemsSourceProperty, typeof(ListView));
            descriptor.AddValueChanged(lv, new  EventHandler(ItemsSourceChanged));
        }
 
        private static  void ItemsSourceChanged(object sender, EventArgs e)
        {
            ListView lv = (ListView)sender;
            IEnumerable its = lv.ItemsSource;
            IEnumerator itsEnumerator = its.GetEnumerator();
            bool hasItems = itsEnumerator.MoveNext();
            if (hasItems)
            {
                SetUpTheColumns(lv, itsEnumerator.Current);
            }
        }
 
        private static  void SetUpTheColumns(ListView theListView,  object  firstObject)
        {
            PropertyInfo[] theClassProperties = firstObject.GetType().GetProperties();
            GridView gv = (GridView)theListView.View;
            foreach (PropertyInfo pi in theClassProperties)
            {
                string columnName = pi.Name;
                GridViewColumn grv = new  GridViewColumn { Header = columnName };
 
                if (object.ReferenceEquals(pi.PropertyType, typeof(DateTime)))
                {
                    Binding bnd = new  Binding(columnName);
                    string formatString = (string)theListView.GetValue(DateFormatStringProperty);
                    if (formatString != string.Empty)
                    {
                        bnd.StringFormat = formatString;
                    }
                    BindingOperations.SetBinding(grv, TextBlock.TextProperty, bnd);
                    grv.DisplayMemberBinding = bnd;
                }
                else
                {
                    Binding bnd = new  Binding(columnName);
                    BindingOperations.SetBinding(grv, TextBlock.TextProperty, bnd);
                    grv.DisplayMemberBinding = bnd;
                }
                gv.Columns.Add(grv);
            }
        }
 
    }
}

How does it work?

The DynamicBindigListView class holds two AttachedProperties.  The first GenerateColumnsGridView is a boolean but in reality it does not use the value provided in XAML.  This property is used as an initialization of the class and has a method thePropChanged as a method of setting up for generating columns in a ListView/GridView.  The thePropChanged method simply gets a DependencyPropertyDescriptor for the ItemsSource property of the ListView and registers a callback method for when the property is changed (ItemsSourceChanged).

Then when the ItemsSource of the ListView is changed and there are items in the collection it will get the first items and iterate thru the properties.  For each property a GridViewColumn is created with a Header equivalent to the property name.  When testing this code I used only the DisplayMemberBinding but while that works it would not allow either converters or FormatStrings to be used, so the BindingOperations.SetBinding was added to allow that.  I could not find any documentation of why this is so but I went on observed behaviour for this part of the code.

To illustrate how to extend this I added the ability to add a FormatString if the datatype of the property is a DateTime.  The DateFormatString attached property is use to pass this information to the class.  Adding other attached properties could be used to pass other information for use in binding.

 
 I hope this will help anyone who wants to have the same functionality as the DataGrid with respect to automatically generating columns.