Search code examples
c#mvvmuwp-xamlwinui-3

Fill DataGrid with only the Data from DataTable WinUI3 MVVM


The DataTable is populated with the data from a database.

 var query = $"SELECT * FROM {databaseTable.Name}";
 OracleCommand oracleCommand = new OracleCommand(query, _connection);
 OracleDataAdapter oracleDataAdapter = new OracleDataAdapter(oracleCommand);
 DataTable data = new DataTable();
 oracleDataAdapter.Fill(data);
 return data;

I want to populate a DataGrid with the DataTable trough Itemssource. We don't want to specify any objects (classes). We want to fill the Grid with only the informations we get from the DataTable

<control:DataGrid x:Name="gridView1" Margin="5" ItemsSource="{Binding DataTable}" GridLinesVisibility="All" AlternatingRowBackground="DarkBlue">

At the moment I have a ListView that shows me all the Tables that we read out separately from the database

 <ListView x:Name="listView" ItemsSource="{Binding DatabaseTables, Mode=TwoWay}" DisplayMemberPath="Name" SelectedItem="{Binding DatabaseTable, Mode=TwoWay}">

and by selecting a table we want all the entries shown in the Grid with the expecting columns from the DataTable

I tried to look around, but couldn't find a solution.


Solution

  • This should work:

    MainPageViewModel.cs

    using CommunityToolkit.Mvvm.ComponentModel;
    using CommunityToolkit.Mvvm.Input;
    using System.Collections.ObjectModel;
    using System.Data;
    using System.Threading.Tasks;
    
    namespace DataTableExample;
    
    // This class needs to be "partial" 
    // for the CommunityToolkit.NuGet package source generators.
    public partial class  MainPageViewModel : ObservableObject
    {
        [ObservableProperty]
        // "Items" property will be auto-generated for you.
        private ObservableCollection<object>? _items;
    
        [RelayCommand]
        // "LoadItemsCommand" will be auto-generated for you.
        private async Task LoadItems()
        {
            DataTable dataTable = await LoadItemsFromDataBase();
    
            ObservableCollection<object> items = new();
    
            foreach (DataRow row in dataTable.Rows)
            {
                items.Add(row.ItemArray);
            }
    
            Items = items;
        }
    
        private Task<DataTable> LoadItemsFromDataBase()
        {
            return Task.Run(() =>
            {
                DataTable dataTable = new();
                dataTable.Columns.Add("Id", typeof(int));
                dataTable.Columns.Add("Name", typeof(string));
                dataTable.Columns.Add("Price", typeof(decimal));
                dataTable.Rows.Add(1, "Product 1", 10.0);
                dataTable.Rows.Add(2, "Product 2", 20.0);
                dataTable.Rows.Add(3, "Product 3", 30.0);
                return dataTable;
            });
        }
    }
    

    MainPage.xaml.cs

    using Microsoft.UI.Xaml.Controls;
    
    namespace DataTableExample;
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
        }
    
        private MainPageViewModel ViewModel => new();
    }
    

    MainPage.xaml

    <Page
        x:Class="DataTableExample.MainPage"
        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:local="using:DataTableExample"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:toolkit="using:CommunityToolkit.WinUI.UI.Controls"
        Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
        mc:Ignorable="d">
    
        <Grid RowDefinitions="Auto,*">
            <Button
                Grid.Row="0"
                Command="{x:Bind ViewModel.LoadItemsCommand}"
                Content="Load items" />
            <toolkit:DataGrid
                Grid.Row="1"
                AutoGenerateColumns="False"
                ItemsSource="{x:Bind ViewModel.Items, Mode=OneWay}">
                <toolkit:DataGrid.Columns>
                    <toolkit:DataGridTextColumn
                        Binding="{Binding [0]}"
                        Header="ID" />
                    <toolkit:DataGridTextColumn
                        Binding="{Binding [1]}"
                        Header="Name" />
                    <toolkit:DataGridTextColumn
                        Binding="{Binding [2]}"
                        Header="Price" />
                </toolkit:DataGrid.Columns>
            </toolkit:DataGrid>
        </Grid>
    
    </Page>
    

    NOTE:

    I'm using the CommunityToolkit.Mvvm NuGet package for the MVVM design.

    UPDATE:

    I came up with another solution that might be a bit closer to your requirements. This way you won't need to declare each column in XAML.

    DataGridExtensions.cs

    using CommunityToolkit.WinUI.UI.Controls;
    using Microsoft.UI.Xaml;
    using Microsoft.UI.Xaml.Data;
    using System.Collections.ObjectModel;
    
    namespace DataTableExample;
    
    public static class DataGridExtensions
    {
        public static readonly DependencyProperty ColumnsProperty =
            DependencyProperty.RegisterAttached(
                "Columns",
                typeof(ObservableCollection<Column>),
                typeof(DataGridExtensions),
                new PropertyMetadata(
                    new ObservableCollection<Column>(),
                    OnColumnsPropertyChanged));
    
        public static ObservableCollection<Column> GetColumns(DependencyObject obj)
        {
            return (ObservableCollection<Column>)obj.GetValue(ColumnsProperty);
        }
    
        public static void SetColumns(DependencyObject obj, ObservableCollection<Column> value)
        {
            obj.SetValue(ColumnsProperty, value);
        }
    
        private static void OnColumnsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is not DataGrid dataGrid ||
                e.NewValue is not ObservableCollection<Column> columns)
            {
                return;
            }
    
            dataGrid.Columns.Clear();
    
            foreach (Column column in columns)
            {
                Binding binding = new()
                {
                    Path = new PropertyPath(column.Binding)
                };
    
                dataGrid.Columns.Add(
                    new DataGridTextColumn
                    {
                        Header = column.Header,
                        Binding = binding,
                    });
            }
        }
    
        public class Column
        {
            public string? Header { get; set; }
    
            public string? Binding { get; set; }
        }
    }
    

    MainPageViewModel.cs

    using CommunityToolkit.Mvvm.ComponentModel;
    using CommunityToolkit.Mvvm.Input;
    using System.Collections.ObjectModel;
    using System.Data;
    using System.Threading.Tasks;
    
    namespace DataTableExample;
    
    public partial class  MainPageViewModel : ObservableObject
    {
        [ObservableProperty]
        private ObservableCollection<object>? _items;
    
        [ObservableProperty]
        private ObservableCollection<DataGridExtensions.Column>? _columns;
    
        [RelayCommand]
        private async Task LoadItems()
        {
            DataTable dataTable = await LoadItemsFromDataBase();
    
            ObservableCollection<DataGridExtensions.Column> columns = new();
            ObservableCollection<object> items = new();
    
            int index = 0;
    
            foreach (DataColumn column in dataTable.Columns)
            {
                columns.Add(
                    new DataGridExtensions.Column {
                        Header = column.ColumnName,
                        Binding = $"[{index++}]"
                    });
            }
    
            foreach (DataRow row in dataTable.Rows)
            {
                items.Add(row.ItemArray);
            }
    
            Columns = columns;
            Items = items;
        }
    
        private Task<DataTable> LoadItemsFromDataBase()
        {
            return Task.Run(() =>
            {
                DataTable dataTable = new();
                dataTable.Columns.Add("Id", typeof(int));
                dataTable.Columns.Add("Name", typeof(string));
                dataTable.Columns.Add("Price", typeof(decimal));
                dataTable.Rows.Add(1, "Product 1", 10.0);
                dataTable.Rows.Add(2, "Product 2", 20.0);
                dataTable.Rows.Add(3, "Product 3", 30.0);
                return dataTable;
            });
        }
    }
    

    and...

    MainPage.xaml

    <Page
        x:Class="DataTableExample.MainPage"
        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:local="using:DataTableExample"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:toolkit="using:CommunityToolkit.WinUI.UI.Controls"
        Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
        mc:Ignorable="d">
    
        <Grid RowDefinitions="Auto,*">
            <Button
                Grid.Row="0"
                Command="{x:Bind ViewModel.LoadItemsCommand}"
                Content="Load items" />
            <toolkit:DataGrid
                Grid.Row="1"
                local:DataGridExtensions.Columns="{x:Bind ViewModel.Columns, Mode=OneWay}"
                AutoGenerateColumns="False"
                ItemsSource="{x:Bind ViewModel.Items, Mode=OneWay}" />
        </Grid>
    
    </Page>