How to Build a School Grade System App in WPF DataGrid

Summarize this blog post with:

TL;DR: Learn how to implement custom filtering, sorting, grouping, and column visibility in a WPF DataGrid using the MVVM pattern. Using a school grade management application as an example, this guide demonstrates how to build a responsive, data-driven interface that helps users efficiently organize, analyze, and manage large datasets.

Modern desktop applications often need more than just displaying data. Users expect powerful capabilities such as filtering records, sorting information, grouping related data, and customizing column visibility to quickly analyze large datasets.

Rather than building these features from scratch, you can leverage the WPF DataGrid together with the MVVM pattern to create rich and interactive data-management experiences.

In this guide, we’ll use a school grade management system as a practical example to demonstrate how to implement sorting, filtering, and grouping in a Syncfusion® WPF DataGrid and a clean MVVM approach.

Let’s get started.

Step 1: Define data model

Start by creating a model that represents individual student records, as shown in the following code example.

public class Grade : INotifyPropertyChanged
{
    private int _id;
    private string _studentName;
    private string _subjectName;
    private double _assignmentScore;
    private double _quizScore;
    private double _examScore;
    private double _projectScore;
    private string _comments;

    public int ID
    {
        get
        {
            return _id;
        }
        set
        {
            _id = value;
            OnPropertyChanged(nameof(ID));
        }
    }

    …
    …

    public event PropertyChangedEventHandler? PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public double CalculateFinalGrade()
    {
        // Simple example calculation. Real-world grading systems often use weighted score calculations based on academic requirements.
        return (AssignmentScore + QuizScore + ExamScore + ProjectScore) / 4;
    }
}

Step 2: Create the ViewModel

The ViewModel ties everything together by storing data, defining grid columns, and handling user interactions. In this step, we’ll create the GradeSheetViewModel class to populate the Grades collection for ItemsSource binding, configure column definitions, and implement data manipulation logic.

public class GradeSheetViewModel : INotifyPropertyChanged
{
    private ObservableCollection<Grade> grades;

    public ObservableCollection<Grade> Grades
    {
        get
        {
            return grades;
        }
        set
        {
            grades = value;
            OnPropertyChanged(nameof(Grades));
        }
    }    
}

To populate the grades collection with default student data, refer to the code example below.

private void PopulateCollection()
{
    Grades = new ObservableCollection<Grade>();
    //Add sample data here
}

Configure DataGrid columns

Instead of auto-generating columns, explicitly defining them gives you full control over how data is displayed.

private void InitializeColumns()
{
    columns = new Columns();
    var studentNameIdUnboundColumn = new GridUnBoundColumn
    {
        MappingName = "StudentID",
        HeaderText = "Student ID"
    };
    …
    …
    columns.Add(gradeColumn);
    var commentsColumn = new GridTextColumn
    {
        MappingName = "Comments",
        HeaderText = "Comments"
    };
    columns.Add(commentsColumn);
}

You can add columns to the Columns collection using specialized objects like GridTextColumn and GridNumericColumn, chosen based on your data type requirements.

Each column requires two key properties:

  • MappingName: Specifies which data source property the column binds to.
  • HeaderText: Defines the column’s display title.

Step 3: Design the UI

Bind the XAML UI to the ViewModel and use the Columns property to connect the DataGrid with column definitions, ensuring it updates dynamically.

<syncfusion:SfDataGrid ItemsSource="{Binding Grades}"
                       Columns="{Binding Columns}"
                       AutoGenerateColumnsMode="None"
                       AllowTriStateSorting="True"
                       AutoExpandGroups="True"
                       ColumnSizer="SizeToHeader">
    <behaviors:Interaction.Behaviors>
        <local:DataGridBehavior/>
    </behaviors:Interaction.Behaviors>
</syncfusion:SfDataGrid>

What’s happening here?

  • ItemsSource binds your student data
  • Columns ensure your custom column definitions are applied
  • Sorting and grouping options are enabled out of the box

With just a few properties, the grid becomes highly interactive.

Step 4: Add data operations

