Introduction
There is not really much to say about this article except that it shows how to automate Microsoft Excel through C#. More precisley, what it shows is how to export a C# GridView
/DataView
to Excel, and how to format the resultant Excel spreadsheet cells. It also shows how to place WordArt and how to use a custom template, where a few values are filled in at predetermined cell positions.
The Associated GUI
Normally, I would say not to worry too much about the GUI, but in this one, there are some nice features like how do use the BackgroundWorker
object and how to use Invoke to marshal threads to allow different threads to change GUI component properties. It also shows the use of anonomous delegates which is something new to .NET (old news in Java).
This is all really covered in the following areas.
if (this.InvokeRequired)
{
this.Invoke(new EventHandler(delegate
{
progressBar1.Value = e.ProgressValue;
}));
}
else
{
progressBar1.Value = e.ProgressValue;
}
In this snippet, we can see two of the three points mentioned above (although this is nothing to do with Excel automation, it is code of interest I hope), but the export2Excel
object raises an event, which is meant to update the GUI. But the GUI component handles were created on a different thread, so we need to use Invoke to get onto the correct thread to change the GUI component properties or call their methods, so why not chuck in an anonomous delegate to demo this.
The code is probably the best place to look at this, for a complete understanding. This article is really about the excel automation so lets stick to that. I just thought I mention this stuff here.
Excel office automation
So what does this code do??
Well it does two things:
- It exports a
GridView
to Excel and colors cells, applies formatting etc., and also adds some WordArt. All the normal Excel stuff that one might want to do. It then saves this as a new Excel document, as specified by the user.
- It also uses a premade template and puts some values in the correct cell positions and saves that as a new Excel document, as specified by the user.
Using the code
The demo project attached actually contains a Visual Studio 2005 solution, with the following three classes:
Program class
This is the main entry point into the GridviewToExcel application. Essentially, all this class does is create a new Form1
object.
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace GridviewToExcel
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
Form1 class (Designer code not shown, see zip file)
Queries access database (part of the zip) to create a new GridView
, and exports the data to a new Excel document, or creates a new Excel document from an existing template, based on what button was clicked.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Data.OleDb;
using System.IO;
namespace GridviewToExcel
{
public partial class Form1 : Form
{
private export2Excel export2XLS;
private DataSet _dataSet;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Directory.SetCurrentDirectory(Application.StartupPath +
@"..\..\..\");
String accessPath = Directory.GetCurrentDirectory() +
@"\Northwind.mdb";
string connString =
@"Provider=Microsoft.JET.OLEDB.4.0;data source=" + accessPath;
string sqlString = "SELECT * FROM customers";
OleDbDataAdapter dataAdapter = null;
_dataSet = null;
try
{
OleDbConnection connection = new OleDbConnection(connString);
dataAdapter = new OleDbDataAdapter(sqlString, connection);
_dataSet = new DataSet();
dataAdapter.Fill(_dataSet, "customers");
connection.Close();
}
catch (Exception ex)
{
MessageBox.Show("Problem with DB access-\n\n connection: "
+ connString + "\r\n\r\n query: " + sqlString
+ "\r\n\r\n\r\n" + ex.ToString());
this.Close();
return;
}
DataView dvCust = _dataSet.Tables["customers"].DefaultView;
dg1.DataSource = dvCust;
}
private void btn2Excel_Click(object sender, EventArgs e)
{
saveFileDialog1.Filter = "Excel (*.xls)|*.xls";
if (saveFileDialog1.ShowDialog() == DialogResult.OK)
{
if (!saveFileDialog1.FileName.Equals(String.Empty))
{
FileInfo f = new FileInfo(saveFileDialog1.FileName);
if (f.Extension.Equals(".xls"))
{
StartExport(saveFileDialog1.FileName);
}
else
{
MessageBox.Show("Invalid file type");
}
}
else
{
MessageBox.Show("You did pick a location " +
"to save file to");
}
}
}
private void StartExport(String filepath)
{
btn2Excel.Enabled = false;
btnUseTemplate.Enabled = false;
BackgroundWorker bg = new BackgroundWorker();
bg.DoWork += new DoWorkEventHandler(bg_DoWork);
bg.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(bg_RunWorkerCompleted);
bg.RunWorkerAsync(filepath);
export2XLS = new export2Excel();
export2XLS.prg +=
new export2Excel.ProgressHandler(export2XLS_prg);
}
private void bg_DoWork(object sender, DoWorkEventArgs e)
{
DataView dv = _dataSet.Tables["customers"].DefaultView;
export2XLS.ExportToExcel(dv, (String)e.Argument, "newSheet1");
}
private void btnUseTemplate_Click(object sender, EventArgs e)
{
saveFileDialog1.Filter = "Excel (*.xls)|*.xls";
if (saveFileDialog1.ShowDialog() == DialogResult.OK)
{
if (!saveFileDialog1.FileName.Equals(String.Empty))
{
FileInfo f = new FileInfo(saveFileDialog1.FileName);
if (f.Extension.Equals(".xls"))
{
StartExportUseTemplate(saveFileDialog1.FileName);
}
else
{
MessageBox.Show("Invalid file type");
}
}
else
{
MessageBox.Show("You did pick a location" +
" to save file to");
}
}
}
private void StartExportUseTemplate(String filepath)
{
btn2Excel.Enabled = false;
btnUseTemplate.Enabled = false;
BackgroundWorker bg = new BackgroundWorker();
bg.DoWork += new DoWorkEventHandler(bg_DoWorkUseTemplate);
bg.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(bg_RunWorkerCompleted);
bg.RunWorkerAsync(filepath);
export2XLS = new export2Excel();
export2XLS.prg +=
new export2Excel.ProgressHandler(export2XLS_prg);
}
private void bg_DoWorkUseTemplate(object sender, DoWorkEventArgs e)
{
String[,] templateValues = { { "task1",
"CompletedBy1", "CompletedDate1" },
{ "task2", "CompletedBy2", "CompletedDate2" }
};
Directory.SetCurrentDirectory(Application.StartupPath +
@"..\..\..\");
String templatePath = Directory.GetCurrentDirectory() +
@"\TaskList.xlt";
export2XLS.UseTemplate((String)e.Argument, templatePath,
templateValues);
}
private void export2XLS_prg(object sender, ProgressEventArgs e)
{
if (this.InvokeRequired)
{
this.Invoke(new EventHandler(delegate
{
progressBar1.Value = e.ProgressValue;
}));
}
else
{
progressBar1.Value = e.ProgressValue;
}
}
private void bg_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
btn2Excel.Enabled = true;
btnUseTemplate.Enabled = true;
MessageBox.Show("Finished");
}
}
}
export2Excel class does the excel automation
This is where all the real office automation stuff occurs.
using System;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using Microsoft.Office.Interop.Excel;
using Microsoft.Office.Core;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Diagnostics;
namespace GridviewToExcel
{
#region export2Excel CLASS
public class export2Excel
{
#region InstanceFields
public delegate void ProgressHandler(object sender,
ProgressEventArgs e);
public event ProgressHandler prg;
private DataView dv;
private Style styleRows;
private Style styleColumnHeadings;
private Microsoft.Office.Interop.Excel.Application EXL;
private Workbook workbook;
private Sheets sheets;
private Worksheet worksheet;
private string[,] myTemplateValues;
private int position;
#endregion
#region Constructor
public export2Excel()
{
}
#endregion
#region EXCEL : ExportToExcel
public void ExportToExcel(DataView dv,string path, string sheetName)
{
try
{
this.dv = dv;
#region NEW EXCEL DOCUMENT : Create Excel Objects
EXL = new Microsoft.Office.Interop.Excel.ApplicationClass();
int indexOfsheetName;
#region FILE EXISTS
if (File.Exists(path))
{
workbook = EXL.Workbooks.Open(path,
0, false, 5, "", "", false,
Microsoft.Office.Interop.Excel.XlPlatform.xlWindows,
"", true, false, 0, true, false, false);
sheets = workbook.Sheets;
indexOfsheetName = -1;
for (int i = 1; i <= sheets.Count; i++)
{
worksheet = (Worksheet)sheets.get_Item(i);
if (worksheet.Name.ToString().Equals(sheetName))
{
indexOfsheetName = i;
Microsoft.Office.Interop.Excel.Range myAllRange =
worksheet.Cells;
myAllRange.Select();
myAllRange.CurrentRegion.Select();
myAllRange.ClearContents();
}
}
if (indexOfsheetName == -1)
{
Worksheet sh = (Worksheet)workbook.Sheets.Add(
Type.Missing,
(Worksheet)sheets.get_Item(sheets.Count),
Type.Missing, Type.Missing);
sh.Name = sheetName;
}
}
#endregion
#region FILE DOESNT EXIST
else
{
workbook =
EXL.Workbooks.Add(XlWBATemplate.xlWBATWorksheet);
sheets = workbook.Sheets;
worksheet = (Worksheet)sheets.get_Item(1);
worksheet.Name = sheetName;
}
#endregion
#region get correct worksheet index for requested sheetName
sheets = workbook.Sheets;
indexOfsheetName = -1;
for (int i = 1; i <= sheets.Count; i++)
{
worksheet = (Worksheet)sheets.get_Item(i);
if (worksheet.Name.ToString().Equals(sheetName))
{
indexOfsheetName = i;
}
}
worksheet = (Worksheet)sheets.get_Item(indexOfsheetName);
#endregion
#endregion
SetUpStyles();
fillWorksheet_WithDataView();
AddAutoShapesToExcel();
SelectAllUsedCells();
try
{
workbook.Close(true, path, Type.Missing);
EXL.UserControl = false;
EXL.Quit();
EXL = null;
killExcel();
ProgressEventArgs pe = new ProgressEventArgs(100);
OnProgressChange(pe);
MessageBox.Show("Finished adding " +
"dataview to Excel", "Info",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
catch (COMException cex)
{
MessageBox.Show("User closed Excel manually, " +
"so we don't have to do that");
}
catch (Exception ex)
{
MessageBox.Show("Error " + ex.Message);
}
}
catch (Exception ex)
{
MessageBox.Show("Error " + ex.Message);
}
}
#endregion
#region EXCEL : UseTemplate
public void UseTemplate(string path, string templatePath,
string[,] myTemplateValues)
{
try
{
this.myTemplateValues = myTemplateValues;
EXL = new Microsoft.Office.Interop.Excel.ApplicationClass();
workbook = EXL.Workbooks.Open(templatePath,
0, false, 5, "", "", false,
Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "",
true, false, 0, true, false, false);
sheets = workbook.Sheets;
worksheet = (Worksheet)sheets.get_Item(1);
worksheet.Name = "ATemplate";
fillTemplate_WithTestValues();
SelectAllUsedCells();
try
{
workbook.Close(true, path, Type.Missing);
EXL.UserControl = false;
EXL.Quit();
EXL = null;
killExcel();
ProgressEventArgs pe = new ProgressEventArgs(100);
OnProgressChange(pe);
MessageBox.Show("Finished adding test values to " +
"Template", "Info",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
catch (COMException)
{
Console.WriteLine("User closed Excel manually," +
" so we don't have to do that");
}
}
catch (Exception ex)
{
MessageBox.Show("Error " + ex.Message);
}
}
#endregion
#region STEP 1 : Create Column & Row Workbook Cell Rendering Styles
private void SetUpStyles()
{
try
{
styleColumnHeadings = workbook.Styles["styleColumnHeadings"];
}
catch
{
styleColumnHeadings =
workbook.Styles.Add("styleColumnHeadings", Type.Missing);
styleColumnHeadings.Font.Name = "Arial";
styleColumnHeadings.Font.Size = 14;
styleColumnHeadings.Font.Color =
(255 << 16) | (255 << 8) | 255;
styleColumnHeadings.Interior.Color =
(0 << 16) | (0 << 8) | 0;
styleColumnHeadings.Interior.Pattern =
Microsoft.Office.Interop.Excel.XlPattern.xlPatternSolid;
}
try
{
styleRows = workbook.Styles["styleRows"];
}
catch
{
styleRows = workbook.Styles.Add("styleRows", Type.Missing);
styleRows.Font.Name = "Arial";
styleRows.Font.Size = 10;
styleRows.Font.Color = (0 << 16) | (0 << 8) | 0;
styleRows.Interior.Color =
(192 << 16) | (192 << 8) | 192;
styleRows.Interior.Pattern =
Microsoft.Office.Interop.Excel.XlPattern.xlPatternSolid;
}
}
#endregion
#region STEP 2 : Fill Worksheet With DataView
private void fillWorksheet_WithDataView()
{
position = 0;
int row = 1;
int col = 1;
for (int i = 0; i < dv.Table.Columns.Count; i++)
{
fillExcelCell(worksheet, row, col++,
dv.Table.Columns[i].ToString(),
styleColumnHeadings.Name);
}
row = 2;
col = 1;
for (int i = 0; i < dv.Table.Rows.Count; i++)
{
for (int j = 0; j < dv.Table.Columns.Count; j++)
{
fillExcelCell(worksheet, row, col++, dv[i][j].ToString(),
styleRows.Name);
}
col = 1;
row++;
position = (100 / dv.Table.Rows.Count) * row + 2;
ProgressEventArgs pe = new ProgressEventArgs(position);
OnProgressChange(pe);
}
}
#endregion
#region STEP 3 : Fill Individual Cell and Render Using Predefined Style
private void fillExcelCell(Worksheet worksheet, int row, int col,
Object Value, string StyleName)
{
Range rng = (Range)worksheet.Cells[row, col];
rng.Select();
rng.Value2 = Value.ToString();
rng.Style = StyleName;
rng.Columns.EntireColumn.AutoFit();
rng.Borders.Weight = XlBorderWeight.xlThin;
rng.Borders.LineStyle = XlLineStyle.xlContinuous;
rng.Borders.ColorIndex = XlColorIndex.xlColorIndexAutomatic;
}
#endregion
#region STEP 4 : Add Auto Shapes To Excel Worksheet
private void AddAutoShapesToExcel()
{
float txtSize = 80;
float Left = 100.0F;
float Top = 100.0F;
int[] numShapes = new int[2];
Microsoft.Office.Interop.Excel.Shape[] myShapes =
new Microsoft.Office.Interop.Excel.Shape[numShapes.Length];
try
{
for (int i = 0; i < numShapes.Length; i++)
{
myShapes[i] =
worksheet.Shapes.AddTextEffect(
MsoPresetTextEffect.msoTextEffect1, "DRAFT",
"Arial Black", txtSize, MsoTriState.msoFalse,
MsoTriState.msoFalse, (Left * (i * 3)), Top);
myShapes[i].Rotation = 45F;
myShapes[i].Fill.Visible =
Microsoft.Office.Core.MsoTriState.msoFalse;
myShapes[i].Fill.Transparency = 0F;
myShapes[i].Line.Weight = 1.75F;
myShapes[i].Line.DashStyle =
MsoLineDashStyle.msoLineSolid;
myShapes[i].Line.Transparency = 0F;
myShapes[i].Line.Visible =
Microsoft.Office.Core.MsoTriState.msoTrue;
myShapes[i].Line.ForeColor.RGB =
(0 << 16) | (0 << 8) | 0;
myShapes[i].Line.BackColor.RGB =
(255 << 16) | (255 << 8) | 255;
}
}
catch (Exception ex)
{
}
}
#endregion
#region STEP 5 : Select All Used Cells
private void SelectAllUsedCells()
{
Microsoft.Office.Interop.Excel.Range myAllRange = worksheet.Cells;
myAllRange.Select();
myAllRange.CurrentRegion.Select();
}
#endregion
#region STEP 6 : Fill Template With Test Values
private void fillTemplate_WithTestValues()
{
int StartRow = 3;
int StartCol = 2;
position=0;
for (int i=0; i <= myTemplateValues.GetUpperBound(0); i++)
{
for (int j = 0 ;
j <= myTemplateValues.GetUpperBound(1) ;
j++)
{
position = (100 / myTemplateValues.Length) * i;
ProgressEventArgs pe = new ProgressEventArgs(position);
OnProgressChange(pe);
Range rng = (Range)worksheet.Cells[StartRow,StartCol++];
rng.Select();
rng.Value2 = myTemplateValues[i,j].ToString();
rng.Rows.EntireRow.AutoFit();
}
StartCol=2;
StartRow++;
}
}
#endregion
#region Kill EXCEL
private void killExcel()
{
try
{
Process[] ps = Process.GetProcesses();
foreach (Process p in ps)
{
if (p.ProcessName.ToLower().Equals("excel"))
{
p.Kill();
}
}
}
catch (Exception ex)
{
MessageBox.Show("ERROR " + ex.Message);
}
}
#endregion
#region Events
public virtual void OnProgressChange(ProgressEventArgs e)
{
if (prg != null)
{
prg(this, e);
}
}
#endregion
}
#endregion
#region ProgressEventArgs CLASS
public class ProgressEventArgs : EventArgs
{
#region Instance Fields
private int prgValue = 0;
#endregion
#region Public Constructor
public ProgressEventArgs(int prgValue)
{
this.prgValue = prgValue;
}
#endregion
#region Public Methods/Properties
public int ProgressValue
{
get { return prgValue; }
}
#endregion
}
#endregion
}
The result when creating a new Excel file:
The results when using an existing template:
Points of Interest
I hope this article has demonstrated how to write some Office automation using C#. I find the best way to deal with this sort of thing is to fire up Office, record a VBA macro, and convert it from there. This is what I did for this application.
History