C# 如何从WPF中的基类型集合为子类型集合自动生成DataGrid列?

C# 如何从WPF中的基类型集合为子类型集合自动生成DataGrid列?,c#,wpf,mvvm,datagrid,C#,Wpf,Mvvm,Datagrid,所以我有一堆从基类派生的类。我有一些类(收集器),它们有一个返回这些类集合的方法。我还有一个TabControl,其中每个选项卡都有包含DataGrid的自定义控件。这些自定义控件有一个ViewModel。在ViewModel中,我拥有由收集器返回的基类元素的集合。我想将DataGrids绑定到这些集合并自动生成列,但派生类有不同的属性,基类没有任何应该显示的属性 internal class ElementsInfoViewModel : INotifyPropertyChanged {

所以我有一堆从基类派生的类。我有一些类(收集器),它们有一个返回这些类集合的方法。我还有一个
TabControl
,其中每个选项卡都有包含
DataGrid
的自定义控件。这些自定义控件有一个
ViewModel
。在ViewModel中,我拥有由收集器返回的基类元素的集合。我想将
DataGrid
s绑定到这些集合并自动生成列,但派生类有不同的属性,基类没有任何应该显示的属性

internal class ElementsInfoViewModel : INotifyPropertyChanged
{
    private readonly ElementScaner _elementScaner;

    public ElementsInfoViewModel(ElementScaner elementScaner)
    {
        _elementScaner = elementScaner;
    }

    public ReadOnlyCollection<SystemElement> ShownElements => _elementScaner.Elements;
}


我不知道它是否有效,但您尝试过泛型吗? 我的意思是这样的:

internal class ElementsInfoViewModel<T> : INotifyPropertyChanged
               where T : SystemElement
{

    public ElementsInfoViewModel(ElementScaner elementScaner)
    {
        var list = new List<T>();
        foreach (var el in elementScanner.Elements)
        {
           list.Add(el as T);
        }
        ShownElements = new ReadOnlyCollection<T>(list);
    }

    public ReadOnlyCollection<T> ShownElements { get; }
}
内部类元素SinfoViewModel:INotifyPropertyChanged
其中T:SystemElement
{
公共元素SinfoViewModel(元素扫描器元素扫描器)
{
var list=新列表();
foreach(elementScanner.Elements中的变量el)
{
列表。添加(el为T);
}
ShownElements=新的只读集合(列表);
}
公共只读集合显示元素{get;}
}
我是在浏览器中写的,所以我的代码可能是错误的,但我还是要接受这个想法。 如果可以的话,最好是使用通用版本的ElementScanner(类似于ElementScanner


让我们知道它是否会工作

我不知道这是否是您需要的,但我有另一个选择。 由于DataGrid没有一种方法来动态绑定列,因此您可以对其进行扩展并自行创建

例如:

class MyDataGrid : DataGrid
{

    public MyDataGrid() : base()
    {
    }

    protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue)
    {
        base.OnItemsSourceChanged(oldValue, newValue);
        if (newValue != null)
        {
            var enumerator = newValue.GetEnumerator();
            if (enumerator.MoveNext())
            {
                Columns.Clear();
                var firstElement = enumerator.Current;
                var actualType = firstElement.GetType();
                foreach (var prop in actualType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.CanRead))
                {
                    Columns.Add(new DataGridTextColumn
                    {
                        Header = prop.Name,
                        Binding = new Binding(prop.Name)
                    });
                }

            }
        }
    }
}
我写了一个简单的项目来测试它,它在我的环境中工作

第一对象类型(个人)

第二种对象类型(汽车)

称为SystemElement的元素(标题用作选项卡标题)

类系统元素
{
公共系统元素(字符串标题、IList元素)
{
头衔=头衔;
元素=新的只读集合(元素);
}
公共字符串标题{get;set;}
公共只读集合元素{get;}
}
ElementScanner,用于提取要显示的元素。在我的测试中,列表在构造函数中设置:

class ElementScanner
{

    public ElementScanner()
    {
        var data = new List<SystemElement>();

        data.Add(new SystemElement("People", new List<object>
        {
            new Person { Name = "John", Surname = "Doe", Age = 22},
            new Person { Name = "Lenny", Surname = "Pegasus", Age = 30},
            new Person { Name = "Duffy", Surname = "Duck", Age = 22}
        }));

        data.Add(new SystemElement("Cars", new List<object>
        {
            new Car { Name = "Mercedes", HP = 700 },
            new Car { Name = "Red Bull", HP = 650 },
            new Car { Name = "Ferrari", HP = 600 }
        }));

        Elements = new ReadOnlyCollection<SystemElement>(data);
    }

