views:

11980

answers:

6

I want to use the GridView mode of a ListView to display a set of data that my program will be receiving from an external source. The data will consist of two arrays, one of column names and one of strings values to populate the control.

I don't see how to create a suitable class that I can use as the Item in a ListView. The only way I know to populate the Items is to set it to a class with properties that represent the columns, but I have no knowledge of the columns before run-time.

I could create an ItemTemplate dynamically as described in: http://stackoverflow.com/questions/125638/create-wpf-itemtemplate-dynamically-at-runtime but it still leaves me at a loss as to how to describe the actual data.

Any help gratefully received.

+2  A: 

You can add GridViewColumns to the GridView dynamically given the first array using a method like this:

private void AddColumns(GridView gv, string[] columnNames)
{
    for (int i = 0; i < columnNames.Length; i++)
    {
        gv.Columns.Add(new GridViewColumn
        {
            Header = columnNames[i],
            DisplayMemberBinding = new Binding(String.Format("[{0}]", i))
        });
    }
}

I assume the second array containing the values will be of ROWS * COLUMNS length. In that case, your items can be string arrays of length COLUMNS. You can use Array.Copy or LINQ to split up the array. The principle is demonstrated here:

<Grid>
    <Grid.Resources>
        <x:Array x:Key="data" Type="{x:Type sys:String[]}">
            <x:Array Type="{x:Type sys:String}">
                <sys:String>a</sys:String>
                <sys:String>b</sys:String>
                <sys:String>c</sys:String>
            </x:Array>
            <x:Array Type="{x:Type sys:String}">
                <sys:String>do</sys:String>
                <sys:String>re</sys:String>
                <sys:String>mi</sys:String>
            </x:Array>
        </x:Array>
    </Grid.Resources>
    <ListView ItemsSource="{StaticResource data}">
        <ListView.View>
            <GridView>
                <GridViewColumn DisplayMemberBinding="{Binding Path=[0]}" Header="column1"/>
                <GridViewColumn DisplayMemberBinding="{Binding Path=[1]}" Header="column2"/>
                <GridViewColumn DisplayMemberBinding="{Binding Path=[2]}" Header="column3"/>
            </GridView>
        </ListView.View>
    </ListView>
</Grid>
Robert Macnee
+1  A: 

Thanks, that is very helpful.

I used it to create a dynamic version as follows. I created the column headings as you suggested:

private void AddColumns(List<String> myColumns)
{
    GridView viewLayout = new GridView();
    for (int i = 0; i < myColumns.Count; i++)
    {
     viewLayout.Columns.Add(new GridViewColumn
     {
      Header = myColumns[i],
      DisplayMemberBinding = new Binding(String.Format("[{0}]", i))
     });
    }
    myListview.View = viewLayout;
}

Set up the ListView very simply in XAML:

<ListView Name="myListview" DockPanel.Dock="Left"/>

Created an wrapper class for ObservableCollection to hold my data:

public class MyCollection : ObservableCollection<List<String>>
{
    public MyCollection()
     : base()
    {
    }
}

And bound my ListView to it:

results = new MyCollection();

Binding binding = new Binding();
binding.Source = results;
myListview.SetBinding(ListView.ItemsSourceProperty, binding);

Then to populate it, it was just a case of clearing out any old data and adding the new:

results.Clear();
List<String> details = new List<string>();
for (int ii=0; ii < externalDataCollection.Length; ii++)
{
    details.Add(externalDataCollection[ii]);
}
results.Add(details);

There are probably neater ways of doing it, but this is very useful for my application. Thanks again.

TomDestry
A: 

