GuillaumeA
GuillaumeA

Reputation: 3545

WPF MVVM Bind DataTable to DataGrid does not display data

I've got a simple control which contains a DataGrid which ItemsSource is bound to a DataTable. When I fill the DataTable, I can see that rows are added in the DataGrid, but no data is displayed. I don't use any special style for this DataGrid (take the default one), the only setting is the AutoGenerateColumn set to True.

In the XAML

<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding TableResult}"/>

In the view model

private DataTable tableResult = new DataTable();
public DataTable TableResult
{
   get { return tableResult; }
   set { tableResult = value; OnPropertyChanged("TableResult"); }
}

private void FillTable()
{
   DataColumn c = new DataColumn();
   c.ColumnName = "Col1";
   this.TableResult.Columns.Add(c);

   c = new DataColumn();
   c.ColumnName = "Col2";
   this.TableResult.Columns.Add(c);

   DataRow row1 = this.TableResult.NewRow();
   row1["Col1"] = "Blue";
   row1["Col2"] = "12";;
   this.TableResult.Rows.Add(row1);

   DataRow row2 = this.TableResult.NewRow();
   row2["Col1"] = "Red";
   row2["Col2"] = "18";
   this.TableResult.Rows.Add(row2);

   DataRow row3 = this.TableResult.NewRow();
   row3["Col1"] = "Yellow";
   row3["Col2"] = "27";
   this.TableResult.Rows.Add(row3);
}

Upvotes: 5

Views: 11703

Answers (6)

DavidFromPhila
DavidFromPhila

Reputation: 11

I just wrestled with this and found another answer I have found very reliable that incorporates some but not all of what others here have contributed. The Xaml (assumes datacontext is set at the page level):

<DataGrid ItemsSource="{Binding Findings}" />

The code in the ViewModel (assumes a DataTable is created with data):

Findings = null;
Findings = dataTable.DefaultView

The key thing is to explicitly set the bound property to null first. I have found this to be true in WinForms as well.

Upvotes: 0

StepUp
StepUp

Reputation: 38209

Working example of populating data of DataGrid:

    public MyViewModel()//constructor of MyViewModel
    {
        FillMyDataGrid();            
    }

    private DataTable employeeDataTable;

    public DataTable EmployeeDataTable
    {
        get { return employeeDataTable; }
        set
        {
            employeeDataTable = value;
            OnPropertyChanged("EmployeeDataTable");
        }
    }

    public FillMyDataGrid()
    {
            var _ds = new DataSet("Test");
            employeeDataTable = new DataTable();
            employeeDataTable = _ds.Tables.Add("DT");
            for (int i = 0; i < 50; i++)
            {
                //employeeDataTable.Columns.Add(i.ToString() + ".");
                employeeDataTable.Columns.Add(i.ToString());
            }
            for (int i = 0; i < 2; i++)
            {
                var theRow = employeeDataTable.NewRow();
                for (int j = 0; j < 50; j++)
                {                       
                        theRow[j] = "a";

                }
                employeeDataTable.Rows.Add(theRow);
            }
   }

XAML code:

<DataGrid ItemsSource="{Binding EmployeeDataTable}"/>

Update:

I've tested your example and it works correctly, but you should call your method FillTable() in constructor to populate DataTable. For example:

public class YourViewModel
{
    public YourViewModel()
    {  
        FillTable();
    }
}

Upvotes: 2

Tyress
Tyress

Reputation: 3653

Your ItemsSource would need to be a List of objects, not a DataRow/Column. In any case referencing these sorts of things on the ViewModel breaks MVVM!

So:

public List<Result> TableResult {get;set;} // raise property change on set

and

public class Result 
{
public string Color {get;set;}
public int Number {get;set;}
}

EDIT: Since you don't know anything about the table itself beforehand, I suggest you use a simple List of List of Strings to produce your rows and columns.

Something like this:

    public List<List<string>> TableResult { get; set; } // raise property changed

