如何在运行时使用 MVVM 将 List 绑定到 DataGrid
2024-04-27

所有,我有一个绑定到的视图模型DataGrid使用MVVM。

<DataGrid ItemsSource="{Binding Path=Resources}">...</DataGrid>

Where

public ObservableCollection<ResourceViewModel> Resources { get; private set; }

in the ResourceViewModel类 I 具有以下属性

public string ResourceName
{
    get { return this.resource.ResourceName; }
    set { 
        ...
    }
}

public ObservableCollection<string> ResourceStringList
{
    get { return this.resource.ResourceStringList; }
    set {
        ...
    }
}

所有属性都显示在DataGrid但是ResourceStringList集合显示为“(集合)”。

我怎样才能得到DataGrid显示包含在的每个字符串ResourceStringList在自己的专栏中?

非常感谢你花时间陪伴。


编辑。我已经实施了下面@Marc 的建议。我现在有以下屏幕截图来说明我现在需要什么:

我的资源列索引 3(零索引)之前的空白列不是必需的,如何删除该列?.

我也想知道如何将列名称添加到我的资源列?也许我可以添加一个Binding to Header的财产SeedColumn.

再次感谢您的宝贵时间。


数据网格通常用于显示相同类型的项目列表,每个项目具有一组固定的属性,其中每一列都是一个属性。因此,每一行都是一个项目,每一列都是该项目的一个属性。您的情况有所不同,因为没有固定的属性集,而是您想要显示的集合,就好像它是多个属性的固定集一样。

采用的方法很大程度上取决于您是否只想显示数据或是否希望允许用户操作数据。虽然第一个可以使用值转换器相对容易地实现,但后者需要更多的编码来扩展 DataGrid 类以允许这种行为。我展示的解决方案是一千种可能性中的两种,而且可能不是最优雅的。话虽这么说,我将描述这两种方式,并从双向版本开始。

双向绑定(允许编辑)

示例项目 (100KB) http://www.einmb.de/GridSample.zip

我创建了一个自定义DataGrid和一个自定义的“DataGridColumn”,称为“SeedColumn”。SeedColumn就像文本列一样工作,但有一个属性CollectionName. The DataGrid将为您指定的集合中的每一项添加一个新文本列CollectionName在种子栏的右侧。种子列仅充当一种占位符,告诉 DataGrid 在何处插入哪些列。您可以在一个网格中使用多个种子列。

网格和列类:

public class HorizontalGrid : DataGrid
{
    protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue)
    {
        base.OnItemsSourceChanged(oldValue, newValue);
        foreach (var seed in Columns.OfType<SeedColumn>().ToList())
        { 
            var seedColumnIndex = Columns.IndexOf(seed) + 1;
            var collectionName = seed.CollectionName;
            var headers = seed.Headers;

            // Check if ItemsSource is IEnumerable<object>
            var data = ItemsSource as IEnumerable<object>;
            if (data == null) return;

            // Copy to list to allow for multiple iterations
            var dataList = data.ToList();
            var collections = dataList.Select(d => GetCollection(collectionName, d));
            var maxItems = collections.Max(c => c.Count());

            for (var i = 0; i < maxItems; i++)
            {
                var header = GetHeader(headers, i);
                var columnBinding = new Binding(string.Format("{0}[{1}]" , seed.CollectionName , i));
                Columns.Insert(seedColumnIndex + i, new DataGridTextColumn {Binding = columnBinding, Header = header});
            }
        }
    }

    private static string GetHeader(IList<string> headerList, int index)
    {
        var listIndex = index % headerList.Count;
        return headerList[listIndex];
    }

    private static IEnumerable<object> GetCollection(string collectionName, object collectionHolder)
    {
        // Reflect the property which holds the collection
        var propertyInfo = collectionHolder.GetType().GetProperty(collectionName);
        // Get the property value of the property on the collection holder
        var propertyValue = propertyInfo.GetValue(collectionHolder, null);
        // Cast the value
        var collection = propertyValue as IEnumerable<object>;
        return collection;
    }
}

public class SeedColumn : DataGridTextColumn
{
    public static readonly DependencyProperty CollectionNameProperty =
        DependencyProperty.Register("CollectionName", typeof (string), typeof (SeedColumn), new PropertyMetadata(default(string)));

    public static readonly DependencyProperty HeadersProperty =
        DependencyProperty.Register("Headers", typeof (List<string>), typeof (SeedColumn), new PropertyMetadata(default(List<string>)));

    public List<string> Headers
    {
        get { return (List<string>) GetValue(HeadersProperty); }
        set { SetValue(HeadersProperty, value); }
    }

    public string CollectionName
    {
        get { return (string) GetValue(CollectionNameProperty); }
        set { SetValue(CollectionNameProperty, value); }
    }

    public SeedColumn()
    {
        Headers = new List<string>();
    }
}

