4
votes

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);
}
6
Did you assign the DataContext anywhere?Lei Yang
Yes, it is done in a resource dictionary merged in the app.xamlGuillaumeA
look at my answer, you have to set the datacontext the right wayblindmeis
The DataContext is not the issue, every other control is well bound. Furthermore, rows are added to the datagrid, but nothing is displayed.GuillaumeA

6 Answers

2
votes

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();
    }
}
0
votes

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

0
votes

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;
        }
0
votes

your code works for me if i add the following

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

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 ;)

0
votes

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.