具有动态定义的 WPF GridView

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/356144/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 19:51:55  来源:igfitidea点击:

WPF GridView with a dynamic definition

wpfgridviewlistview

提问by TomDestry

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.

我想使用 ListView 的 GridView 模式来显示我的程序将从外部源接收的一组数据。数据将由两个数组组成,一个是列名,另一个是用于填充控件的字符串值。

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.

我不知道如何创建一个合适的类,我可以将其用作 ListView 中的 Item。我知道填充 Items 的唯一方法是将其设置为具有表示列的属性的类,但在运行前我不知道这些列。

I could create an ItemTemplate dynamically as described in: Create WPF ItemTemplate DYNAMICALLY at runtimebut it still leaves me at a loss as to how to describe the actual data.

我可以按照以下所述动态创建 ItemTemplate:在运行时动态创建 WPF ItemTemplate但它仍然让我不知如何描述实际数据。

Any help gratefully received.

感激地收到任何帮助。

采纳答案by Robert Macnee

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

您可以使用如下方法动态地将 GridViewColumns 添加到 GridView 给定第一个数组:

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:

我假设包含值的第二个数组的长度为 ROWS * COLUMNS。在这种情况下,您的项目可以是长度为 COLUMNS 的字符串数组。您可以使用 Array.Copy 或 LINQ 来拆分数组。原理演示如下:

<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>

回答by TomDestry

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:

在 XAML 中非常简单地设置 ListView:

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

Created an wrapper class for ObservableCollection to hold my data:

为 ObservableCollection 创建了一个包装类来保存我的数据:

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

And bound my ListView to it:

并将我的 ListView 绑定到它:

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.

可能有更简洁的方法,但这对我的应用程序非常有用。再次感谢。

回答by Tawani

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

CodeProject 上的这篇文章准确地解释了如何创建动态 ListView - 当数据仅在运行时已知时。 http://www.codeproject.com/KB/WPF/WPF_DynamicListView.aspx

回答by ChrisWue

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):

不确定它是否仍然相关,但我找到了一种使用单元格模板选择器设置单个单元格样式的方法。这有点麻烦,因为您必须使用 ContentPresenter 的内容来获取单元格的正确 DataContext(因此您可以绑定到单元格模板中的实际单元格项目):

    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).

注意:我更改了 CodeProject 文章中的 DataMatrix,因此行是字典(列名 -> 单元格对象)。

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 :))

我不能保证这个解决方案不会破坏某些东西或不会在未来的 .Net 版本中破坏。它依赖于这样一个事实,即 ContentPresenter 在选择模板后将 DataContext 设置为它自己的内容。(反射器在这些情况下有很大帮助:))

When creating the GridColumns, I do something like that:

创建 GridColumns 时,我会这样做:

           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

注意:我扩展了 ListView 以便它有一个可以在 xaml 中绑定的 CellTemplateSelector 属性

@Edit 15/03/2011: I wrote a little article which has a little demo project attached: http://codesilence.wordpress.com/2011/03/15/listview-with-dynamic-columns/

@Edit 15/03/2011:我写了一篇小文章,附有一个小演示项目:http://codesilence.wordpress.com/2011/03/15/listview-with-dynamic-columns/

回答by Kayomani

Totally progmatic version:

完全程序化的版本:

        var view = grid.View as GridView;
        view.Columns.Clear();
        int count=0;
        foreach (var column in ViewModel.GridData.Columns)
        {
            //Create Column
            var nc = new GridViewColumn();
            nc.Header = column.Field;
            nc.Width = column.Width;
            //Create template
            nc.CellTemplate = new DataTemplate();
            var factory = new FrameworkElementFactory(typeof(System.Windows.Controls.Border));
            var tbf = new FrameworkElementFactory(typeof(System.Windows.Controls.TextBlock));

            factory.AppendChild(tbf);
            factory.SetValue(System.Windows.Controls.Border.BorderThicknessProperty, new Thickness(0,0,1,1));
            factory.SetValue(System.Windows.Controls.Border.MarginProperty, new Thickness(-7,0,-7,0));
            factory.SetValue(System.Windows.Controls.Border.BorderBrushProperty, Brushes.LightGray);
            tbf.SetValue(System.Windows.Controls.TextBlock.MarginProperty, new Thickness(6,2,6,2));
            tbf.SetValue(System.Windows.Controls.TextBlock.HorizontalAlignmentProperty, column.Alignment);

            //Bind field
            tbf.SetBinding(System.Windows.Controls.TextBlock.TextProperty, new Binding(){Converter = new GridCellConverter(), ConverterParameter=column.BindingField});
            nc.CellTemplate.VisualTree = factory;

            view.Columns.Add(nc);
            count++;
        }

回答by stricq

I would go about doing this by adding an AttachedProperty to the GridView where my MVVM application can specify the columns (and perhaps some additional metadata.) The Behavior code can then dynamically work directly with the GridView object to create the columns. In this way you adhere to MVVM and the ViewModel can specify the columns on the fly.

我将通过向 GridView 添加一个 AttachedProperty 来执行此操作,我的 MVVM 应用程序可以在其中指定列(可能还有一些额外的元数据)。然后行为代码可以直接与 GridView 对象动态工作以创建列。通过这种方式,您遵循 MVVM,并且 ViewModel 可以动态指定列。