Click here to Skip to main content
15,868,016 members
Please Sign up or sign in to vote.
1.00/5 (1 vote)
Hi.

Lets say I have a collection called Orders, and one of the members in the collection, is another collection called Products.

So if I have an order, say ID = 001, and it has two different products, I would like to show this on a DataGrid, where the order ID is repeated by the number of products in the record.

So for example, if Jim Carrey was to buy both Face Cream and a Bluetooth speaker, it would come out like...
Order ID   Customer      Product
001        Jim Carrey    Face Cream
001        Jim Carrey    Bluetooth Speaker


What I have tried:

So far, I have looked at an option for a DataTemplate in Xaml, however that displayed a new DataGrid in the column of the DataGrid. Not what I wanted.

I have also read into a CollectionView, however that appears to be more for sorting and searching the collection.

And I have also looked at the option of using a DataTable, however that seems to be copying the values into the DataTable, so the data is duplicated. Maybe it is referencing the original values, however I am unsure.

So my question is, is there something I can use as a go between, between the DataGrid and the Collection? Is the DataTable the better option to look into? If you can please point me in the right direction, and I can investigate it from there. I just don't want to spend hours learning something that will not do what I want.
Posted
Updated 13-Feb-23 22:03pm
v2
Comments
[no name] 11-Feb-23 20:18pm    
What you're showing is a "join"; so join orders and products into another collection and show that.
Member 15627495 12-Feb-23 6:01am    
here is a full code about How to fill a datagridview 'by code', using datatable

https://learn.microsoft.com/en-us/dotnet/api/system.data.datatable?view=net-7.0
Member 15627495 12-Feb-23 6:13am    
an old object from Microsoft is the --> 'Hierarchical FlexGrid' <-- could fully cover your need.
Grant Mc 13-Feb-23 0:18am    
Thanks @Member15627495 and @Gerry Schmitz. Will check out these options

This link will show you how: DataGrid with row details - The complete WPF tutorial[^]

Here is a little demo with Order > Product grouped data structure:
C#
public partial class MainWindow : Window
{
    public MainWindow()
    {

        InitializeComponent();

        MockData();
        MyDataGrid.ItemsSource = CustomerOrders;
    }

    private Random random = new();

    private List<Product> products = new();
    private List<Order> Orders = new();
    private List<CustomerOrder> CustomerOrders = new();

    private void MockData()
    {
        for (int i = 0; i < 10; i++)
        {
            Orders.Add(new Order(
                i,
                DateTime.Now - TimeSpan.FromDays(random.Next(1, 500))));
        }

        for (int i = 0; i < 100; i++)
        {
            products.Add(new(
                i,
                $"Product {i}",
                random.Next(20, 100),
                random.Next(1, 5),
                Orders[random.Next(0, 9)].Id));
        }

        foreach (Order order in Orders)
        {
            List<Product> orderProducts = products
                .Where(p => p.OrderId == order.Id)
                .ToList();

            order.TotalCost = orderProducts
                .Select(p => p.Cost)
                .Sum();

            CustomerOrders.Add(new (order, orderProducts));
        }
    }
}

public record Product(int Id, string Name, float Cost, int Quantity, int OrderId);

public record Order(int Id, DateTime Created) { public float TotalCost { get; set; } }

public record CustomerOrder(Order Order, List<Product> Products);

Now the UI:
XML
<Window x:Class="WpfDataGridParentChild.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Wpf DataGrid Parent Child" Height="450" Width="800">

    <DataGrid x:Name="MyDataGrid" AutoGenerateColumns="False" RowDetailsVisibilityMode="Visible">
        <DataGrid.Columns>
            <DataGridTextColumn Header="ORDER ID" Binding="{Binding Order.Id}" />
            <DataGridTextColumn Header="ORDER Date" Binding="{Binding Order.Created}" />
            <DataGridTextColumn Header="TOTAL COST" Binding="{Binding Order.TotalCost}" />
        </DataGrid.Columns>
        <DataGrid.RowDetailsTemplate>
            <DataTemplate>
                <DataGrid ItemsSource="{Binding Products}" AutoGenerateColumns="False">
                    <DataGrid.Columns>
                        <DataGridTextColumn Header="PRODUCT ID" Binding="{Binding Id}" />
                        <DataGridTextColumn Header="NAME" Binding="{Binding Name}" />
                        <DataGridTextColumn Header="QTY" Binding="{Binding Quantity}" />
                        <DataGridTextColumn Header="COST" Binding="{Binding Cost}" />
                    </DataGrid.Columns>
                </DataGrid>
            </DataTemplate>
        </DataGrid.RowDetailsTemplate>
    </DataGrid>