and

        List<string> columns = new List<string>() { "Col1", "Col2", "Col3", "Col4" };

        List<string> row1 = new List<string>() { "a", "b", "c", "d" };
        List<string> row2 = new List<string>() { "w", "x", "y", "z" };
        List<List<string>> table = new List<List<string>>(){columns,row1,row2};
        TableResult = table;

You then must create a converter to convert your rows into a DataTable:

public class ListToTables : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var list =  value as List<List<string>>;
        var headers = list.First();
        DataTable dt = new DataTable();

        foreach (string header in headers)
        {
            dt.Columns.Add(header);
        }
        foreach (List<string> row in list.Skip(1))
        {
            int index = 0;
            DataRow r = dt.NewRow();
            foreach (string col in row)
            {
                r[index++] = col;
            }
            dt.Rows.Add(r);
        }
        return dt;

    }

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

on your XAML, add this to your resources (e.g. UserControl.Resources):

        <local:ListToTables x:Key="listToTables"></local:ListToTables>

where "local" is the added namespace of your converter, and:

    <DataGrid ItemsSource="{Binding TableResult,Converter={StaticResource listToTables}}" >

for your DataGrid.

enter image description here

EDIT FINAL:

You can keep your DataTable if you believe MVVM allows it, just replace the latter part of your code with this:

   DataTable tempDataTable = new DataTable();
   DataColumn c = new DataColumn();
   c.ColumnName = "Col1";
   tempDataTable.Columns.Add(c);

   c = new DataColumn();
   c.ColumnName = "Col2";
   tempDataTable.Columns.Add(c);

   DataRow row1 = tempDataTable.NewRow();
   row1["Col1"] = "Blue";
   row1["Col2"] = "12";;
   tempDataTable.Rows.Add(row1);

   DataRow row2 = tempDataTable.NewRow();
   row2["Col1"] = "Red";
   row2["Col2"] = "18";
   tempDataTable.Rows.Add(row2);

   DataRow row3 = tempDataTable.NewRow();
   row3["Col1"] = "Yellow";
   row3["Col2"] = "27";
   tempDataTable.Rows.Add(row3);
   this.DataTable = tempDataTable;

You have to set the DataTable to raise property changed. Adding to it won't do anything ;)

Upvotes: 0

blindmeis
blindmeis

Reputation: 22445

your code works for me if i add the following

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        FillTable();
        DataContext = this;
    }

Upvotes: 0

Sajeetharan
Sajeetharan

Reputation: 222722

You need to call DefaultView, here is the change in FillTable() Method,

 private void FillTable()
        {
            DataColumn c = new DataColumn();
            c.ColumnName = "Col1";
            this.TableResult.Columns.Add(c);
            c = new DataColumn();
            c.ColumnName = "Col2";
            this.TableResult.Columns.Add(c);
            DataRow row1 = this.TableResult.NewRow();
            row1["Col1"] = "Blue";
            row1["Col2"] = "12"; ;
            this.TableResult.Rows.Add(row1);
            DataRow row2 = this.TableResult.NewRow();
            row2["Col1"] = "Red";
            row2["Col2"] = "18";
            this.TableResult.Rows.Add(row2);
            DataRow row3 = this.TableResult.NewRow();
            row3["Col1"] = "Yellow";
            row3["Col2"] = "27";
            this.TableResult.Rows.Add(row3);
            dataGrid1.ItemsSource = this.tableResult.DefaultView;
        }

Upvotes: 0

sujith karivelil
sujith karivelil

Reputation: 29036

You have to define your columns too: for example:

<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Source=TableResult}" >
            <DataGrid.Columns>
                <DataGridTextColumn Header="Name" Binding="{Binding col1}"/>
                <DataGridTextColumn Header="Surname" Binding="{Binding col2}"/>
                <DataGridTextColumn Header="Phone" Binding="{Binding col3}" />
            </DataGrid.Columns>
 </DataGrid>

And ensure that you have implemented INotifyPropertyChanged in the view model

Upvotes: 0

Related Questions