C# DataGridView Export to Excel: Complete Guide with Formatting | IronXL

Exporting DataGridView data to an Excel file is one of the most common tasks in Windows Forms development. When building business applications that display tabular data -- whether sales reports, inventory records, or customer lists -- users expect to click a button and receive a properly formatted Excel file they can share or analyze further. The challenge is doing this cleanly, without depending on a Microsoft Excel installation on every end-user machine, and without wrestling with COM interop cleanup code that leaks memory or breaks silently. This guide walks you through the complete process of exporting DataGridView to Excel in C# using IronXL, covering everything from project setup through advanced cell formatting, so you end up with production-ready code.
How Do You Set Up a Windows Forms Project for DataGridView Export?
Traditional approaches to exporting DataGridView data relied on Microsoft Interop -- you would open Add Reference, navigate to the COM tab, select the Microsoft Excel Object Library, and write fragile code that called Marshal.ReleaseComObject to avoid memory leaks. That pattern requires Microsoft Excel installed on every machine where the application runs, performs slowly with large datasets, and often produces COMException errors in deployment environments that lack Office licenses. Microsoft's own guidance on Office Automation explicitly recommends third-party libraries for server-side and automated scenarios.
IronXL removes all of those dependencies. It is a pure .NET library that reads and writes .xlsx, .xls, .csv, and .ods files without Microsoft Office or any COM registration. You install it through NuGet and start writing code immediately.
Installing IronXL via NuGet
Start by creating a new Windows Forms App project in Visual Studio targeting .NET 10. Add a DataGridView control and a Button to the form surface. Name the button btnExport and give it the label "Export to Excel". Then open the NuGet Package Manager Console and run:
Install-Package IronXL.Excel
Install-Package IronXL.Excel
Add the required namespaces to the top of your form file:
using IronXL;
using System.Data;
using IronXL;
using System.Data;
Imports IronXL
Imports System.Data
These two namespaces cover all the IronXL types you need for reading and writing Excel workbooks, and the standard System.Data types for working with DataTable objects that bridge your DataGridView to the export pipeline.
How Do You Load Sample Data into a DataGridView Control?
Before building the export logic, populate your DataGridView with representative data. The Form1_Load event is the right place to bind a DataTable as the data source. In a real application you would query a database or call a service; here a hard-coded DataTable illustrates the structure clearly. The DataGridView control overview on Microsoft Docs provides additional context on how the control manages data sources.
Binding a DataTable to DataGridView
void Form1_Load(object sender, EventArgs e)
{
DataTable dt = new DataTable();
dt.Columns.Add("ProductID", typeof(int));
dt.Columns.Add("ProductName", typeof(string));
dt.Columns.Add("Price", typeof(decimal));
dt.Columns.Add("Stock", typeof(int));
dt.Rows.Add(1, "Laptop", 999.99m, 50);
dt.Rows.Add(2, "Mouse", 29.99m, 200);
dt.Rows.Add(3, "Keyboard", 79.99m, 150);
dt.Rows.Add(4, "Monitor", 349.99m, 75);
dt.Rows.Add(5, "Webcam", 89.99m, 120);
dataGridView1.DataSource = dt;
}
void Form1_Load(object sender, EventArgs e)
{
DataTable dt = new DataTable();
dt.Columns.Add("ProductID", typeof(int));
dt.Columns.Add("ProductName", typeof(string));
dt.Columns.Add("Price", typeof(decimal));
dt.Columns.Add("Stock", typeof(int));
dt.Rows.Add(1, "Laptop", 999.99m, 50);
dt.Rows.Add(2, "Mouse", 29.99m, 200);
dt.Rows.Add(3, "Keyboard", 79.99m, 150);
dt.Rows.Add(4, "Monitor", 349.99m, 75);
dt.Rows.Add(5, "Webcam", 89.99m, 120);
dataGridView1.DataSource = dt;
}
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim dt As New DataTable()
dt.Columns.Add("ProductID", GetType(Integer))
dt.Columns.Add("ProductName", GetType(String))
dt.Columns.Add("Price", GetType(Decimal))
dt.Columns.Add("Stock", GetType(Integer))
dt.Rows.Add(1, "Laptop", 999.99D, 50)
dt.Rows.Add(2, "Mouse", 29.99D, 200)
dt.Rows.Add(3, "Keyboard", 79.99D, 150)
dt.Rows.Add(4, "Monitor", 349.99D, 75)
dt.Rows.Add(5, "Webcam", 89.99D, 120)
dataGridView1.DataSource = dt
End Sub
This code uses top-level statement style for the event handler signature. The DataTable has four typed columns -- integer, string, decimal, and integer -- which IronXL will preserve when writing to the Excel workbook. Typed columns matter because IronXL writes numeric columns as numeric cells rather than text, which lets users sort and sum values in Excel without reformatting.

