Home > Enterprise >  How to bind data from two different nested objects to a DataGrid
How to bind data from two different nested objects to a DataGrid

Time:03-21

I curretly have a View which is supposed to show data from two different Objects. Tod do so i created a view model which contains a list of an object type that contains the two different objects mentioned before. The two objects are Suppliers and Items. Items class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.Json.Serialization;


namespace MLaRealERP.Models
{
    public class Insumo
    {
        [JsonPropertyName("Id")]
        public int Id { get; set; }

        [JsonPropertyName("proveedorId")]
        public int ProveedorId { get; set; }

        [JsonPropertyName("name")]
        public string Name { get; set; }

        [JsonPropertyName("tipo")]
        public string Tipo { get; set; }

        [JsonPropertyName("unidad")]
        public string Unidad { get; set; }

        [JsonPropertyName("precio")]
        public float Precio { get; set; }
    }
}

Suppliers Class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.Json.Serialization;


namespace MLaRealERP.Models
{
    public class Proveedor
    {
        [JsonPropertyName("id")]
        public int Id { get; set; }

        [JsonPropertyName("name")]
        public string Name { get; set; }

        [JsonPropertyName("celular")]
        public string Celular { get; set; }

        public void NuevoProveedor(string n, string c)
        {
            Name = n;
            Celular = c;
        }
    }
}

ViewModel

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MLaRealERP.Models;

namespace MLaRealERP.ViewModels
{
    public class InsumosViewModel
    {
        public class InsumoVM
        {
            public Insumo insumo { get; set; }
            public Proveedor proveedor { get; set; }

        }
        List<Insumo> repo;
        List<Proveedor> proveedores;
        public List<InsumoVM> insumos;
        //IOrderedEnumerable<Proveedor> proveedores;
        public InsumosViewModel()
        {
            repo = new List<Insumo>();
            proveedores = new List<Proveedor>();
            insumos = new List<InsumoVM>();
        }
        public async void InitializeViewModel()
        {
            repo = await Funciones.GetAll<Insumo>("insumos", App.client);
            proveedores = await Funciones.GetAll<Proveedor>("proveedors", App.client);
            proveedores = proveedores.OrderBy(p => p.Id).ToList();
            

            //proveedores = repo.OrderBy(repo => repo.Id);
            foreach(Insumo insumo in repo)
            {
                newInsumoVM(insumo);
            }
        }
        private void newInsumoVM(Insumo ins)
        {
            InsumoVM insumoVM = new InsumoVM();
            insumoVM.insumo = ins;
            insumoVM.proveedor = proveedores[ins.ProveedorId - 1];
            insumos.Add(insumoVM);
        }

    }
}

Data Grid Xaml

<Window x:Class="MLaRealERP.Views.InsumosView"
        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:MLaRealERP.Views"
        mc:Ignorable="d"
        Title="InsumosView" Height="450" Width="800">
    <DockPanel Margin="10">
        <Canvas DockPanel.Dock="Top" Margin="0,0,0,20">
            <TextBox  Canvas.Left="10"  Name="txtFilter" TextChanged="txtFilter_TextChanged" Width="171" />
            <Button  Canvas.Right="10" Name="btnAddInsumo" Click="btnAddInsumo_Click">Agregar Nuevo Producto</Button>
        </Canvas>
        <DataGrid ItemsSource="{Binding insumos}" Name="dgInsumos" Margin="10" ColumnWidth="*" CanUserAddRows="False" >
            <DataGrid.ColumnHeaderStyle>
                <Style TargetType="DataGridColumnHeader">
                    <Setter Property="FontWeight" Value="Bold" />
                    <Setter Property="Background" Value="LightBlue" />
                    <Setter Property="BorderBrush" Value="Black" />
                    <Setter Property="BorderThickness" Value="0.5" />
                </Style>
            </DataGrid.ColumnHeaderStyle>
            <DataGrid.Columns>
                <DataGridTextColumn Header="Id" Binding="{Binding insumos.insumo.Id}" />
                <DataGridTextColumn Header="Nombre" Binding="{Binding insumos.insumo.Name}" />
                <DataGridTextColumn Header="Tipo" Binding="{Binding insumos.insumo.Tipo}" />
            </DataGrid.Columns>
        </DataGrid>
    </DockPanel>
</Window>

Datagrid cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using MLaRealERP.ViewModels;

namespace MLaRealERP.Views
{
    /// <summary>
    /// Interaction logic for InsumosView.xaml
    /// </summary>
    public partial class InsumosView : Window
    {
        InsumosViewModel insumos;
        public InsumosView()
        {
            InitializeComponent();
            insumos = new InsumosViewModel();
            insumos.InitializeViewModel();
            dgInsumos.DataContext = insumos;
        }