</Window>

I have all orders expanded with products per order.
 
Share this answer
 
Comments
Grant Mc 13-Feb-23 0:11am    
Hi Graeme. I really appreciate your time taken for this, however this is not what I am looking for.
Your solution lists the order ID, Date, and Total Cost, and then lists all the products underneath it.

What I am looking for is a solution that displays on each line, all of the data.
Order ID | Order Date | Total Cost | Product ID | Name | Qty | Cost
001 | 13/02/2023 | $59.23 | PID001 | Spkr | 1 | $24.93
001 | 13/02/2023 | $59.23 | PID002 | Creme | 1 | $34.30
002 | 12/02/2023 | $63.18 | PID001 | Spkr | 1 | $24.93
002 | 12/02/2023 | $63.18 | PID005 | Donut | 1 | $28.88

The reason for this is that 90% of the orders will only have 1 product, so would prefer them on the one line.

My apologies if I was not clear in my question.
Graeme_Grant 13-Feb-23 0:26am    
You don't need to include the RowDetailsTemplate and use a flat data structure.
OK, still working through this, but here is what I have so far.

public partial class MainWindow : Window
{
    MovieCollectionViewModel movieCollection;
    ObservableCollection<ActorModel> actors;

    public MainWindow()
    {
        InitializeComponent();
        actors = new ObservableCollection<ActorModel>();
        // Add sample data to actors
        movieCollection = new MovieCollectionViewModel(actors);
        dataGrid.ItemsSource = movieCollection;
    }
}

public class ActorModel
{
    public string Name { get; set; }
    public int Age { get; set; }
    public ObservableCollection<MovieModel> Movies { get; set; } = new ObservableCollection<MovieModel>();
}

public class MovieModel
{
    public string Name { get; set; }
    public DateTime ReleaseDate { get; set; }
}

public class MovieViewModel
{
    public ActorModel Actor { get; set; }
    public MovieModel Movie { get; set; }

    public MovieViewModel()
    { }

    public MovieViewModel(ActorModel Actor, MovieModel Movie)
    {
        this.Actor = Actor;
        this.Movie = Movie;
    }
}

public class MovieCollectionViewModel : ObservableCollection<MovieViewModel>
{
    public MovieCollectionViewModel(ObservableCollection<ActorModel> Actors)
    {
        foreach (ActorModel actor in Actors)
            foreach (MovieModel movie in actor.Movies)
                this.Add(new MovieViewModel(actor, movie));
    }
}
<DataGrid x:Name="dataGrid" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Actor" Binding="{Binding Actor.Name}"/>
        <DataGridTextColumn Header="Age" Binding="{Binding Actor.Age}"/>
        <DataGridTextColumn Header="Movie" Binding="{Binding Movie.Name}"/>
        <DataGridTextColumn Header="Release" Binding="{Binding Movie.ReleaseDate}" />
    </DataGrid.Columns>
</DataGrid>
 
Share this answer
 
Comments
Graeme_Grant 14-Feb-23 5:25am    
You should update the question, not post a solution.

You mention what you have but not what errors or issues ... also, you changed the data from Orders and Products to Movies and Actors. Why the change?
Grant Mc 14-Feb-23 18:07pm    
Thanks Graeme. This works so I have marked it as accepted solution. No real reason for changing it from orders to movies, just was trying this as a solution and it worked, so now I can apply it to my products list. Realise now I have made it more confusing, sorry.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900