Exportação de DataGridView para Excel em WinForms com NPOI

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();
        }
    }
}

Tags: NPOI WinForms DataGridView Excel CSharp

Publicado em 6-10 04:18 por Thomas