A grading system isn’t complete without methods for analyzing and manipulating data. Let’s add support for filtering, sorting, grouping, and column visibility.

public class GradeSheetViewModel : INotifyPropertyChanged
{
    // Commands for opening a pop-up to handle columns and data manipulation.
    public ICommand HideColumns { get; set; }
    public ICommand HideAllColumns { get; set; }
    public ICommand ShowAllColumns { get; set; }
    public ICommand FilterColumns { get; set; }
    public ICommand ClearFilter { get; set; }
    public ICommand GroupColumns { get; set; }
    public ICommand ClearGroup { get; set; }
    public ICommand SortColumns { get; set; }
    public ICommand ClearSort { get; set; }

    private void InitializeCommands()
    {
        HideColumns = new RelayCommand(_=>ExecuteHideColumns());
        ShowAllColumns = new RelayCommand(_=>ExecuteShowAllColumns());
        HideAllColumns = new RelayCommand(_=>ExecuteHideAllColumns());
        FilterColumns = new RelayCommand(_=>ExecuteFilterColumns());
        ClearFilter = new RelayCommand(_=>ExecuteClearFilter());
        GroupColumns = new RelayCommand(_=>ExecuteGroupColumns());
        ClearGroup = new RelayCommand(_=>ExecuteClearGroups());
        SortColumns = new RelayCommand(_=>ExecuteSortColumns());
        ClearSort = new RelayCommand(_=>ExecuteClearSorts());
    }
}

In the above code example,

  • The HideColumnsShowAllColumns, and HideAllColumns commands manage column visibility in the DataGrid.
  • HideColumns and ShowAllColumns are linked to UI buttons or actions, toggling visibility.
  • The ExecuteHideAllColumns and ExecuteShowAllColumns methods iterate through the column collection to set the IsHidden property.

Column visibility

You can easily toggle columns using the code example:

private void ExecuteHideColumns()
{
    IsOpenForHideColumns = true;
    //  which will open the show/hide all columns popup.
}

private void ExecuteHideAllColumns()
{
    foreach (var item in columns)
    {
        item.IsHidden = true;                
    }
}

private void ExecuteShowAllColumns()
{
    foreach (var item in columns)
    {
        item.IsHidden = false;                
    }
}
Show/Hide column in WPF DataGrid
Show/Hide column in WPF DataGrid

Sorting

The SortColumns command manages the DataGrid’s sorting behavior through its associated ExecuteAddSorting method, which performs two key operations.

  • Clears all existing sort descriptions
  • Applies new sorting based on user input.

The SortColumnDescriptions collection property will be used from the ViewModel to handle this operation, as shown in the code example below:

private void ExecuteAddSorting()
{
    if (SelectedSortColumn != null)
    {
        SortColumnDescriptions.Clear();
        var sortColumnDescription = new SortColumnDescription()
        {
            ColumnName = this.SelectedSortColumn.Name,
            SortDirection = IsOnState
                ? ListSortDirection.Ascending
                : ListSortDirection.Descending
        };

        var tempCollection = new SortColumnDescriptions();
        tempCollection.Add(sortColumnDescription);
        SortColumnDescriptions = tempCollection;
    }
}
Sorting in WPF DataGrid
Sorting in WPF DataGrid

Grouping

The GroupColumns command organizes data into logical categories. The ExecuteAddGrouping method clears previous grouping criteria and applies new ones, enhancing data analysis.

The GroupColumnDescriptions property is used here to handle this grouping operation, as shown in the code example below:

private void ExecuteAddGrouping()
{
    if (SelectedGroupColumn != null)
    {
        GroupColumnDescriptions.Clear();
        var groupColumnDescription = new GroupColumnDescription()
        {
            ColumnName = this.SelectedGroupColumn.Name
        };
        GroupColumnDescriptions.Add(groupColumnDescription);
    }
}
Grouping is enabled in the WPF DataGrid
Grouping is enabled in the WPF DataGrid

Filtering

The FilterColumns command allows users to quickly narrow down results based on conditions.

The filtering logic uses helper methods such as MakeStringFilter() and MakeNumericFilter() to evaluate user-defined filter conditions.