    public ReadOnlyCollection<SystemElement> Elements { get; }
}
class元素扫描器
{
公共元素扫描器()
{
var data=新列表();
添加(新系统元素(“人员”),新列表
{
新人{Name=“John”,姓氏=“Doe”,年龄=22},
新人{Name=“Lenny”,姓氏=“Pegasus”,年龄=30},
新人{Name=“Duffy”,姓氏=“Duck”,年龄=22}
}));
添加(新系统元素(“汽车”),新列表
{
新车{Name=“Mercedes”,HP=700},
新车{Name=“红牛”,HP=650},
新车{Name=“法拉利”,马力=600}
}));
元素=新的只读集合(数据);
}
公共只读集合元素{get;}
}
现在我们有了ViewModel。在我的示例中,我没有使用INotifyPropertyChanged,因为属性永远不会更改(这是一个测试项目)

类元素SinfoViewModel
{
公共元素SinfoViewModel()
{
var elementScanner=new elementScanner();
Elements=elementScanner.Elements;
}
公共只读集合元素{get;}
}
现在移到视图侧。首先,我们有一个UserControl,它显示每个选项卡的内容。因此,它将显示MyDataGrid类型的DataGrid:

<UserControl x:Class="Stack.ElementInfoView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Stack"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <local:MyDataGrid ItemsSource="{Binding Elements}" >
        </local:MyDataGrid>
    </Grid>
</UserControl>

最后,我们有一个主视图,它将所有内容合并到一起:

<Window x:Class="Stack.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Stack"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:ElementsInfoViewModel />
    </Window.DataContext>
    <Grid>
        <TabControl ItemsSource="{Binding Elements}">
            <TabControl.ItemContainerStyle>
                <Style TargetType="TabItem">
                    <Setter Property="Header" Value="{Binding Title}" />
                </Style>
            </TabControl.ItemContainerStyle>
            <TabControl.ContentTemplate>
                <DataTemplate DataType="local:ElementsInfoViewModel">
                    <local:ElementInfoView></local:ElementInfoView>
                </DataTemplate>
            </TabControl.ContentTemplate>
        </TabControl>
    </Grid>
</Window>

这就是我们所拥有的(我想这就是你想要的):


首先,我想使用泛型,但这在我的情况下不起作用,因为我有一个ElementsInfoViewModel集合,所以我不能有一个
列表
嗯。。。听起来是个好主意。我会在尝试后给你回复。最重要的是,我喜欢我可以添加属性到一些属性,我不想显示,因为我真的!可以使用数据属性管理任何自定义项(例如,可以使用组合框列而不是文本)
class Person 
{

    public string Name { get; set; }

    public string Surname { get; set; }

    public int Age { get; set; }
}
class Car 
{

    public string Name { get; set; }


    public int HP { get; set; }
}
class SystemElement
{

    public SystemElement(string title, IList<object> elements)
    {
        Title = title;
        Elements = new ReadOnlyCollection<object>(elements);
    }


    public string Title { get; set; }


    public ReadOnlyCollection<object> Elements { get; }


}
class ElementScanner
{

    public ElementScanner()
    {
        var data = new List<SystemElement>();

        data.Add(new SystemElement("People", new List<object>
        {
            new Person { Name = "John", Surname = "Doe", Age = 22},
            new Person { Name = "Lenny", Surname = "Pegasus", Age = 30},
            new Person { Name = "Duffy", Surname = "Duck", Age = 22}
        }));

        data.Add(new SystemElement("Cars", new List<object>
        {
            new Car { Name = "Mercedes", HP = 700 },
            new Car { Name = "Red Bull", HP = 650 },
            new Car { Name = "Ferrari", HP = 600 }
        }));

        Elements = new ReadOnlyCollection<SystemElement>(data);
    }

    public ReadOnlyCollection<SystemElement> Elements { get; }
}
class ElementsInfoViewModel
{

    public ElementsInfoViewModel()
    {
        var elementScanner = new ElementScanner();
        Elements = elementScanner.Elements;
    }

    public ReadOnlyCollection<SystemElement> Elements { get; }
}
<UserControl x:Class="Stack.ElementInfoView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Stack"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <local:MyDataGrid ItemsSource="{Binding Elements}" >
        </local:MyDataGrid>
    </Grid>
</UserControl>
<Window x:Class="Stack.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Stack"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:ElementsInfoViewModel />
    </Window.DataContext>
    <Grid>
        <TabControl ItemsSource="{Binding Elements}">
            <TabControl.ItemContainerStyle>
                <Style TargetType="TabItem">
                    <Setter Property="Header" Value="{Binding Title}" />
                </Style>
            </TabControl.ItemContainerStyle>
            <TabControl.ContentTemplate>
                <DataTemplate DataType="local:ElementsInfoViewModel">
                    <local:ElementInfoView></local:ElementInfoView>
                </DataTemplate>
            </TabControl.ContentTemplate>
        </TabControl>
    </Grid>
</Window>