        //public async void LoadViewItems()
        //{
        //    insumos.InitializeViewModel();
        //}
        private void txtFilter_TextChanged(object sender, TextChangedEventArgs e)
        {

        }

        private void btnAddInsumo_Click(object sender, RoutedEventArgs e)
        {

        }
    }
}

I want to show Isumos id, name, and tipo along with proveedores name.

EDIT: Current View.xaml for the grid

<Window x:Class="MLaRealERP.Views.InsumosView"
        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:MLaRealERP.Views"
        mc:Ignorable="d"
        Title="InsumosView" Height="450" Width="800">
    <DockPanel Margin="10">
        <Canvas DockPanel.Dock="Top" Margin="0,0,0,20">
            <TextBox  Canvas.Left="10"  Name="txtFilter" TextChanged="txtFilter_TextChanged" Width="171" />
            <Button  Canvas.Right="10" Name="btnAddInsumo" Click="btnAddInsumo_Click">Agregar Nuevo Producto</Button>
        </Canvas>
        <DataGrid ItemsSource="{Binding insumos}" DataContext="{Binding InsumoVM}" Name="dgInsumos" Margin="10" ColumnWidth="*" CanUserAddRows="False" >
            <DataGrid.ColumnHeaderStyle>
                <Style TargetType="DataGridColumnHeader">
                    <Setter Property="FontWeight" Value="Bold" />
                    <Setter Property="Background" Value="LightBlue" />
                    <Setter Property="BorderBrush" Value="Black" />
                    <Setter Property="BorderThickness" Value="0.5" />
                </Style>
            </DataGrid.ColumnHeaderStyle>
            <DataGrid.Columns>
                <DataGridTextColumn Header="Id" Binding="{Binding insumo.Id}" />
                <DataGridTextColumn Header="Nombre" Binding="{Binding insumo.Name}" />
                <DataGridTextColumn Header="Tipo" Binding="{Binding insumo.Tipo}" />

            </DataGrid.Columns>
        </DataGrid>
    </DockPanel>
</Window>

CodePudding user response:

There are some issue with your code:

  1. Required Fix: You can only bind to public properties and not fields. This means insumos must be a public property. Additionally the binding source, in this case the InsumosViewModel and the InsumoVM classes, must implement INotifyPropertyChanged (see Microsoft Docs for complete example) - even when you don't expect property changes.

InsumoVM.cs

public class InsumoVM : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public Insumo Insumo { get; set; }
    public Proveedor Proveedor { get; set; }
}

InsumosViewModel.cs

public class InsumosViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public ObservableCollection<InsumoVM> Insumos { get; }
    private List<Insumo> Repo { get; set; }
    private List<Proveedor> Proveedores { get; set; }

    public InsumosViewModel()
    {
        this.Repo = new List<Insumo>();
        this.Proveedores = new List<Proveedor>();
        this.Insumos = new ObservableCollection<InsumoVM>();
    }

    public async Task InitializeViewModel()
    {
        this.Repo = await Funciones.GetAll<Insumo>("insumos", App.client);
        this.Proveedores = (await Funciones.GetAll<Proveedor>("proveedors", App.client)).
          OrderBy(p => p.Id)
          .ToList();
        
        this.Insumos.Clear();
        foreach(Insumo insumo in this.Repo)
        {
            NewInsumoVM(insumo);
        }
    }

    private void NewInsumoVM(Insumo ins)
    {
        InsumoVM insumoVM = new InsumoVM()
        {
          Insumo = ins,
          Proveedor = proveedores[ins.ProveedorId - 1]
        };
        this.Insumos.Add(insumoVM);
    }
}
  1. Required Fix: You must use the correct Binding.Path. The items are of type InsumoVM. Therefore InsumoVM is the type of the DataContext of each column:
<DataGrid ItemsSource="{Binding Insumos}">
  <DataGridTextColumn Header="Insumo Name" 
                      Binding="{Binding Insumo.Name}" />
  <DataGridTextColumn Header="Proveedor Name" 
                      Binding="{Binding Proveedor.Name}" />
</DataGrid>
  1. Never return void from an async method (except this method is an event handler). A async method must return Task or Task<T>:
public async Task InitializeViewModel()
{}
  1. Also when using MVVM you should not pass data directly to the view. For example, instead of passing a List<Cliente> to the ClientesView, let the ClientesView bind its controls like dgClients to a property on the DataContext/view model class. List<Cliente> must be a property of the view model class.

  2. To filter data for ItemsControl like a DataGrid use the collection view. Don't create a new collection that you assign to the ItemsSource for this purpose. It will become difficult to maintain very quick.

dgClientes.Items.Filter = item => (items as Cliente).Apellidos.ToLower().Contains(txtFilter.Text.ToLower()); 
  • Related