用法:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:loc="clr-namespace:WpfApplication1"
        xmlns:system="clr-namespace:System;assembly=mscorlib" xmlns:sample="clr-namespace:Sample"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <sample:HorizontalGrid ItemsSource="{Binding Resources}" AutoGenerateColumns="False">
            <sample:HorizontalGrid.Columns>
                <sample:SeedColumn CollectionName="Strings" Binding="{Binding Name}" Header="Name" Visibility="Collapsed">
                    <sample:SeedColumn.Headers>
                        <system:String>Header1</system:String>
                        <system:String>Header2</system:String>
                        <system:String>Header3</system:String>
                        <system:String>Header4</system:String>
                    </sample:SeedColumn.Headers>
                </sample:SeedColumn>
            </sample:HorizontalGrid.Columns>
        </sample:HorizontalGrid>
    </Grid>
</Window>

以及我用于测试的 ViewModel:

public class MainViewModel
{
    public ObservableCollection<ResourceViewModel> Resources { get; private set; }

    public MainViewModel()
    {
        Resources = new ObservableCollection<ResourceViewModel> {new ResourceViewModel(), new ResourceViewModel(), new ResourceViewModel()};
    }
}

public class ResourceViewModel
{
    private string _name;
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public ObservableCollection<string> Strings { get; private set; }

    public ResourceViewModel()
    {
        Name = "Resource";
        Strings = new ObservableCollection<string> {"s1", "s2", "s3"};
    }
}

和外观(没有标题的旧版本):

附录:

关于新问题和您的评论:

The 空引用异常可能有几个原因,但你显然已经 解决了它。然而,它发生的地方有点像意大利面条 代码,我不会在生产代码中这样做。你需要 处理在任何情况下都可能出错的事情...我已经修改了 代码并将该行重构为它自己的方法。这会给你 当异常被抛出时,知道发生了什么。

The 空栏你看到的是种子列,它显然没有绑定到任何东西。我的想法是使用这一列作为一种行 标头并将其绑定到Name的资源。如果你不需要 根本没有种子列,只需设置它的Visibility到崩溃。

<loc:SeedColumn CollectionName="Strings" Visibility="Collapsed">

Adding 列标题并不难,但你需要思考 关于你想从哪里获取。当您存储所有字符串时 在列表中,它们只是字符串,因此与第二个字符串无关 您可以将其用作标题。我已经实现了一种方法来指定 纯粹在 XAML 中的列,目前对您来说可能就足够了:您可以 像这样使用它:

<loc:HorizontalGrid ItemsSource="{Binding Resources}" AutoGenerateColumns="False">
    <loc:HorizontalGrid.Columns>
        <loc:SeedColumn CollectionName="Strings" Binding="{Binding Name}" Header="Name" Visibility="Collapsed">
            <loc:SeedColumn.Headers>
                <system:String>Header1</system:String>
                <system:String>Header2</system:String>
                <system:String>Header3</system:String>
                <system:String>Header4</system:String>
            </loc:SeedColumn.Headers>
        </loc:SeedColumn>
    </loc:HorizontalGrid.Columns>
</loc:HorizontalGrid>

如果集合中的元素多于指定的标题, 列标题将重复“Header3”、“Header4”、“Header1”,.. 实施过程非常简单。请注意,Headers财产 种子列的 也是可绑定的,您可以将其绑定到任何列表。

单向绑定(无需编辑数据)

一种直接的方法是实现一个转换器,该转换器格式化表中的数据并返回该表上的视图,DataGrid 可以绑定到该视图。缺点:它不允许编辑字符串,因为一旦从原始数据源创建表,显示的数据和原始数据之间就不存在逻辑联系。尽管如此,集合上的更改仍会反映在 UI 中,因为每次数据源更改时 WPF 都会执行转换。简而言之:如果您只想显示数据,则此解决方案非常合适。

它是如何工作的

  • 创建一个自定义值转换器类,该类实现IValueConverter
  • 在 XAML 资源中创建此类的实例并为其命名
  • 绑定网格ItemsSource用这个转换器

这就是它的样子(我的 IDE 是 StackOverflow,所以请检查并更正,如有必要):

public class ResourceConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var resources = value as IEnumerable<ResourceViewModel>;
        if (resources== null) return null;

        // Better play safe and serach for the max count of all items
        var columns = resources[0].ResourceStringList.Count;

        var t = new DataTable();
        t.Columns.Add(new DataColumn("ResourceName"));

        for (var c = 0; c < columns; c++)
        {
            // Will create headers "0", "1", "2", etc. for strings
            t.Columns.Add(new DataColumn(c.ToString()));
        }

        foreach (var r in resources)
        {
            var newRow = t.NewRow();

            newRow[0] = resources.ResourceName;

            for (var c = 0; c < columns; c++)
            {
                newRow[c+1] = r.ResourceStringList[c];
            }

            t.Rows.Add(newRow);
        }


        return t.DefaultView;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

然后在 XAML 中定义一个资源,如下所示,其中 loc 是您的命名空间:

<loc:ResourceConverter x:Key="Converter" />

然后像这样使用它:

<DataGrid ItemsSource="{Binding Resources, Converter={StaticResource Converter}}" />
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在运行时使用 MVVM 将 List 绑定到 DataGrid 的相关文章

随机推荐