Create WPF ItemTemplate DYNAMICALLY at runtime
Asked Answered
P

7

4

At run time I want to dynamically build grid columns (or another display layout) in a WPF ListView. I do not know the number and names of the columns before hand.

I want to be able to do:
MyListView.ItemSource = MyDataset;
MyListView.CreateColumns();

Ponceau answered 24/9, 2008 at 6:4 Comment(0)
S
1

i'd try following approach:

A) you need to have the list box display grid view - i believe this you've done already
B) define a style for GridViewColumnHeader:

        <Style TargetType="{x:Type GridViewColumnHeader}" x:Key="gridViewColumnStyle">
            <EventSetter Event="Click" Handler="OnHeaderClicked"/>
            <EventSetter Event="Loaded" Handler="OnHeaderLoaded"/>
        </Style>

in my case, i had a whole bunch of other properties set, but in the basic scenario - you'd need Loaded event. Clicked - this is useful if you want to add sorting and filtering functionality.

C) in your listview code, bind the template with your gridview:

    public MyListView()
    {
        InitializeComponent();
        GridView gridViewHeader = this.listView.View as GridView;
        System.Diagnostics.Debug.Assert(gridViewHeader != null, "Expected ListView.View should be GridView");
        if (null != gridViewHeader)
        {
            gridViewHeader.ColumnHeaderContainerStyle = (Style)this.FindResource("gridViewColumnStyle");
        }
    }

D) then in you OnHeaderLoaded handler, you can set a proper template based on the column's data

    void OnHeaderLoaded(object sender, RoutedEventArgs e)
    {
        GridViewColumnHeader header = (GridViewColumnHeader)sender;
        GridViewColumn column = header.Column;

//select and apply your data template here.

        e.Handled = true;
    }

E) I guess you'd need also to acquire ownership of ItemsSource dependency property and handle it's changed event.

            ListView.ItemsSourceProperty.AddOwner(typeof(MyListView), new PropertyMetadata(OnItemsSourceChanged));

        static void OnItemsSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            MyListView view = (MyListView)sender;
            //do reflection to get column names and types
            //and for each column, add it to your grid view:
            GridViewColumn column = new GridViewColumn();
            //set column properties here...
            view.Columns.Add(column);
        }

the GridViewColumn class itself doesn't have much properties, so you might want to add some information there using attached properties - i.e. like unique column tag - header most likely will be used for localization, and you will not relay on this one.

In general, this approach, even though quite complicated, will allow you to easily extend your list view functionality.

Sewellel answered 24/9, 2008 at 16:8 Comment(0)
S
3

You can add columns dynamically to a ListView by using Attached Properties. Check out this article on the CodeProject it explains exactly that...

WPF DynamicListView - Binding to a DataMatrix

Sapper answered 15/5, 2009 at 6:5 Comment(0)
H
2

From MSDN:

    MyListBox.ItemsSource = view;
    ListView myListView = new ListView();

    GridView myGridView = new GridView();
    myGridView.AllowsColumnReorder = true;
    myGridView.ColumnHeaderToolTip = "Employee Information";

    GridViewColumn gvc1 = new GridViewColumn();
    gvc1.DisplayMemberBinding = new Binding("FirstName");
    gvc1.Header = "FirstName";
    gvc1.Width = 100;
    myGridView.Columns.Add(gvc1);
    GridViewColumn gvc2 = new GridViewColumn();
    gvc2.DisplayMemberBinding = new Binding("LastName");
    gvc2.Header = "Last Name";
    gvc2.Width = 100;
    myGridView.Columns.Add(gvc2);
    GridViewColumn gvc3 = new GridViewColumn();
    gvc3.DisplayMemberBinding = new Binding("EmployeeNumber");
    gvc3.Header = "Employee No.";
    gvc3.Width = 100;
    myGridView.Columns.Add(gvc3);

    //ItemsSource is ObservableCollection of EmployeeInfo objects
    myListView.ItemsSource = new myEmployees();
    myListView.View = myGridView;
    myStackPanel.Children.Add(myListView);
Household answered 16/5, 2009 at 12:15 Comment(0)
S
1

i'd try following approach:

A) you need to have the list box display grid view - i believe this you've done already
B) define a style for GridViewColumnHeader:

        <Style TargetType="{x:Type GridViewColumnHeader}" x:Key="gridViewColumnStyle">
            <EventSetter Event="Click" Handler="OnHeaderClicked"/>
            <EventSetter Event="Loaded" Handler="OnHeaderLoaded"/>
        </Style>