This methods is good only when the user doesn't have any rights to add or change the data. Because if he have - one should then put them back to the data source - what is not so easy without data binding. And how to use data binding (i.e. templates to set data input type, appropriate controls and property/field that it should bind to), if you should do it programmaticly in code??? Even if I'm puttin new DP like CellTemplate (with type DataTemplate), in outer code it's setting only once, though it should be different in each column. I tried doing it like this:

            for (int col = 1; col < this.Columns + 1; col++)
            {
                DataTemplate template = this.CellTemplate;
                template.VisualTree.SetBinding(FrameworkElement.DataContextProperty,
                            new Binding(String.Format("[{0}]", col - 1)));

                GridViewColumn column = GridView.Columns[col];
                column.CellTemplate = CellTemplate;
            }

to change one DP - CellTemplate - dynamicly in each column by adding DataContext to root control (It's supposed that path to property that holds value of interest is already somewhere deep in the template. So just need to point it out to the right element in the array) But it doesn't work, saying that VisualTree property of DataTemplate is null. And I don't know any other way to change user passed template dynamicly. Any suggestions?

Aks1
+1  A: 

This article on the CodeProject explains exactly how to create a Dynamic ListView - when data is known only at runtime. http://www.codeproject.com/KB/WPF/WPF_DynamicListView.aspx

Tawani
A: 

Not sure if it's still relevant but I found a way to style the individual cells with a celltemplate selector. It's a bit hacky because you have to munch around with the Content of the ContentPresenter to get the proper DataContext for the cell (so you can bind to the actual cell item in the cell template):

    public class DataMatrixCellTemplateSelectorWrapper : DataTemplateSelector
    {
        private readonly DataTemplateSelector _ActualSelector;
        private readonly string _ColumnName;
        private Dictionary<string, object> _OriginalRow;

        public DataMatrixCellTemplateSelectorWrapper(DataTemplateSelector actualSelector, string columnName)
        {
            _ActualSelector = actualSelector;
            _ColumnName = columnName;
        }

        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            // The item is basically the Content of the ContentPresenter.
            // In the DataMatrix binding case that is the dictionary containing the cell objects.
            // In order to be able to select a template based on the actual cell object and also
            // be able to bind to that object within the template we need to set the DataContext
            // of the template to the actual cell object. However after the template is selected
            // the ContentPresenter will set the DataContext of the template to the presenters
            // content. 
            // So in order to achieve what we want, we remember the original DataContext and then
            // change the ContentPresenter content to the actual cell object.
            // Therefor we need to remember the orginal DataContext otherwise in subsequent calls
            // we would get the first cell object.

            // remember old data context
            if (item is Dictionary<string, object>)
            {
                _OriginalRow = item as Dictionary<string, object>;
            }

            if (_OriginalRow == null)
                return null;

            // get the actual cell object
            var obj = _OriginalRow[_ColumnName];

            // select the template based on the cell object
            var template = _ActualSelector.SelectTemplate(obj, container);

            // find the presenter and change the content to the cell object so that it will become
            // the data context of the template
            var presenter = WpfUtils.GetFirstParentForChild<ContentPresenter>(container);
            if (presenter != null)
            {
                presenter.Content = obj;
            }

            return template;
        }
    }

Note: I changed the DataMatrix frome the CodeProject article so that rows are Dictionaries (ColumnName -> Cell Object).

I can't guarantee that this solution will not break something or will not break in future .Net release. It relies on the fact that the ContentPresenter sets the DataContext after it selected the template to it's own Content. (Reflector helps a lot in these cases :))

When creating the GridColumns, I do something like that:

           var column = new GridViewColumn
                          {
                              Header = col.Name,
                              HeaderTemplate = gridView.ColumnHeaderTemplate
                          };
            if (listView.CellTemplateSelector != null)
            {
                column.CellTemplateSelector = new DataMatrixCellTemplateSelectorWrapper(listView.CellTemplateSelector, col.Name);
            }
            else
            {
                column.DisplayMemberBinding = new Binding(string.Format("[{0}]", col.Name));
            }
            gridView.Columns.Add(column);

Note: I have extended ListView so that it has a CellTemplateSelector property you can bind to in xaml

ChrisWue
A: 

Hi, Can u share the full source with extended listview and modified row matrix.

Thanks Krish

Krish