Ao desenvolver aplicações Windows Forms, é comum a necessidade de exportar dados de um controle DataGridView para uma planilha Excel. Existem diferentes abordagens para realizar essa tarefa, e a escolha da biblioteca impacta diretamente no desempenho.
Abordagem convencional com Interop do Excel
Uma técnica frequentemente encontrada utiliza a automação do Microsoft Excel por meio de Microsoft.Office.Interop.Excel. Apesar de funcional, esse método apresenta limitações significativas: exige que o Excel esteja instalado na máquina, consome muita memória e é extremamente lento ao processar grandes volumes de dados.
private void ExportarComInterop(string nomeArquivo, DataGridView grade)
{
var dialogoSalvar = new SaveFileDialog
{
DefaultExt = "xls",
Filter = "Arquivo Excel|*.xls",
FileName = nomeArquivo
};
if (dialogoSalvar.ShowDialog() != DialogResult.OK) return;
var aplicativo = new Microsoft.Office.Interop.Excel.Application();
if (aplicativo == null)
{
MessageBox.Show("Não foi possível criar o objeto Excel. Verifique se o programa está instalado.");
return;
}
var livro = aplicativo.Workbooks.Add(Microsoft.Office.Interop.Excel.XlWBATemplate.xlWBATWorksheet);
var planilha = (Microsoft.Office.Interop.Excel.Worksheet)livro.Worksheets[1];
for (int col = 0; col < grade.ColumnCount; col++)
{
planilha.Cells[1, col + 1] = grade.Columns[col].HeaderText;
}
for (int lin = 0; lin < grade.RowCount; lin++)
{
for (int col = 0; col < grade.ColumnCount; col++)
{
planilha.Cells[lin + 2, col + 1] = grade.Rows[lin].Cells[col].Value;
}
System.Windows.Forms.Application.DoEvents();
}
planilha.Columns.EntireColumn.AutoFit();
try
{
livro.Saved = true;
livro.SaveCopyAs(dialogoSalvar.FileName);
}
catch (Exception ex)
{
MessageBox.Show("Erro ao salvar o arquivo: " + ex.Message);
}
aplicativo.Quit();
GC.Collect();
}
Solução de alto desempenho com NPOI
A biblioteca NPOI é uma implementação open source da API Apache POI para .NET, permitindo a manipulação de arquivos Office sem dependência do Excel instalado. O tempo de execução é drasticamente inferior, proporcionando uma experiência de exportação quase instantânea.
Para utilizar, basta referenciar os pacotes NPOI no projeto. O trecho abaixo demonstra o ponto de chamada da exportação:
private void AoClicarExportar(object sender, EventArgs e)
{
try
{
PlanilhaUtil.ExportarGrade(gradeDados);
MessageBox.Show("Exportação concluída com sucesso!");
}
catch (Exception erro)
{
MessageBox.Show("Falha na exportação: " + erro.Message);
}
}
A lógica principle de exportação pode ser estruturada da seguinte forma:
public static string ExportarGrade(DataGridView grade, string aba = "Dados", string caminhoArquivo = null)
{
if (grade.Rows.Count == 0) return null;
if (string.IsNullOrEmpty(caminhoArquivo))
{
caminhoArquivo = ObterCaminhoSalvamento();
}
if (string.IsNullOrEmpty(caminhoArquivo)) return null;
bool formatoAntigo = caminhoArquivo.EndsWith(".xls", StringComparison.OrdinalIgnoreCase);
IWorkbook pastaTrabalho = formatoAntigo ? (IWorkbook)new HSSFWorkbook() : new XSSFWorkbook();
ICellStyle estiloCabecalho = CriarEstiloCabecalho(pastaTrabalho);
ISheet folha = pastaTrabalho.CreateSheet(aba);
IRow linhaCabecalho = folha.CreateRow(0);
for (int idx = 0; idx < grade.Columns.Count; idx++)
{
ICell celula = linhaCabecalho.CreateCell(idx);
celula.SetCellValue(grade.Columns[idx].Name);
celula.CellStyle = estiloCabecalho;
}
int numeroLinha = 1;
foreach (DataGridViewRow registro in grade.Rows)
{
IRow linhaDados = folha.CreateRow(numeroLinha);
for (int col = 0; col < grade.Columns.Count; col++)
{
var valor = registro.Cells[col].Value;
linhaDados.CreateCell(col).SetCellValue(valor?.ToString() ?? string.Empty);
}
numeroLinha++;
}
AjustarLarguraColunas(folha, linhaCabecalho.LastCellNum);
using (var fluxo = new FileStream(caminhoArquivo, FileMode.Create, FileAccess.Write))
{
pastaTrabalho.Write(fluxo);
}
return caminhoArquivo;
}
O método auxiliar para criar o estilo do cabeçalho aplica uma cor de fundo para diferenciar os títulos das colunas:
private static ICellStyle CriarEstiloCabecalho(IWorkbook pasta)
{
ICellStyle estilo = pasta.CreateCellStyle();
estilo.FillPattern = FillPattern.SolidForeground;
estilo.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.Grey25Percent.Index;
return estilo;
}
O ajuste automático da largura das colunas percorre cada coluna e compara o tamanho do conteúdo para definir uma largura adequada:
public static void AjustarLarguraColunas(ISheet folha, int totalColunas)
{
for (int col = 0; col < totalColunas; col++)
{
folha.AutoSizeColumn(col);
int larguraAtual = folha.GetColumnWidth(col) / 256;
for (int linha = 1; linha <= folha.LastRowNum; linha++)
{
IRow registro = folha.GetRow(linha);
if (registro == null) continue;
ICell celula = registro.GetCell(col);
if (celula != null)
{
int tamanhoConteudo = Encoding.UTF8.GetBytes(celula.ToString()).Length;
if (tamanhoConteudo > larguraAtual)
larguraAtual = tamanhoConteudo;
}
}
folha.SetColumnWidth(col, larguraAtual * 200);
}
}
Para obter o caminho de salvamento, utiliza-se um diálogo padrão do Windows:
public static string ObterCaminhoSalvamento()
{
var dialogo = new SaveFileDialog
{
Filter = "Excel 97-2003 (*.xls)|*.xls|Excel 2007+ (*.xlsx)|*.xlsx",
FileName = DateTime.Now.ToString("yyyyMMdd_HHmmss"),
FilterIndex = 0,
OverwritePrompt = true,
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)
};
return dialogo.ShowDialog() == DialogResult.OK ? dialogo.FileName : null;
}
Classe utilitária completa para manipulação de Excel
Para reutilização em múltiplos projetos, é recomendável encapsular toda a lógica em uma classe estática. A seguir, uma versão consoldiada com métodos para leitura, escrita e conversão de dados entre Excel e estruturas .NET:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Text;
using System.Windows.Forms;
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
namespace UtilidadesPlanilhas
{
public static class GerenciadorExcel
{
public static bool EhFormatoAntigo(string caminho)
{
return caminho.EndsWith(".xls", StringComparison.OrdinalIgnoreCase);
}
public static IWorkbook FabricarPastaTrabalho(bool formatoAntigo)
{
return formatoAntigo ? (IWorkbook)new HSSFWorkbook() : new XSSFWorkbook();
}
public static IWorkbook FabricarPastaTrabalho(bool formatoAntigo, Stream fluxo)
{
return formatoAntigo ? (IWorkbook)new HSSFWorkbook(fluxo) : new XSSFWorkbook(fluxo);
}
public static IWorkbook AbrirPastaTrabalho(string caminho)
{
bool compativel = EhFormatoAntigo(caminho);
using (var fs = File.Open(caminho, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
return FabricarPastaTrabalho(compativel, fs);
}
}
public static string ObterCaminhoSalvar()
{
var dlg = new SaveFileDialog
{
Filter = "Excel 97-2003(*.xls)|*.xls|Excel 2007+(*.xlsx)|*.xlsx",
FileName = DateTime.Now.ToString("yyyyMMddHHmmss"),
OverwritePrompt = true,
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)
};
return dlg.ShowDialog() == DialogResult.OK ? dlg.FileName : null;
}
public static string ObterCaminhoAbrir()
{
var dlg = new OpenFileDialog
{
Title = "Selecione o arquivo",
Multiselect = false,
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
Filter = "Arquivos Excel|*.xls;*.xlsx"
};
return dlg.ShowDialog() == DialogResult.OK ? dlg.FileName : null;
}
private static ICellStyle ConstruirEstiloCabecalho(IWorkbook pasta)
{
ICellStyle estilo = pasta.CreateCellStyle();
estilo.FillPattern = FillPattern.SolidForeground;
estilo.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.Grey25Percent.Index;
return estilo;
}
public static void AutoDimensionarColunas(ISheet folha, int qtdColunas)
{
for (int c = 0; c <= qtdColunas; c++)
{
folha.AutoSizeColumn(c);
int larguraBase = folha.GetColumnWidth(c) / 256;
for (int r = 1; r <= folha.LastRowNum; r++)
{
IRow linha = folha.GetRow(r);
if (linha == null) continue;
ICell cel = linha.GetCell(c);
if (cel != null)
{
int len = Encoding.UTF8.GetBytes(cel.ToString()).Length;
larguraBase = Math.Max(larguraBase, len);
}
}
folha.SetColumnWidth(c, larguraBase * 200);
}
}
public static string ExportarGrade(DataGridView grade, string nomeAba = "resultado", string destino = null)
{
if (grade.Rows.Count == 0) return null;
destino = destino ?? ObterCaminhoSalvar();
if (string.IsNullOrEmpty(destino)) return null;
bool formatoLegado = EhFormatoAntigo(destino);
IWorkbook wb = FabricarPastaTrabalho(formatoLegado);
ICellStyle estilo = ConstruirEstiloCabecalho(wb);
ISheet folha = wb.CreateSheet(nomeAba);
IRow cabecalho = folha.CreateRow(0);
for (int i = 0; i < grade.Columns.Count; i++)
{
ICell celula = cabecalho.CreateCell(i);
celula.SetCellValue(grade.Columns[i].Name);
celula.CellStyle = estilo;
}
int idxLinha = 1;
foreach (DataGridViewRow reg in grade.Rows)
{
IRow dados = folha.CreateRow(idxLinha);
for (int n = 0; n < grade.Columns.Count; n++)
{
dados.CreateCell(n).SetCellValue((reg.Cells[n].Value ?? "").ToString());
}
idxLinha++;
}
AutoDimensionarColunas(folha, cabecalho.LastCellNum - 1);
using (var fs = new FileStream(destino, FileMode.Create, FileAccess.Write))
{
wb.Write(fs);
}
return destino;
}
public static string ExportarTabela(DataTable tabela, string nomeAba = "resultado", string destino = null)
{
if (tabela.Rows.Count == 0) return null;
destino = destino ?? ObterCaminhoSalvar();
if (string.IsNullOrEmpty(destino)) return null;
bool formatoLegado = EhFormatoAntigo(destino);
IWorkbook wb = FabricarPastaTrabalho(formatoLegado);
ICellStyle estilo = ConstruirEstiloCabecalho(wb);
ISheet folha = wb.CreateSheet(nomeAba);
IRow cabecalho = folha.CreateRow(0);
foreach (DataColumn col in tabela.Columns)
{
ICell cel = cabecalho.CreateCell(col.Ordinal);
cel.SetCellValue(col.ColumnName);
cel.CellStyle = estilo;
}
int linhaAtual = 1;
foreach (DataRow reg in tabela.Rows)
{
IRow dados = folha.CreateRow(linhaAtual);
foreach (DataColumn col in tabela.Columns)
{
dados.CreateCell(col.Ordinal).SetCellValue((reg[col] ?? "").ToString());
}
linhaAtual++;
}
using (var fs = new FileStream(destino, FileMode.Create, FileAccess.Write))
{
wb.Write(fs);
}
return destino;
}
public static string ExportarConjunto(DataSet conjunto, string destino = null)
{
destino = destino ?? ObterCaminhoSalvar();
if (string.IsNullOrEmpty(destino)) return null;
bool formatoLegado = EhFormatoAntigo(destino);
IWorkbook wb = FabricarPastaTrabalho(formatoLegado);
ICellStyle estilo = ConstruirEstiloCabecalho(wb);
for (int t = 0; t < conjunto.Tables.Count; t++)
{
DataTable tabela = conjunto.Tables[t];
ISheet folha = wb.CreateSheet("folha" + t);
IRow cabecalho = folha.CreateRow(0);
foreach (DataColumn col in tabela.Columns)
{
ICell cel = cabecalho.CreateCell(col.Ordinal);
cel.SetCellValue(col.ColumnName);
cel.CellStyle = estilo;
}
int idx = 1;
foreach (DataRow reg in tabela.Rows)
{
IRow dados = folha.CreateRow(idx);
foreach (DataColumn col in tabela.Columns)
{
dados.CreateCell(col.Ordinal).SetCellValue((reg[col] ?? "").ToString());
}
idx++;
}
}
using (var fs = new FileStream(destino, FileMode.Create, FileAccess.Write))
{
wb.Write(fs);
}
return destino;
}
public static string ExportarLista<T>(List<T> itens, IList<KeyValuePair<string, string>> mapeamento,
string nomeAba = "resultado", string destino = null) where T : class
{
if (itens.Count == 0) return null;
destino = destino ?? ObterCaminhoSalvar();
if (string.IsNullOrEmpty(destino)) return null;
bool formatoLegado = EhFormatoAntigo(destino);
IWorkbook wb = FabricarPastaTrabalho(formatoLegado);
ICellStyle estilo = ConstruirEstiloCabecalho(wb);
ISheet folha = wb.CreateSheet(nomeAba);
IRow cabecalho = folha.CreateRow(0);
for (int i = 0; i < mapeamento.Count; i++)
{
ICell cel = cabecalho.CreateCell(i);
cel.SetCellValue(mapeamento[i].Value);
cel.CellStyle = estilo;
}
var tipo = typeof(T);
int numLinha = 1;
foreach (T item in itens)
{
IRow dados = folha.CreateRow(numLinha);
for (int n = 0; n < mapeamento.Count; n++)
{
object valor = tipo.GetProperty(mapeamento[n].Key)?.GetValue(item, null);
dados.CreateCell(n).SetCellValue((valor ?? "").ToString());
}
numLinha++;
}
using (var fs = new FileStream(destino, FileMode.Create, FileAccess.Write))
{
wb.Write(fs);
}
return destino;
}
public static bool AlterarCelula(string caminho, string nomeAba, int coluna, int linha, string novoValor)
{
try
{
IWorkbook wb = AbrirPastaTrabalho(caminho);
ISheet folha = wb.GetSheetAt(0);
ICell celula = folha.GetRow(linha).GetCell(coluna);
celula.SetCellValue(novoValor);
using (var fs = File.Open(caminho, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
wb.Write(fs);
}
return true;
}
catch
{
return false;
}
}
public static string LerCelula(string caminho, string nomeAba, int coluna, int linha)
{
IWorkbook wb = AbrirPastaTrabalho(caminho);
ISheet folha = wb.GetSheetAt(0);
ICell celula = folha.GetRow(linha).GetCell(coluna);
return celula?.ToString();
}
public static ArrayList ObterDadosLinha(string caminho, int indiceFolha, int numLinha)
{
var resultado = new ArrayList();
bool compativel = EhFormatoAntigo(caminho);
using (var fs = File.OpenRead(caminho))
{
IWorkbook wb = FabricarPastaTrabalho(compativel, fs);
ISheet folha = wb.GetSheetAt(indiceFolha - 1);
IRow linha = folha.GetRow(numLinha - 1);
for (int c = 0; c < linha.LastCellNum; c++)
{
ICell celula = linha.GetCell(c);
if (celula != null)
resultado.Add(celula.ToString());
}
}
return resultado;
}
public static int LocalizarIndiceColuna(string caminho, int indiceFolha, int numLinha, string valorProcurado)
{
bool compativel = EhFormatoAntigo(caminho);
using (var fs = File.OpenRead(caminho))
{
IWorkbook wb = FabricarPastaTrabalho(compativel, fs);
ISheet folha = wb.GetSheetAt(indiceFolha - 1);
IRow cabecalho = folha.GetRow(numLinha - 1);
for (int c = 0; c < cabecalho.LastCellNum; c++)
{
ICell celula = cabecalho.GetCell(c);
if (celula != null && celula.ToString() == valorProcurado)
return c;
}
}
return -1;
}
public static Dictionary<string, int> MapearCabecalho(string caminho, int indiceFolha, int numLinha)
{
var mapa = new Dictionary<string, int>();
bool compativel = EhFormatoAntigo(caminho);
using (var fs = File.OpenRead(caminho))
{
IWorkbook wb = FabricarPastaTrabalho(compativel, fs);
ISheet folha = wb.GetSheetAt(indiceFolha - 1);
IRow cabecalho = folha.GetRow(numLinha - 1);
for (int c = 0; c < cabecalho.LastCellNum; c++)
{
ICell celula = cabecalho.GetCell(c);
if (celula != null)
{
string chave = celula.ToString();
if (!mapa.ContainsKey(chave))
mapa[chave] = c;
}
}
}
return mapa;
}
public static DataTable ImportarParaTabela(string caminho, string nomeAba, int linhaCabecalho)
{
bool compativel = EhFormatoAntigo(caminho);
using (var fs = File.Open(caminho, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
IWorkbook wb = FabricarPastaTrabalho(compativel, fs);
ISheet folha = wb.GetSheet(nomeAba);
return ExtrairTabela(folha, linhaCabecalho);
}
}
private static DataTable ExtrairTabela(ISheet folha, int idxCabecalho)
{
var tabela = new DataTable();
IRow cabecalho = folha.GetRow(idxCabecalho);
int totalColunas = cabecalho.LastCellNum;
for (int i = cabecalho.FirstCellNum; i < totalColunas; i++)
{
ICell cel = cabecalho.GetCell(i);
if (cel == null || string.IsNullOrEmpty(cel.StringCellValue?.Trim()))
{
totalColunas = i + 1;
break;
}
tabela.Columns.Add(new DataColumn(cel.StringCellValue));
}
for (int r = idxCabecalho + 1; r <= folha.LastRowNum; r++)
{
IRow linha = folha.GetRow(r);
if (linha == null || string.IsNullOrEmpty(linha.Cells[0]?.StringCellValue)) continue;
DataRow registro = tabela.NewRow();
for (int c = linha.FirstCellNum; c < totalColunas; c++)
{
if (linha.GetCell(c) != null)
registro[c] = linha.GetCell(c).ToString();
}
tabela.Rows.Add(registro);
}
return tabela;
}
public static DataSet ImportarParaConjunto(string caminho, int linhaCabecalho)
{
var conjunto = new DataSet();
bool compativel = EhFormatoAntigo(caminho);
using (var fs = File.Open(caminho, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
IWorkbook wb = FabricarPastaTrabalho(compativel, fs);
for (int i = 0; i < wb.NumberOfSheets; i++)
{
ISheet folha = wb.GetSheetAt(i);
conjunto.Tables.Add(ExtrairTabela(folha, linhaCabecalho));
}
}
return conjunto;
}
public static string[] ObterNomesFolhas(string caminho)
{
IWorkbook wb = AbrirPastaTrabalho(caminho);
var nomes = new string[wb.NumberOfSheets];
for (int i = 0; i < wb.NumberOfSheets; i++)
nomes[i] = wb.GetSheetName(i);
return nomes;
}
public static void RemoverLinhas(string caminho, int apartirDe)
{
IWorkbook wb = AbrirPastaTrabalho(caminho);
ISheet folha = wb.GetSheetAt(0);
for (int i = apartirDe; i <= folha.LastRowNum; i++)
{
IRow linha = folha.GetRow(i);
if (linha != null)
folha.RemoveRow(linha);
}
using (var ms = new MemoryStream())
{
wb.Write(ms);
File.WriteAllBytes(caminho, ms.ToArray());
}
}
public static string[,] ParaArrayBidimensional(string caminho, int linhas, int colunas, int indiceFolha)
{
var matriz = new string[linhas, colunas];
bool compativel = EhFormatoAntigo(caminho);
using (var fs = File.OpenRead(caminho))
{
IWorkbook wb = FabricarPastaTrabalho(compativel, fs);
ISheet folha = wb.GetSheetAt(indiceFolha - 1);
int totalLinhas = compativel ? folha.LastRowNum : folha.LastRowNum - 2;
for (int r = 0; r <= totalLinhas; r++)
{
IRow linha = folha.GetRow(r);
if (linha == null) continue;
for (int c = 0; c < linha.LastCellNum; c++)
{
ICell celula = linha.GetCell(c);
matriz[r, c] = celula?.ToString() ?? string.Empty;
}
}
}
return matriz;
}
public static string ConverterIndiceColunaParaNome(int indice)
{
indice++;
const int baseSistema = 26;
var caracteres = new char[100];
int pos = 0;
while (indice > 0)
{
int resto = indice % baseSistema;
if (resto == 0) resto = baseSistema;
caracteres[pos++] = (char)(resto - 1 + 'A');
indice = (indice - 1) / 26;
}
var sb = new StringBuilder(pos);
for (int j = pos - 1; j >= 0; j--)
sb.Append(caracteres[j]);
return sb.ToString();
}
}
}