public bool FilterRecords(object o)
{
    double res;
    bool checkNumeric = double.TryParse(this.FilterText, out res);
    var item = o as Grade;
    if (item != null && SelectedColumn != null)
    {
        if (checkNumeric
            && !this.SelectedColumn.Name!.Equals("All Columns")
            && !this.SelectedCondition!.Equals("Contains"))
        {
            bool result = this.MakeNumericFilter(
                item, this.SelectedColumn.Name,
                this.SelectedCondition
            );
            return result;
        }
        else if (this.SelectedColumn.Name!.Equals("All Columns"))
        {
            if (item.ID!.ToString().ToLower().Contains(this.FilterText!.ToLower())
                || item.StudentName!.ToString().ToLower().Contains(this.FilterText.ToLower())
                || item.SubjectName!.ToString().ToLower().Contains(this.FilterText.ToLower())
                || item.AssignmentScore!.ToString().ToLower().Contains(this.FilterText.ToLower())
                || item.QuizScore!.ToString().ToLower().Contains(this.FilterText.ToLower())
                || item.ExamScore!.ToString().ToLower().Contains(this.FilterText.ToLower())
                || item.ProjectScore!.ToString().ToLower().Contains(this.FilterText.ToLower())
                || item.Comments!.ToString().ToLower().Contains(this.FilterText.ToLower()))
            {
                return true;
            }
            return false;
        }
        else
        {
            bool result = this.MakeStringFilter(
                item,
                this.SelectedColumn.Name,
                this.SelectedCondition!
            );
            return result;
        }
    }
    return false;
}
Filtering in WPF DataGrid
Filtering in WPF DataGrid

GitHub reference

You can explore the full implementation of the School Gradesheet application on GitHub.

Frequently Asked Questions

How can I export the gradesheet to Excel or PDF?

SfDataGrid provides built-in export support for Excel and PDF. You can use APIs like ExportToExcel() and ExportToPdf() with export options to customize headers, formatting, and layout. Trigger these actions from UI buttons so users can download reports in their preferred format.

How do I validate grade scores (e.g., between 0–100)?

You can enforce data validation in multiple ways: Implement IDataErrorInfo or INotifyDataErrorInfo in your model, use ValidationRule in XAML bindings, and add a custom Validate() method before saving.

Display validation errors in the UI to prevent invalid data entry and improve user experience.

Can I add calculated columns like GPA or letter grades?

Yes, calculated values like GPA or letter grades can be added easily. Extend your model logic (e.g., CalculateFinalGrade()) and bind results to the grid.

For dynamic columns, use: GridUnBoundColumn with QueryUnBoundColumnValue, and value converters in the ViewModel.

This approach keeps calculations clean and separate from UI logic.

How can I persist grade data to a database or file?

While the example uses in-memory collections, you can extend it to persist data using Entity Framework Core (database storage), and JSON or CSV (file-based storage).

Add save/load methods in the ViewModel and ensure proper error handling and UI refresh after loading data.

How do I allow users to add or edit student records in the DataGrid?

Enable editing directly in the DataGrid by setting AllowEditing="True", and AddNewRowPosition for inserting new rows.

Bind save actions to commands in the ViewModel and update the ObservableCollection. Add validation to ensure required fields are completed before saving.

Wrap Up

By combining a structured ViewModel with a flexible WPF DataGrid, you’ve built a complete grade management system that supports real-world needs like sorting, grouping, and filtering.

This approach keeps your code clean, scalable, and easy to extend, whether you’re adding analytics, exporting data, or integrating with other systems.

If you’re a Syncfusion user, you can download the setup from the license and downloads page. Otherwise, you can download a free 30-day trial.

You can also contact us via our support forumssupport portal, or feedback portal for queries. We are always happy to assist you!

Be the first to get updates

Piruthiviraj MalaimelrajPiruthiviraj Malaimelraj profile icon

Meet the Author

Piruthiviraj Malaimelraj

Product manager for MAUI and Xamarin products in Syncfusion. I have been working as a .NET developer since 2015, and have experience in the developing of custom controls in .NET Frameworks.

Leave a comment