1
votes

I am trying to display a DataTable onto a DataGrid which has two columns.

When I update the DataTable, the DataGrid shows new rows but the cells are empty. I have looked through many different possible solutions for this, and still have not been able to display the results.

Here is my xaml code for the DataGrid:

<DataGrid x:Name="SubjectsList" Height="500" ScrollViewer.CanContentScroll="True" AutoGenerateColumns="False" CanUserAddRows="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Subject" Width="2*"/>
        <DataGridTextColumn Header="Weekly" Width="*"/>
    </DataGrid.Columns>
</DataGrid>

The following is my C# code for updating the table:

public void AddSubject(object sender, RoutedEventArgs e)
{
    Subject temp = new Subject(SubjectName.Text, Convert.ToInt32(PerWeek.Text));

    subjects.Add(temp);
    MessageBox.Show(temp.Name + " has been added");

    for(int i = 0; i < subjectsTable.Rows.Count; i++)
    {
        subjectsTable.Rows.RemoveAt(i);
    }

    foreach (Subject subject in subjects)
    {
        DataRow dataRow = subjectsTable.NewRow();
        dataRow[0] = subject.Name;
        dataRow[1] = subject.ClassesPerWeek;
        subjectsTable.Rows.Add(dataRow);
        MessageBox.Show(subject.Name);
    }

    SubjectsList.ItemsSource = subjectsTable.DefaultView;
}

In the above code, SubjectsList is my DataGrid, and subjectsTable is my DataTable.

I have tried the following:

  1. Use DataGrid.DataContext instead of DataGrid.ItemSource
  2. Added ItemSource = "{Binding Path=subjectsTable}" in my xaml code
  3. Tried to add the rows as items using DataGrid.Items.Add(dataRow)
  4. Added a getter and setter method for each of the data members of my user-defined class Subject
  5. All of my variables, data members and data structures are public.

If anybody knows how to make the data visible, then please help me. Thank you.

Here is what happens after I have added two subjects:

Output of DataGrid

2

2 Answers

2
votes

You need to specify the binding for each column in the DataGrid.

The Binding paths will be the names of the columns in the DataTable.

Assuming your DataTable columns are defined like this (you haven't shown this so I had to just give an example):

subjectsTable.Columns.Add("NameColumn", typeof(string));
subjectsTable.Columns.Add("ClassesColumn", typeof(int));

The DataGrid column definitions in the XAML should look like this:

<DataGridTextColumn Header="Subject" Width="2*" Binding="{Binding NameColumn}"/>
<DataGridTextColumn Header="Weekly" Width="*" Binding="{Binding ClassesColumn}"/>

An alternative is to set the AutoGenerateColumns property of the DataGrid to true and omit the column definitions in the XAML. But then you don't have as much control over the grid.

1
votes

In case you want to consider using MVVM pattern (https://intellitect.com/getting-started-model-view-viewmodel-mvvm-pattern-using-windows-presentation-framework-wpf/), here is a basic implementation:

Create your ViewModel:

       public class ViewModel : INotifyPropertyChanged
       {
        public ViewModel()
        {
            CreateTestData();
            AddSubjectCommand = new Command(AddSubject);
        }

        public ICommand AddSubjectCommand { get; }

        private ObservableCollection<Subject> _subjects;
        public ObservableCollection<Subject> Subjects
        {
            get => _subjects;
            set
            {
                _subjects = value;
                OnPropertyChanged();
            }
        }

        public void AddSubject()
        {
            Subjects = new ObservableCollection<Subject>();
            DataTable subjectsTable = new DataTable();

            foreach (Subject subject in subjects)
            {
                //DataRow dataRow = subjectsTable.NewRow();
                //dataRow[0] = subject.Name;
                //dataRow[1] = subject.ClassesPerWeek;
                //subjectsTable.Rows.Add(dataRow);

                Subjects.Add(new Subject
                {
                    Name = subject.Name,
                    ClassesPerWeek = subject.ClassesPerWeek
                });
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        #region Test data

        public IList<Subject> subjects { get; set; }

        private void CreateTestData()
        {
            subjects = new List<Subject>();
            subjects.Add(new Subject { Name = "Subject 1", ClassesPerWeek = 5 });
            subjects.Add(new Subject { Name = "Subject 2", ClassesPerWeek = 10 });
        }

        #endregion
        }

Stuff that you need to understand here:

  1. ObservableCollection: https://www.c-sharpcorner.com/UploadFile/e06010/observablecollection-in-wpf/
  2. INotifyPropertyChanged: https://www.c-sharpcorner.com/article/use-inotifypropertychanged-interface-in-wpf-mvvm/
  3. ICommand: https://www.c-sharpcorner.com/UploadFile/e06010/wpf-icommand-in-mvvm/

Create your command that implements ICommand:

public class Command : ICommand
    {
        private readonly Action _action;
        private readonly bool _canExecute;

        public Command(Action action, bool canExecute = true)
        {
            _action = action;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute;
        }

        public void Execute(object parameter)
        {
            _action();
        }

        public event EventHandler CanExecuteChanged;
    }

Code-behind (Ain't it clean?):

 public partial class MainWindow
    {
        public MainWindow()
        {
            this.InitializeComponent();
        }
    }

Xaml:

<Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Button Content="Add Subject" Command="{Binding AddSubjectCommand}" Width="100" Height="30" HorizontalAlignment="Left" />
        <DataGrid Grid.Row="1" x:Name="SubjectsList" ItemsSource="{Binding Subjects}" Height="500" ScrollViewer.CanContentScroll="True" AutoGenerateColumns="False" CanUserAddRows="False">
            <DataGrid.Columns>
                <DataGridTemplateColumn Header="Subject" Width="100">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Name}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTemplateColumn Header="Weekly" Width="100">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding ClassesPerWeek}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>

See the following:

  1. DataContext is the ViewModel
  2. Grid itemsource is my ObservableCollection
  3. Button command is bound to AddSubjectCommand that's in the ViewModel

Sample Output

enter image description here

enter image description here