in my case, i had a whole bunch of other properties set, but in the basic scenario - you'd need Loaded event. Clicked - this is useful if you want to add sorting and filtering functionality.

C) in your listview code, bind the template with your gridview:

    public MyListView()
    {
        InitializeComponent();
        GridView gridViewHeader = this.listView.View as GridView;
        System.Diagnostics.Debug.Assert(gridViewHeader != null, "Expected ListView.View should be GridView");
        if (null != gridViewHeader)
        {
            gridViewHeader.ColumnHeaderContainerStyle = (Style)this.FindResource("gridViewColumnStyle");
        }
    }

D) then in you OnHeaderLoaded handler, you can set a proper template based on the column's data

    void OnHeaderLoaded(object sender, RoutedEventArgs e)
    {
        GridViewColumnHeader header = (GridViewColumnHeader)sender;
        GridViewColumn column = header.Column;

//select and apply your data template here.

        e.Handled = true;
    }

E) I guess you'd need also to acquire ownership of ItemsSource dependency property and handle it's changed event.

            ListView.ItemsSourceProperty.AddOwner(typeof(MyListView), new PropertyMetadata(OnItemsSourceChanged));

        static void OnItemsSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            MyListView view = (MyListView)sender;
            //do reflection to get column names and types
            //and for each column, add it to your grid view:
            GridViewColumn column = new GridViewColumn();
            //set column properties here...
            view.Columns.Add(column);
        }

the GridViewColumn class itself doesn't have much properties, so you might want to add some information there using attached properties - i.e. like unique column tag - header most likely will be used for localization, and you will not relay on this one.

In general, this approach, even though quite complicated, will allow you to easily extend your list view functionality.

Sewellel answered 24/9, 2008 at 16:8 Comment(0)
S
0

Have a DataTemplateselector to select one of the predefined templates(Of same DataType) and apply the selector on to the ListView. You can have as many DataTemplates with different columns.

Saker answered 24/9, 2008 at 6:47 Comment(1)
I do not know the number or name of the columns before - how will this help?Ponceau
B
0

You can use a DataTemplateSelector to return a DataTemplate that you have created dynamically in code. However, this is a bit tedious and more complicated than using a predefined one from XAML, but it is still possible. Have a look at this example: http://dedjo.blogspot.com/2007/03/creating-datatemplates-from-code.html

Bluish answered 16/10, 2008 at 15:18 Comment(0)
C
0

From experience I can recommend steering clear of dynamic data templates if you can help it... rather use the advice given here to explictly create the ListView columns, rather than trying to create a DataTemplate dynamically.

Reason is that the FrameworkElementFactory (or whatever the class name is for producing DataTemplates at run time) is somewhat cludgey to use (and is deprecated in favor of using XAML for dynamic templates) - either way you take a performance hit.

Capitalism answered 23/5, 2009 at 20:20 Comment(0)
M
0

This function will bind columns to a specified class and dynamically set header, binding, width, and string format.

private void AddListViewColumns<T>(GridView GvFOO)
    {
        foreach (System.Reflection.PropertyInfo property in typeof(T).GetProperties().Where(p => p.CanWrite)) //loop through the fields of the object
        {
            if (property.Name != "Id") //if you don't want to add the id in the list view
            {
                GridViewColumn gvc = new GridViewColumn(); //initialize the new column
                gvc.DisplayMemberBinding = new Binding(property.Name); // bind the column to the field
                if (property.PropertyType == typeof(DateTime)) { gvc.DisplayMemberBinding.StringFormat = "yyyy-MM-dd"; } //[optional] if you want to display dates only for DateTime data
                gvc.Header = property.Name; //set header name like the field name
                gvc.Width = (property.Name == "Description") ? 200 : 100; //set width dynamically
                GvFOO.Columns.Add(gvc); //add new column to the Gridview
            }
        }
    }

Let's say you have a GridView with Name="GvFoo" in your XAML, which you would like to bind to a class FOO. then, you can call the function by passing your class "FOO and GridView "GvFoo" as arguments in your MainWindow.xaml.cs on Window loading

AddLvTodoColumns<FOO>(GvFoo);

your MainWindow.xaml file should include the following

<ListView x:Name="LvFOO">
     <ListView.View>
          <GridView x:Name="GvTodos"/>
     </ListView.View>
 </ListView>
Mars answered 10/10, 2022 at 17:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.