The DataGridView renders a column header row automatically from the DataTable column names. When you export, you want that header row preserved in the Excel file, which means your export code must handle headers separately from data rows -- the next section covers exactly that.
For production use, the same pattern applies whether the DataTable comes from Entity Framework, Dapper, ADO.NET, or any other data access layer. The DataGridView binding is decoupled from the export code, so you can swap the data source without touching the export logic.
How Do You Export DataGridView Data to an Excel File?
The core export logic runs inside the button click handler. IronXL provides a LoadFromDataTable method on WorkSheet that handles the column-to-cell mapping automatically. The cleanest approach extracts a DataTable from the DataGridView and passes it directly. The Open XML SDK, which underlies the .xlsx format, is documented by Microsoft and confirms why pure .NET solutions like IronXL outperform Interop for programmatic generation.
Button Click Export Handler
void btnExport_Click(object sender, EventArgs e)
{
try
{
DataTable dt = new DataTable();
foreach (DataGridViewColumn column in dataGridView1.Columns)
dt.Columns.Add(column.HeaderText);
foreach (DataGridViewRow row in dataGridView1.Rows)
{
if (row.IsNewRow) continue;
DataRow dataRow = dt.NewRow();
for (int i = 0; i < dataGridView1.Columns.Count; i++)
dataRow[i] = row.Cells[i].Value ?? DBNull.Value;
dt.Rows.Add(dataRow);
}
WorkBook workbook = WorkBook.Create(ExcelFileFormat.XLSX);
WorkSheet worksheet = workbook.DefaultWorkSheet;
worksheet.Name = "Product Data";
worksheet.LoadFromDataTable(dt, true);
string outputPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
"DataGridViewExport.xlsx"
);
workbook.SaveAs(outputPath);
MessageBox.Show($"Exported successfully to:\n{outputPath}", "Export Complete",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show($"Export failed: {ex.Message}", "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
void btnExport_Click(object sender, EventArgs e)
{
try
{
DataTable dt = new DataTable();
foreach (DataGridViewColumn column in dataGridView1.Columns)
dt.Columns.Add(column.HeaderText);
foreach (DataGridViewRow row in dataGridView1.Rows)
{
if (row.IsNewRow) continue;
DataRow dataRow = dt.NewRow();
for (int i = 0; i < dataGridView1.Columns.Count; i++)
dataRow[i] = row.Cells[i].Value ?? DBNull.Value;
dt.Rows.Add(dataRow);
}
WorkBook workbook = WorkBook.Create(ExcelFileFormat.XLSX);
WorkSheet worksheet = workbook.DefaultWorkSheet;
worksheet.Name = "Product Data";
worksheet.LoadFromDataTable(dt, true);
string outputPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
"DataGridViewExport.xlsx"
);
workbook.SaveAs(outputPath);
MessageBox.Show($"Exported successfully to:\n{outputPath}", "Export Complete",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show($"Export failed: {ex.Message}", "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
Imports System
Imports System.Data
Imports System.IO
Imports System.Windows.Forms
Public Sub btnExport_Click(sender As Object, e As EventArgs)
Try
Dim dt As New DataTable()
For Each column As DataGridViewColumn In dataGridView1.Columns
dt.Columns.Add(column.HeaderText)
Next
For Each row As DataGridViewRow In dataGridView1.Rows
If row.IsNewRow Then Continue For
Dim dataRow As DataRow = dt.NewRow()
For i As Integer = 0 To dataGridView1.Columns.Count - 1
dataRow(i) = If(row.Cells(i).Value, DBNull.Value)
Next
dt.Rows.Add(dataRow)
Next
Dim workbook As WorkBook = WorkBook.Create(ExcelFileFormat.XLSX)
Dim worksheet As WorkSheet = workbook.DefaultWorkSheet
worksheet.Name = "Product Data"
worksheet.LoadFromDataTable(dt, True)
Dim outputPath As String = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
"DataGridViewExport.xlsx"
)
workbook.SaveAs(outputPath)
MessageBox.Show($"Exported successfully to:{Environment.NewLine}{outputPath}", "Export Complete",
MessageBoxButtons.OK, MessageBoxIcon.Information)
Catch ex As Exception
MessageBox.Show($"Export failed: {ex.Message}", "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
End Sub

The LoadFromDataTable(dt, true) call takes the DataTable and a boolean flag that tells IronXL to write the column names as the first Excel row -- those become your header cells. The workbook is saved to the user's Desktop using Environment.SpecialFolder.Desktop rather than a hard-coded path, which makes the code portable across user accounts.
The null check (?? DBNull.Value) prevents a NullReferenceException when a cell contains no value. This is important for real-world data where optional fields may be empty. IronXL writes DBNull as an empty cell rather than the string "DBNull", so the output stays clean.
For more details on reading data back from Excel files into a DataGridView, see the IronXL DataTable documentation, which covers the reverse operation and how to convert Excel to DataSet for multi-sheet workbooks.
How Do You Apply Professional Formatting to the Exported Excel File?
Plain data in an Excel file is functional, but professionally formatted output -- bold headers, column widths fitted to content, alternating row background colors -- makes the difference between a tool users trust and one they export and immediately reformat by hand. IronXL exposes a rich cell styling API that covers fonts, colors, borders, number formats, and alignment. The OOXML specification for spreadsheet styles defines the underlying format that IronXL writes, giving you confidence the output opens correctly in any compliant application.
Applying Header Styles and Alternating Row Colors
void ExportWithFormatting(object sender, EventArgs e)
{
WorkBook workbook = WorkBook.Create(ExcelFileFormat.XLSX);
WorkSheet worksheet = workbook.DefaultWorkSheet;
worksheet.Name = "Formatted Export";
string[] headers = { "ID", "Product Name", "Price", "Stock" };
// Write and style header row
for (int col = 0; col < headers.Length; col++)
{
char colLetter = (char)('A' + col);
string cellAddress = $"{colLetter}1";
worksheet.SetCellValue(0, col, headers[col]);
worksheet[cellAddress].Style.Font.Bold = true;
worksheet[cellAddress].Style.Font.Height = 12;
worksheet[cellAddress].Style.SetBackgroundColor("#4472C4");
worksheet[cellAddress].Style.Font.Color = "#FFFFFF";
worksheet[cellAddress].Style.HorizontalAlignment =
IronXL.Styles.HorizontalAlignment.Center;
}
// Write data rows with alternating background colors
int rowIndex = 1;
foreach (DataGridViewRow row in dataGridView1.Rows)
{
if (row.IsNewRow) continue;
for (int col = 0; col < dataGridView1.Columns.Count; col++)
{
worksheet.SetCellValue(rowIndex, col,
row.Cells[col].Value?.ToString() ?? string.Empty);
}
if (rowIndex % 2 == 0)
{
string rangeAddress = $"A{rowIndex + 1}:D{rowIndex + 1}";
worksheet[rangeAddress].Style.SetBackgroundColor("#D6DCE5");
}
rowIndex++;
}
// Format the Price column as currency
worksheet["C2:C100"].Style.Format = "$#,##0.00";
// Auto-fit column widths
worksheet.AutoSizeColumn(0);
worksheet.AutoSizeColumn(1);
worksheet.AutoSizeColumn(2);
worksheet.AutoSizeColumn(3);
string outputPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
"FormattedExport.xlsx"
);
workbook.SaveAs(outputPath);
MessageBox.Show("Formatted export complete.", "Done",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
void ExportWithFormatting(object sender, EventArgs e)
{
WorkBook workbook = WorkBook.Create(ExcelFileFormat.XLSX);
WorkSheet worksheet = workbook.DefaultWorkSheet;
worksheet.Name = "Formatted Export";
string[] headers = { "ID", "Product Name", "Price", "Stock" };
// Write and style header row
for (int col = 0; col < headers.Length; col++)
{
char colLetter = (char)('A' + col);
string cellAddress = $"{colLetter}1";
worksheet.SetCellValue(0, col, headers[col]);
worksheet[cellAddress].Style.Font.Bold = true;
worksheet[cellAddress].Style.Font.Height = 12;
worksheet[cellAddress].Style.SetBackgroundColor("#4472C4");
worksheet[cellAddress].Style.Font.Color = "#FFFFFF";
worksheet[cellAddress].Style.HorizontalAlignment =
IronXL.Styles.HorizontalAlignment.Center;
}
// Write data rows with alternating background colors
int rowIndex = 1;
foreach (DataGridViewRow row in dataGridView1.Rows)
{
if (row.IsNewRow) continue;
for (int col = 0; col < dataGridView1.Columns.Count; col++)
{
worksheet.SetCellValue(rowIndex, col,
row.Cells[col].Value?.ToString() ?? string.Empty);
}
if (rowIndex % 2 == 0)
{
string rangeAddress = $"A{rowIndex + 1}:D{rowIndex + 1}";
worksheet[rangeAddress].Style.SetBackgroundColor("#D6DCE5");
}
rowIndex++;
}
// Format the Price column as currency
worksheet["C2:C100"].Style.Format = "$#,##0.00";
// Auto-fit column widths
worksheet.AutoSizeColumn(0);
worksheet.AutoSizeColumn(1);
worksheet.AutoSizeColumn(2);
worksheet.AutoSizeColumn(3);
string outputPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
"FormattedExport.xlsx"
);
workbook.SaveAs(outputPath);
MessageBox.Show("Formatted export complete.", "Done",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
Option Strict On
Sub ExportWithFormatting(sender As Object, e As EventArgs)
Dim workbook As WorkBook = WorkBook.Create(ExcelFileFormat.XLSX)
Dim worksheet As WorkSheet = workbook.DefaultWorkSheet
worksheet.Name = "Formatted Export"
Dim headers As String() = {"ID", "Product Name", "Price", "Stock"}
' Write and style header row
For col As Integer = 0 To headers.Length - 1
Dim colLetter As Char = ChrW(AscW("A"c) + col)
Dim cellAddress As String = $"{colLetter}1"
worksheet.SetCellValue(0, col, headers(col))
worksheet(cellAddress).Style.Font.Bold = True
worksheet(cellAddress).Style.Font.Height = 12
worksheet(cellAddress).Style.SetBackgroundColor("#4472C4")
worksheet(cellAddress).Style.Font.Color = "#FFFFFF"
worksheet(cellAddress).Style.HorizontalAlignment = IronXL.Styles.HorizontalAlignment.Center
Next
' Write data rows with alternating background colors
Dim rowIndex As Integer = 1
For Each row As DataGridViewRow In dataGridView1.Rows
If row.IsNewRow Then Continue For
For col As Integer = 0 To dataGridView1.Columns.Count - 1
worksheet.SetCellValue(rowIndex, col, If(row.Cells(col).Value?.ToString(), String.Empty))
Next
If rowIndex Mod 2 = 0 Then
Dim rangeAddress As String = $"A{rowIndex + 1}:D{rowIndex + 1}"
worksheet(rangeAddress).Style.SetBackgroundColor("#D6DCE5")
End If
rowIndex += 1
Next
' Format the Price column as currency
worksheet("C2:C100").Style.Format = "$#,##0.00"
' Auto-fit column widths
worksheet.AutoSizeColumn(0)
worksheet.AutoSizeColumn(1)
worksheet.AutoSizeColumn(2)
worksheet.AutoSizeColumn(3)
Dim outputPath As String = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "FormattedExport.xlsx")
workbook.SaveAs(outputPath)
MessageBox.Show("Formatted export complete.", "Done", MessageBoxButtons.OK, MessageBoxIcon.Information)
End Sub

The formatting code applies several techniques. The header row gets a blue background (#4472C4) with white text, 12-point bold font, and centered alignment -- a standard business spreadsheet style. Data rows alternate between white and light gray (#D6DCE5) every even row, which makes it easier for users to read across wide tables without losing their place. The Price column uses Excel's built-in currency format ($#,##0.00), so values display with dollar signs and two decimal places in the spreadsheet without changing the underlying numeric data. AutoSizeColumn fits each column to its longest value so no content is truncated.
You can extend this pattern further with cell border styling, conditional formatting, and data validation rules. For reports that must match a corporate template, you can also set the page layout and print area so the exported file is ready to print without adjustment.
How Do You Handle Large Datasets and Performance Tuning?
When a DataGridView is bound to thousands of rows, iterating cell by cell becomes noticeably slow. Two optimizations improve performance significantly. First, use LoadFromDataTable instead of per-cell SetCellValue calls. Second, if your data source is a DataTable, pass it directly to IronXL rather than extracting values through the DataGridView rows:
void ExportLargeDataset(DataTable sourceTable)
{
WorkBook workbook = WorkBook.Create(ExcelFileFormat.XLSX);
WorkSheet worksheet = workbook.DefaultWorkSheet;
// Direct DataTable load -- fastest path for large data
worksheet.LoadFromDataTable(sourceTable, true);
// Apply header styling after load
int colCount = sourceTable.Columns.Count;
for (int col = 0; col < colCount; col++)
{
char colLetter = (char)('A' + col);
worksheet[$"{colLetter}1"].Style.Font.Bold = true;
worksheet[$"{colLetter}1"].Style.SetBackgroundColor("#4472C4");
worksheet[$"{colLetter}1"].Style.Font.Color = "#FFFFFF";
}
workbook.SaveAs(Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
"LargeExport.xlsx"
));
}
void ExportLargeDataset(DataTable sourceTable)
{
WorkBook workbook = WorkBook.Create(ExcelFileFormat.XLSX);
WorkSheet worksheet = workbook.DefaultWorkSheet;
// Direct DataTable load -- fastest path for large data
worksheet.LoadFromDataTable(sourceTable, true);
// Apply header styling after load
int colCount = sourceTable.Columns.Count;
for (int col = 0; col < colCount; col++)
{
char colLetter = (char)('A' + col);
worksheet[$"{colLetter}1"].Style.Font.Bold = true;
worksheet[$"{colLetter}1"].Style.SetBackgroundColor("#4472C4");
worksheet[$"{colLetter}1"].Style.Font.Color = "#FFFFFF";
}
workbook.SaveAs(Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
"LargeExport.xlsx"
));
}
Option Strict On
Sub ExportLargeDataset(sourceTable As DataTable)
Dim workbook As WorkBook = WorkBook.Create(ExcelFileFormat.XLSX)
Dim worksheet As WorkSheet = workbook.DefaultWorkSheet
' Direct DataTable load -- fastest path for large data
worksheet.LoadFromDataTable(sourceTable, True)
' Apply header styling after load
Dim colCount As Integer = sourceTable.Columns.Count
For col As Integer = 0 To colCount - 1
Dim colLetter As Char = ChrW(AscW("A"c) + col)
worksheet($"{colLetter}1").Style.Font.Bold = True
worksheet($"{colLetter}1").Style.SetBackgroundColor("#4472C4")
worksheet($"{colLetter}1").Style.Font.Color = "#FFFFFF"
Next
workbook.SaveAs(Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
"LargeExport.xlsx"
))
End Sub
For datasets with more than 10,000 rows, running the export on a background thread keeps the UI responsive. Wrap the export logic in Task.Run and marshal the MessageBox.Show call back to the UI thread using Invoke. IronXL is thread-safe for write operations on separate WorkBook instances, so you can run multiple exports concurrently if needed.
Additional performance resources:
How Do You Compare IronXL to Microsoft Interop for DataGridView Export?
Many developers start with Microsoft Excel Interop because it ships with Office and requires no additional packages. However, Interop has real-world costs that show up quickly in production environments. The following table summarizes the key differences:
| Capability | IronXL | Microsoft Interop |
|---|---|---|
| Requires Microsoft Excel installed | No | Yes |
| Works in server / cloud environments | Yes | No (unsupported by Microsoft) |
| COM object cleanup required | No | Yes (Marshal.ReleaseComObject) |
| Performance with large datasets | Fast (pure .NET) | Slow (COM marshalling overhead) |
| Install method | NuGet | COM reference / Office installation |
| Supported .NET versions | .NET 4.6.2 -- .NET 10 | .NET Framework only (limited) |
| XLSX, CSV, ODS support | Yes | XLSX/XLS only via Excel |
Microsoft's own documentation warns against using Office Interop on servers or in service accounts, citing stability and licensing concerns. IronXL works correctly in Azure App Service, Windows Service hosts, Docker containers, and any other headless environment where running a desktop application like Excel is not feasible.
For teams already using Interop who want to migrate, IronXL's API maps closely enough that most WorkBook and WorkSheet operations translate directly. The IronXL migration guide covers common Interop patterns and their IronXL equivalents.
What Are Your Next Steps?
Exporting DataGridView data to Excel using IronXL requires only a NuGet package install and a few lines of code, replacing the fragile COM interop approach with a clean, maintainable solution that works in any deployment environment. The techniques covered here -- basic export, formatted output, large-dataset optimization, and a comparison table -- give you everything needed to ship this feature in a production Windows Forms application.
From here, explore these related capabilities:
- Create Excel files from scratch in C# -- build workbooks programmatically without a data source
- Read Excel files into a DataTable -- the reverse operation for importing Excel data back into your application
- Apply conditional formatting -- highlight cells automatically based on their values
- Set chart data in Excel -- embed charts in exported workbooks
- Password-protect Excel files -- secure exported reports before distributing them
- Export to CSV format -- when recipients need flat-file format instead of
.xlsx
Start a free IronXL trial to test the full feature set in your project, or review the IronXL licensing options when you are ready for production deployment.
Frequently Asked Questions
How can I export DataGridView data to Excel in C#?
Install IronXL via NuGet, extract a DataTable from your DataGridView, create a WorkBook and WorkSheet, call worksheet.LoadFromDataTable(dt, true), then save with workbook.SaveAs.
What formatting options are available when exporting DataGridView to Excel?
IronXL supports bold fonts, background colors, font colors, horizontal alignment, number formats (such as currency), auto-size columns, border styles, and conditional formatting.
Do I need Microsoft Excel installed to export DataGridView data?
No. IronXL is a pure .NET library that generates Excel files without requiring Microsoft Office or any COM registration on the machine.
Can I style headers when exporting DataGridView to Excel?
Yes. After writing the header row, access each header cell by address and set Style.Font.Bold, Style.SetBackgroundColor, and Style.Font.Color properties.
How do I apply alternating row colors in Excel when exporting from DataGridView?
Track the row index as you iterate DataGridView rows, and for even rows apply a range style using worksheet[rangeAddress].Style.SetBackgroundColor with your chosen hex color.
How do I handle large datasets when exporting DataGridView to Excel?
Pass the underlying DataTable directly to worksheet.LoadFromDataTable rather than iterating cells one by one. For very large sets, run the export on a background thread using Task.Run.
How does IronXL compare to Microsoft Excel Interop for DataGridView export?
IronXL does not require Microsoft Excel, works in server and cloud environments, needs no COM cleanup code, and performs significantly faster with large datasets.




