How to properly sync a List with a DataGridView in WinForms without manual row clearing? [closed]

15 hours ago 3
ARTICLE AD BOX

I am building a student grading system in WinForms (C#). Currently, I store my data in a List and I display it in a DataGridView.To update the UI, I am manually clearing and rebuilding the rows every time the data changes:

The problem: This approach causes visible flickering, loses the current selection/scroll position, and feels inefficient as the list grows.

What I want to achieve: I want to use a more robust way (like BindingSource or BindingList) to keep the List and the DataGridView in sync. I've heard about Data Binding but I'm not sure how to implement it correctly while still being able to:

Format certain values (e.g., adding "%" to percentage). Filter the data (e.g., by Subject). Handle Boolean values as "Yes/No" text instead of CheckBoxes.

What is the standard "WinForms way" to bind a custom object collection to a Grid without manual row manipulation?

StudentResult.cs:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TestGradingSystem { public class StudentResult { public string Name { get; set; } public string Subject { get; set; } public int Score { get; set; } public int MaxScore { get; set; } public double Percentage { get; set; } public int Grade { get; set; } public bool Passed { get; set; } public bool AdvancedLevel { get; set; } public string TestType { get; set; } } }

Form1.cs:

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace TestGradingSystem { public partial class Form1 : Form { List<StudentResult> results = new List<StudentResult>(); public Form1() { InitializeComponent(); dataGridView1.Columns.Clear(); dataGridView1.Columns.Add("Name", "Name"); dataGridView1.Columns.Add("Subject", "Subject"); dataGridView1.Columns.Add("Score", "Score"); dataGridView1.Columns.Add("MaxScore", "Max Score"); dataGridView1.Columns.Add("Percentage", "Percentage"); dataGridView1.Columns.Add("Grade", "Grade"); dataGridView1.Columns.Add("Passed", "Passed"); dataGridView1.Columns.Add("AdvancedLevel", "Advanced Level"); dataGridView1.Columns.Add("TestType", "Test Type"); dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect; dataGridView1.MultiSelect = false; dataGridView1.ReadOnly = true; dataGridView1.AllowUserToAddRows = false; dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill; comboBox1.Items.Clear(); comboBox1.Items.Add("All Subjects"); comboBox1.Items.Add("Mathematics"); comboBox1.Items.Add("Computer Science"); comboBox1.Items.Add("History"); comboBox1.Items.Add("English"); comboBox1.SelectedIndex = 0; } private void RefreshGrid(List<StudentResult> listToShow = null) { dataGridView1.Rows.Clear(); List<StudentResult> list = listToShow ?? results; foreach (StudentResult item in list) { dataGridView1.Rows.Add( item.Name, item.Subject, item.Score, item.MaxScore, item.Percentage.ToString("0.00") + "%", item.Grade, item.Passed ? "Yes" : "No", item.AdvancedLevel ? "Yes" : "No", item.TestType ); } RefreshSummary(); } private void RefreshSummary() { int count = results.Count; label4.Text = count.ToString(); if (count == 0) { label5.Text = "0%"; label6.Text = "0"; label10.Text = "0"; label11.Text = "0"; label12.Text = "-"; return; } double avgPercentage = results.Average(x => x.Percentage); double avgGrade = results.Average(x => x.Grade); int passedCount = results.Count(x => x.Passed); int failedCount = results.Count(x => !x.Passed); StudentResult best = results.OrderByDescending(x => x.Percentage).First(); int totalStudents = passedCount + failedCount; double passRate = 0; if (totalStudents > 0) { passRate = (double)passedCount / totalStudents * 100; } label5.Text = avgPercentage.ToString("0.00") + "%"; label6.Text = avgGrade.ToString("0.00"); label10.Text = passedCount.ToString(); label11.Text = failedCount.ToString(); label12.Text = passRate.ToString("0.00") + "%"; } private void newStudentToolStripMenuItem_Click(object sender, EventArgs e) { Form2 form = new Form2(); if (form.ShowDialog() == DialogResult.OK) { results.Add(form.Result); RefreshGrid(); } } private void deleteSelectedToolStripMenuItem_Click(object sender, EventArgs e) { if (dataGridView1.SelectedRows.Count == 0) { MessageBox.Show("Select a row first!"); return; } int index = dataGridView1.SelectedRows[0].Index; results.RemoveAt(index); RefreshGrid(); } private void deleteAllToolStripMenuItem_Click(object sender, EventArgs e) { DialogResult answer = MessageBox.Show( "Are you sure you want to delete everything?", "Confirmation", MessageBoxButtons.YesNo, MessageBoxIcon.Warning ); if (answer == DialogResult.Yes) { results.Clear(); RefreshGrid(); } } private void exitToolStripMenuItem_Click(object sender, EventArgs e) { Application.Exit(); } private void bestResultToolStripMenuItem_Click(object sender, EventArgs e) { if (results.Count == 0) { MessageBox.Show("No data!"); return; } StudentResult best = results.OrderByDescending(x => x.Percentage).First(); label16.Text = best.Name; label17.Text = best.Percentage.ToString("0.00") + "%"; label18.Text = best.Grade.ToString(); } private void calculateAvaragesToolStripMenuItem_Click(object sender, EventArgs e) { RefreshSummary(); MessageBox.Show("Statistics updated!"); } private void filterPassedStudentsToolStripMenuItem_Click(object sender, EventArgs e) { List<StudentResult> passedOnly = results .Where(x => x.Passed) .ToList(); RefreshGrid(passedOnly); } private void sortByPercentageToolStripMenuItem_Click(object sender, EventArgs e) { } private void ascendingToolStripMenuItem_Click(object sender, EventArgs e) { results = results.OrderBy(x => x.Percentage).ToList(); RefreshGrid(); } private void descendingToolStripMenuItem_Click(object sender, EventArgs e) { results = results.OrderByDescending(x => x.Percentage).ToList(); RefreshGrid(); } private void statisticsToolStripMenuItem_Click(object sender, EventArgs e) { if (results.Count == 0) { MessageBox.Show("No data!"); return; } int total = results.Count; int passed = results.Count(x => x.Passed); int failed = results.Count(x => !x.Passed); double avgPercentage = results.Average(x => x.Percentage); double avgGrade = results.Average(x => x.Grade); MessageBox.Show( "Total students: " + total + "\nPassed students: " + passed + "\nFailed students: " + failed + "\nAverage percentage: " + avgPercentage.ToString("0.00") + "%" + "\nAverage grade: " + avgGrade.ToString("0.00"), "Statistics" ); } private void button1_Click(object sender, EventArgs e) { string nameSearch = textBox1.Text.Trim().ToLower(); string subjectFilter = comboBox1.SelectedItem?.ToString(); bool passedOnly = checkBox1.Checked; List<StudentResult> filtered = results; if (nameSearch != "") { filtered = filtered .Where(x => x.Name.ToLower().Contains(nameSearch)) .ToList(); } if (subjectFilter != null && subjectFilter != "All Subjects") { filtered = filtered .Where(x => x.Subject == subjectFilter) .ToList(); } if (passedOnly) { filtered = filtered .Where(x => x.Passed) .ToList(); } if (filtered.Count == 0) { MessageBox.Show("No matching result!"); } RefreshGrid(filtered); } private void searchByNameToolStripMenuItem_Click(object sender, EventArgs e) { textBox1.Focus(); } private void exportToTXTToolStripMenuItem_Click(object sender, EventArgs e) { SaveFileDialog save = new SaveFileDialog(); save.Filter = "Text File|*.txt"; if (save.ShowDialog() == DialogResult.OK) { using (StreamWriter writer = new StreamWriter(save.FileName)) { foreach (StudentResult item in results) { writer.WriteLine( item.Name + " | " + item.Subject + " | " + item.Score + "/" + item.MaxScore + " | " + item.Percentage.ToString("0.00") + "% | " + item.Grade ); } } MessageBox.Show("TXT export completed!"); } } private void exportToCSVToolStripMenuItem_Click(object sender, EventArgs e) { SaveFileDialog save = new SaveFileDialog(); save.Filter = "CSV File|*.csv"; if (save.ShowDialog() == DialogResult.OK) { using (StreamWriter writer = new StreamWriter(save.FileName)) { writer.WriteLine( "Name,Subject,Score,MaxScore,Percentage,Grade,Passed,AdvancedLevel,TestType" ); foreach (StudentResult item in results) { writer.WriteLine( item.Name + "," + item.Subject + "," + item.Score + "," + item.MaxScore + "," + item.Percentage.ToString("0.00") + "," + item.Grade + "," + item.Passed + "," + item.AdvancedLevel + "," + item.TestType ); } } MessageBox.Show("CSV export completed!"); } } private void saveToolStripMenuItem_Click(object sender, EventArgs e) { SaveFileDialog save = new SaveFileDialog(); save.Filter = "Data File|*.dat"; if (save.ShowDialog() == DialogResult.OK) { using (StreamWriter writer = new StreamWriter(save.FileName)) { foreach (StudentResult item in results) { writer.WriteLine( item.Name + ";" + item.Subject + ";" + item.Score + ";" + item.MaxScore + ";" + item.Percentage + ";" + item.Grade + ";" + item.Passed + ";" + item.AdvancedLevel + ";" + item.TestType ); } } MessageBox.Show("Data saved!"); } } private void loadToolStripMenuItem_Click(object sender, EventArgs e) { OpenFileDialog open = new OpenFileDialog(); open.Filter = "Data File|*.dat"; if (open.ShowDialog() == DialogResult.OK) { results.Clear(); string[] lines = File.ReadAllLines(open.FileName); foreach (string line in lines) { string[] parts = line.Split(';'); StudentResult item = new StudentResult { Name = parts[0], Subject = parts[1], Score = int.Parse(parts[2]), MaxScore = int.Parse(parts[3]), Percentage = double.Parse(parts[4]), Grade = int.Parse(parts[5]), Passed = bool.Parse(parts[6]), AdvancedLevel = bool.Parse(parts[7]), TestType = parts[8] }; results.Add(item); } RefreshGrid(); MessageBox.Show("Data loaded!"); } } } }

Form2.cs:

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace TestGradingSystem { public partial class Form2 : Form { public StudentResult Result { get; private set; } public Form2() { InitializeComponent(); numericUpDown1.ValueChanged += (s, e) => UpdateInfo(); numericUpDown2.ValueChanged += (s, e) => UpdateInfo(); checkBox1.CheckedChanged += (s, e) => UpdateInfo(); comboBox1.Items.Clear(); comboBox1.Items.Add("Mathematics"); comboBox1.Items.Add("Computer Science"); comboBox1.Items.Add("History"); comboBox1.Items.Add("English"); numericUpDown1.Minimum = 0; numericUpDown1.Maximum = 500; numericUpDown2.Minimum = 1; numericUpDown2.Maximum = 500; numericUpDown2.Value = 100; UpdateInfo(); } private void UpdateInfo() { int score = (int)numericUpDown1.Value; int maxScore = (int)numericUpDown2.Value; double percentage = (double)score / maxScore * 100; if (checkBox1.Checked) { percentage += 5; } if (percentage > 100) { percentage = 100; } int grade; if (percentage < 40) grade = 1; else if (percentage < 55) grade = 2; else if (percentage < 70) grade = 3; else if (percentage < 85) grade = 4; else grade = 5; bool passed = percentage >= 40; label9.Text = percentage.ToString("0.00") + "%"; label10.Text = grade.ToString(); label11.Text = passed ? "Yes" : "No"; } private void button2_Click(object sender, EventArgs e) { if (textBox1.Text.Trim() == "") { MessageBox.Show("Name is required!"); return; } if (comboBox1.SelectedItem == null) { MessageBox.Show("Select a subject!"); return; } if (numericUpDown1.Value > numericUpDown2.Value) { MessageBox.Show("Score cannot be greater than max score!"); return; } if (!radioButton1.Checked && !radioButton2.Checked &&!radioButton3.Checked) { MessageBox.Show("Select a test type!"); return; } string testType = ""; if (radioButton1.Checked) testType = "Quiz"; else if (radioButton2.Checked) testType = "Final Test"; else if (radioButton3.Checked) testType = "Exam"; int score = (int)numericUpDown1.Value; int maxScore = (int)numericUpDown2.Value; double percentage = (double)score / maxScore * 100; if (checkBox1.Checked) { percentage += 5; } if (percentage > 100) { percentage = 100; } int grade; if (percentage < 40) grade = 1; else if (percentage < 55) grade = 2; else if (percentage < 70) grade = 3; else if (percentage < 85) grade = 4; else grade = 5; bool passed = percentage >= 40; Result = new StudentResult { Name = textBox1.Text.Trim(), Subject = comboBox1.SelectedItem.ToString(), Score = score, MaxScore = maxScore, Percentage = percentage, Grade = grade, Passed = passed, AdvancedLevel = checkBox1.Checked, TestType = testType }; DialogResult = DialogResult.OK; Close(); } private void button1_Click(object sender, EventArgs e) { DialogResult = DialogResult.Cancel; Close(); } } }
Read Entire Article