C# - Exporting DataTable to Excel as Left-Justified (Left Aligned) columns - c#

I am trying to export a datatable filled with numbers and letters with spacing before and after. I am using the below code to export - it works great. However, all columns are centered instead of being aligned to the left.
How do I adjust my below code to left-justify the excel sheet?
internal static void Export2Excel(DataTable dataTable, string RevisedFileName)
{
object misValue = System.Reflection.Missing.Value;
Microsoft.Office.Interop.Excel.Application _appExcel = null;
Microsoft.Office.Interop.Excel.Workbook _excelWorkbook = null;
Microsoft.Office.Interop.Excel.Worksheet _excelWorksheet = null;
try
{
// excel app object
_appExcel = new Microsoft.Office.Interop.Excel.Application();
// excel workbook object added to app
_excelWorkbook = _appExcel.Workbooks.Add(misValue);
_excelWorksheet = _appExcel.ActiveWorkbook.ActiveSheet as Microsoft.Office.Interop.Excel.Worksheet;
// Left align all cells - THIS DOES NOT WORK.
_excelWorksheet.get_Range("A1", "A1").Style.HorizontalAlignment = Microsoft.Office.Interop.Excel.XlHAlign.xlHAlignCenter;
// column names row (range obj)
Microsoft.Office.Interop.Excel.Range _columnsNameRange;
_columnsNameRange = _excelWorksheet.get_Range("A1", misValue).get_Resize(1, dataTable.Columns.Count);
// column names array to be assigned to _columnNameRange
string[] _arrColumnNames = new string[dataTable.Columns.Count];
for (int i = 0; i < dataTable.Columns.Count; i++)
{
// array of column names
_arrColumnNames[i] = dataTable.Columns[i].ColumnName;
}
// assign array to column headers range, make 'em bold
_columnsNameRange.set_Value(misValue, _arrColumnNames);
_columnsNameRange.Font.Bold = true;
// populate data content row by row
for (int Idx = 0; Idx < dataTable.Rows.Count; Idx++)
{
_excelWorksheet.Range["A2"].Offset[Idx].Resize[1, dataTable.Columns.Count].Value =
dataTable.Rows[Idx].ItemArray;
}
// Autofit all Columns in the range
_columnsNameRange.Columns.EntireColumn.AutoFit();
_excelWorkbook.SaveAs(#Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "/" + RevisedFileName + " Revised.xlsx");
_excelWorkbook.Close();
System.Runtime.InteropServices.Marshal.ReleaseComObject(_appExcel);
}
catch { throw; }
MessageBox.Show("Success! Revised file has been saved to Desktop!");
}

I would guess the line
_excelWorksheet.get_Range("A1", "A1").Style.HorizontalAlignment = Microsoft.Office.Interop.Excel.XlHAlign.xlHAlignCenter;
ends with
....xlHAlignCenter
but should end something like
....xlHAlignLeft;

Related

Exporting Excel worksheets to Word maintaining merged cells with Spire.Office in C#

As part of my documentation procedures automation project I use Spire.Office to convert .xlsx document containing multiple worksheets to .doc document.
Overall, auto copy paste works well, I've modified this code to jump over few worksheets:
https://www.e-iceblue.com/Tutorials/Spire.XLS/Spire.XLS-Program-Guide/Document-Operation/How-to-Export-Excel-Data-to-Word-Table-Maintaining-Formatting-in-C.html
The problem I'm trying to fix is that merged cell are ignored and copied as individual cells.
Here is the code:
class ToWord
{
public void CopyToWord(string fpath, int qnumber)
{
Workbook spirewb = new Workbook();
spirewb.LoadFromFile(fpath);
Document doc = new Document();
for (int i = 1; i <= qnumber; i++)
{
Spire.Xls.Worksheet sheet = spirewb.Worksheets[i];
Table table = doc.AddSection().AddTable(true);
table.ResetCells(sheet.LastRow, sheet.LastColumn);
for (int r = 1; r <= sheet.LastRow; r++)
{
for (int c = 1; c <= sheet.LastColumn; c++)
{
CellRange xCell = sheet.Range[r, c];
TableCell wCell = table.Rows[r - 1].Cells[c - 1];
//fill data to word table
TextRange textRange = wCell.AddParagraph().AppendText(xCell.NumberText);
//copy font and cell style from excel to word
CopyStyle(textRange, xCell, wCell);
}
}
}
doc.SaveToFile("result.doc", Spire.Doc.FileFormat.Doc);
System.Diagnostics.Process.Start("result.doc");
}
private static void CopyStyle(TextRange wTextRange, CellRange xCell, TableCell wCell)
{
//copy font stlye
wTextRange.CharacterFormat.TextColor = xCell.Style.Font.Color;
wTextRange.CharacterFormat.FontSize = (float)xCell.Style.Font.Size;
wTextRange.CharacterFormat.FontName = xCell.Style.Font.FontName;
wTextRange.CharacterFormat.Bold = xCell.Style.Font.IsBold;
wTextRange.CharacterFormat.Italic = xCell.Style.Font.IsItalic;
//copy backcolor
wCell.CellFormat.BackColor = xCell.Style.Color;
//copy text alignment
switch (xCell.HorizontalAlignment)
{
case HorizontalAlignType.Left:
wTextRange.OwnerParagraph.Format.HorizontalAlignment = HorizontalAlignment.Left;
break;
case HorizontalAlignType.Center:
wTextRange.OwnerParagraph.Format.HorizontalAlignment = HorizontalAlignment.Center;
break;
case HorizontalAlignType.Right:
wTextRange.OwnerParagraph.Format.HorizontalAlignment = HorizontalAlignment.Right;
break;
}
}
}
Working solution has been posted here:
https://www.e-iceblue.com/forum/post37260.html

how to get cell background color of excel using C#?

I want to retrieve the background color of cell of excel using c# but I just can't find a way to do it
which library shall I use ? microsoft.office.tools.excel.workbook.aspx or microsoft.office.interop.excel ? what's the differents?
I have tried following code but get no luck:
using Excel = Microsoft.Office.Interop.Excel;
private void button1_Click(object sender, EventArgs e)
{
ofd.Filter = "xlsx|*.xlsx|xls|*.xls";
if(ofd.ShowDialog() == DialogResult.OK)
{
textBox2.Text = ofd.FileName;
Excel.Application excel = new Excel.Application();
Excel.Workbook wb = excel.Workbooks.Open(textBox2.Text);
Excel.Worksheet ws = wb.Sheets[1];
Excel.Range xlRange = ws.Cells[0, 0];
Debug.WriteLine("===" + xlRange.Interior.Color);
}
}
There is nice lib called EPPlus, that makes your relationships with excel much easier. You can find it on NuGet.
So use this code to get color:
var x = sheet.Cells[rowIndex, colIndex].Style.Fill.BackgroundColor;
And this to set color:
sheet.Cells[rowIndex, colIndex].Style.Fill.SetCellsColor( Color.Yellow );
Easiest solution what-so-ever :
private void Get_Colors()
{
Excel.Workbook excel = Globals.ThisAddIn.Application.ActiveWorkbook;
Excel.Worksheet sheet = null;
Excel.Range ran = sheet.UsedRange;
for (int x = 1; x <= ran.Rows.Count; x++)
{
for (int y = 1; y <= ran.Columns.Count; y++)
{
string CellColor = sheet.Cells[x, y].Interior.Color.ToString(); //Here I go double value which is converted to string.
if (sheet.Cells[x, y].Value != null && (CellColor == Color.Transparent.ToArgb().ToString() || **CellColor == Excel.XlRgbColor.rgbGold.GetHashCode().ToString()**))
{
sheet.Cells[x, y].Interior.Color = Color.Transparent;
}
}
}
}
Here's an answer that uses closed.xml. It's open-source and works with modern Excel sheets.
IXLWorkbook wb = new XLWorkbook("C:\File_path_to_excel_file"); // Replace with your file path
IXLWorksheet ws = wb.Worksheet("SheetName") // Put your sheet name here
int backgroundColor = ws.Cell(rowIndex, colIndex).Style.Fill.BackgroundColor.Color.ToArgb(); // Gives the Argb values for the cell's background. Replace rowIndex and colIndex with the index numbers of the cell you want to check
Typically an uncolored cell's Argb color is 16777215.
That's as simple as following:
using Excel = Microsoft.Office.Interop.Excel;
var cellColor = sheet.Cells[rowIndex, colIndex].Style.Fill.BackgroundColor;

Unmerge and clear cells in epplus 4.1

I had no luck deleting rows in excel so now I try to clear their content and still get the error:
"Can't delete/overwrite merged cells. A range is partly merged with the another merged range. A57788:J57788".
Columns 1-10 are really merged, but how do I unmerge them?
Here's my code:
cntr = 0;
while (ws.Cells[RowNum + cntr, 1].Value == null || !ws.Cells[RowNum + cntr, 1].Value.ToString().StartsWith("Report generation date"))
{
ws.Cells[RowNum + cntr, 1, RowNum + cntr, 18].Value = "";
ws.Cells[RowNum + cntr, 1, RowNum + cntr, 10].Merge = false;
for (int i = 1; i < 17; i++)
{
ws.Cells[RowNum + cntr, i].Style.Border.BorderAround(OfficeOpenXml.Style.ExcelBorderStyle.None);
ws.Cells[RowNum + cntr, i].Clear();
}
cntr++;
}
//ws.DeleteRow(RowNum, cntr);
The thing is you can not unmerge a single cell in a range, you have to unmerge the whole range.
To do that you can get the merged range that a cell belongs to using my solution here:
public string GetMergedRange(ExcelWorksheet worksheet, string cellAddress)
{
ExcelWorksheet.MergeCellsCollection mergedCells = worksheet.MergedCells;
foreach (var merged in mergedCells)
{
ExcelRange range = worksheet.Cells[merged];
ExcelCellAddress cell = new ExcelCellAddress(cellAddress);
if (range.Start.Row<=cell.Row && range.Start.Column <= cell.Column)
{
if (range.End.Row >= cell.Row && range.End.Column >= cell.Column)
{
return merged.ToString();
}
}
}
return "";
}
The second step is unmerging the whole range using:
public void DeleteCell(ExcelWorksheet worksheet, string cellAddress)
{
if (worksheet.Cells[cellAddress].Merge == true)
{
string range = GetMergedRange(worksheet, cellAddress); //get range of merged cells
worksheet.Cells[range].Merge = false; //unmerge range
worksheet.Cells[cellAddress].Clear(); //clear value
}
}
This way will cost you to lose merging of the other cells, and their value, to overcome this you can save value before clearing and unmerging then you can write it back, something like:
public void DeleteCell(ExcelWorksheet worksheet, string cellAddress)
{
if (worksheet.Cells[cellAddress].Merge == true)
{
var value = worksheet.Cells[cellAddress].Value;
string range = GetMergedRange(worksheet, cellAddress); //get range of merged cells
worksheet.Cells[range].Merge = false; //unmerge range
worksheet.Cells[cellAddress].Clear(); //clear value
//merge the cells you want again.
//fill the value in cells again
}
}

how to assign value(from excel) in Wintable row through coded UI

Enter code here .. I am trying to automate application through coded UI. I am stuck at this point:
Application has Table(ControlType : WinTable) and it is divided into 7 rows(ControlType : Row) like this:
FilePath : C:\test.txt
Printer Name : xyz
..
..
According to requirement, value of "FilePath" will take from excel sheet and same for "Printer name".
Here is the code:
WinWindow winP = new WinWindow();
winP.SearchProperties.Add(new PropertyExpression(WinWindow.PropertyNames.Name, "Create Print Job", PropertyExpressionOperator.Contains));
WinTable tbP = new WinTable(winP);
tbP.SearchProperties.Add(new PropertyExpression(WinTable.PropertyNames.Name, "Properties Window", PropertyExpressionOperator.Contains));
Excel.Workbook MyBook = null;
Excel.Application MyApp = null;
Excel.Worksheet MySheet = null;
MyApp = new Excel.Application();
MyApp.Visible = false;
MyBook = MyApp.Workbooks.Open("C:\\DEPCON\\DataInput.xlsx");
MySheet = (Excel.Worksheet)MyBook.Sheets[1];
Excel.Range range = MySheet.UsedRange;
for (int rCnt = 2; rCnt <= range.Rows.Count; rCnt++)
{
for (int cCnt = 1; cCnt <= range.Columns.Count; cCnt++)
{
WinRow row = tbP.GetRow(0);
row.Value= (string)(range.Cells[rCnt, cCnt] as Excel.Range).Value2; // row.Value is showing read only.
}
}
I also tried string[] values = tbP.GetContent();. Can anyone tell me how to assign a value on Row from excel sheet, one-by-one? I also checked WinTable function. All functions are related to get a value. I do not know how to set(assign) a value.

Simple Example of VSTO Excel using a worksheet as a datasource

I think I'm running into a case of "the easiest answers are the hardest ones to find" and I haven't come across any searches that give this to me in a straightforward way. This is for Excel 2010 and VS 2010 within an existing VSTO (C#) project.
I have an Excel worksheet that contains 4 columns of data that I would like to use as a source for a DataGridView. Can someone please provide C# code snippets for (1) getting the data from a particular worksheet and populating a custom object with it? (2) binding the object (like an IEnumerable list) to a Datagridview and (3) some snippets for the update and delete functionality that would be inherent to the grid and feed back to the source worksheet.
I know I'm asking for a lot here, but so much of the VSTO information seems to be dis-jointed and not always easy to find. Thanks!
Edit: Great, I just noticed that I missed a big part of your question, getting updates and deletes back to the worksheet. I have absolutely no idea if that is possible but I think that makes my solution worthless. I'll leave it here anyway, maybe it can help in any way.
Why do you need VSTO? As far as I know VSTO is used for Office Add-Ins. But since you want to show the data in a DataGridView I assume that you have a WinForms application that should just access a workbook. In this case you can simply open the workbook by using Office Interop. Just add a reference to Microsoft.Office.Interop.Excel to your project and add a using Microsoft.Office.Interop.Excel; statement.
MSDN reference documentation for Excel Interop can be found here: http://msdn.microsoft.com/en-us/library/ms262200%28v=office.14%29.aspx
I'll give you the Excel part, maybe someone else can do the rest.
First, open Excel and the workbook:
Application app = new Application();
// Optional, but recommended if the user shouldn't see Excel.
app.Visible = false;
app.ScreenUpdating = false;
// AddToMru parameter is optional, but recommended in automation scenarios.
Workbook workbook = app.Workbooks.Open(filepath, AddToMru: false);
Then somehow get the correct worksheet. You have a few possiblities:
// Active sheet (should be the one which was active the last time the workbook was saved).
Worksheet sheet = workbook.ActiveSheet;
// First sheet (notice that the first is actually 1 and not 0).
Worksheet sheet = workbook.Worksheets[1];
// Specific sheet.
// Caution: Default sheet names differ for different localized versions of Excel.
Worksheet sheet = workbook.Worksheets["Sheet1"];
Then get the correct range. You didn't specify how you know where the needed data is, so I'll assume it is in fixed columns.
// If you also know the row count.
Range range = sheet.Range["A1", "D20"];
// If you want to get all rows until the last one that has some data.
Range lastUsedCell = sheet.Cells.SpecialCells(XlCellType.xlCellTypeLastCell);
string columnName = "D" + lastUsedCell.Row;
Range range = sheet.Range["A1", columnName];
Get the values:
// Possible types of the return value:
// If a single cell is in the range: Different types depending on the cell content
// (string, DateTime, double, ...)
// If multiple cells are in the range: Two dimensional array that exactly represents
// the range from Excel and also has different types in its elements depending on the
// value of the Excel cell (should always be that one in your case)
object[,] values = range.Value;
That two dimensional object array can then be used as a data source for your DataGridView. I haven't used WinForms for years so I don't know if you can bind it directly or first need to get the data into some specific format.
Finally close Excel again:
workbook.Close(SaveChanges: false);
workbook = null;
app.Quit();
app = null;
// Yes, we really want to call those two methods twice to make sure all
// COM objects AND all RCWs are collected.
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
Correctly closing Excel after using Interop is a task itself because you have to make sure that all references to COM objects have been released. The easiest way I have found to do this is to do all the work except opening and closing Excel and the workbook (so my first and last code block) in a seperate method. This ensures that all COM objects used in that method are out of scope when Quit is called.
UPDATE:
I replaced my previous method with newer code for faster approach. System.Array is quite efficient and faster way to read and bind data to the excel. You can download the demo from this link.
I have developed VSTO application in Excel 2003 Workbook. There is no big differences in terms of syntax,so you can use it in 2007 / 2010 with no efforts.
I didn't know which event you will be using to open the window showing data so i am assuming that you will be using.
SheetFollowHyperlink
I am going to use Static workbook object declared in Showdata.cs. Here's the code for your Thisworkbook.cs
private void ThisWorkbook_Startup(object sender, System.EventArgs e)
{
ShowData._WORKBOOK = this;
}
private void ThisWorkbook_SheetFollowHyperlink(object Sh, Microsoft.Office.Interop.Excel.Hyperlink Target)
{
System.Data.DataTable dTable = GenerateDatatable();
showData sh = new showData(dTable);
sh.Show(); // You can also use ShowDialog()
}
I have added a Link on the current sheet and it will pop up the window with a datagridview.
private System.Data.DataTable GenerateDatatable()
{
Range oRng = null;
// It takes the current activesheet from the workbook. You can always pass any sheet as an argument
Worksheet ws = this.ActiveSheet as Worksheet;
// set this value using your own function to read last used column, There are simple function to find last used column
int col = 4;
// set this value using your own function to read last used row, There are simple function to find last used rows
int row = 5;
//lets assume its 4 and 5 returned by method
string strRange = "A1";
string andRange = "D5";
System.Array arr = (System.Array)ws.get_Range(strRange, andRange).get_Value(Type.Missing);
System.Data.DataTable dt = new System.Data.DataTable();
for (int cnt = 1;
cnt <= col; cnt++)
dt.Columns.Add(cnt.Chr(), typeof(string));
for (int i = 1; i <= row; i++)
{
DataRow dr = dt.NewRow();
for (int j = 1; j <= col; j++)
{
dr[j - 1] = arr.GetValue(i, j).ToString();
}
dt.Rows.Add(dr);
}
return dt;
}
Here's the form which will allow user to display and edit values. I have added extension methods and Chr() to convert numerical into respective alphabets which will come handy.
public partial class ShowData : Form
{
//use static workbook object to access Worksheets
public static ThisWorkbook _WORKBOOK;
public ShowData(System.Data.DataTable dt)
{
InitializeComponent();
// binding value to datagrid
this.dataGridView1.DataSource = dt;
}
private void RefreshExcel_Click(object sender, EventArgs e)
{
Worksheet ws = ShowData._WORKBOOK.ActiveSheet as Worksheet;
System.Data.DataTable dTable = dataGridView1.DataSource as System.Data.DataTable;
// Write values back to Excel sheet
// you can pass any worksheet of your choice in ws
WriteToExcel(dTable,ws);
}
private void WriteToExcel(System.Data.DataTable dTable,Worksheet ws)
{
int col = dTable.Columns.Count; ;
int row = dTable.Rows.Count;
string strRange = "A1";
string andRange = "D5";
System.Array arr = Array.CreateInstance(typeof(object),5,4);
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
try
{
arr.SetValue(dTable.Rows[i][j].ToString(), i, j);
}
catch { }
}
}
ws.get_Range(strRange, andRange).Value2 = arr;
this.Close();
}
public static class ExtensionMethods
{
static string alphabets = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static string Chr(this int p_intByte)
{
if (p_intByte > 0 && p_intByte <= 26)
{
return alphabets[p_intByte - 1].ToString();
}
else if (p_intByte > 26 && p_intByte <= 700)
{
int firstChrIndx = Convert.ToInt32(Math.Floor((p_intByte - 1) / 26.0));
int scndIndx = p_intByte % 26;
if (scndIndx == 0) scndIndx = 26;
return alphabets[firstChrIndx - 1].ToString() + alphabets[scndIndx - 1].ToString();
}
return "NA";
}
}
this is one of the most ugly codes I've written but it will work as a proof of concept :)
I've created an example workbook like that
Column1 Column2 Column3 Column4
------------------------------------------------------
Data-1-1 Data-2-1 Data-3-1 Data-4-1
Data-1-2 Data-2-2 Data-3-2 Data-4-2
....
Excel file contains exactly 50 lines, this explains the hard-coded range selectors.
After writing that part of code rest is easy, just create a form, add a dataviewgrid, create a data source for MyExcelData, create an instance of MyExcelData like var data = new MyExcelData(pathToExcelFile); and bind it to grid.
Code is ugly, and has many assumptions but it implements your requirements. If you open excel and program you can see updates on grid are reflected on excel after cell edited. deleted row is also removed from excel. since I did not know whether you have primary keys of your excel or not, I used row index as ID.
BTW, I'm really bad when it comes to VSTO. so if you know a better way open/edit/save please notify me.
public class MyExcelDataObject
{
private readonly MyExcelData owner;
private readonly object[,] realData;
private int RealId;
public MyExcelDataObject(MyExcelData owner, int index, object[,] realData)
{
this.owner = owner;
this.realData = realData;
ID = index;
RealId = index;
}
public int ID { get; set; }
public void DecrementRealId()
{
RealId--;
}
public string Column1
{
get { return (string)realData[RealId, 1]; }
set
{
realData[ID, 1] = value;
owner.Update(ID);
}
}
public string Column2
{
get { return (string)realData[RealId, 2]; }
set
{
realData[ID, 2] = value;
owner.Update(ID);
}
}
public string Column3
{
get { return (string)realData[RealId, 3]; }
set
{
realData[ID, 3] = value;
owner.Update(ID);
}
}
public string Column4
{
get { return (string)realData[RealId, 4]; }
set
{
realData[ID, 4] = value;
owner.Update(ID);
}
}
}
public class MyExcelData : BindingList<MyExcelDataObject>
{
private Application excel;
private Workbook wb;
private Worksheet ws;
private object[,] values;
public MyExcelData(string excelFile)
{
excel = new ApplicationClass();
excel.Visible = true;
wb = excel.Workbooks.Open(excelFile);
ws = (Worksheet)wb.Sheets[1];
var range = ws.Range["A2", "D51"];
values = (object[,])range.Value;
AllowEdit = true;
AllowRemove = true;
AllowEdit = true;
for (var index = 0; index < 50; index++)
{
Add(new MyExcelDataObject(this, index + 1, values));
}
}
public void Update(int index)
{
var item = this[index - 1];
var range = ws.Range["A" + (2 + index - 1), "D" + (2 + index - 1)];
range.Value = new object[,]
{
{item.Column1, item.Column2, item.Column3, item.Column4}
};
}
protected override void RemoveItem(int index)
{
var range = ws.Range[string.Format("A{0}:D{0}", (2 + index)), Type.Missing];
range.Select();
range.Delete();
base.RemoveItem(index);
for (int n = index; n < Count; n++)
{
this[n].DecrementRealId();
}
}
}
PS: I'd like to use lightweight objects but it adds unnecessary complications.
So in the Sheet1_Startup event
Excel.Range range1 = this.Range["A1", missing];
var obj = range1.Value2.ToString();
You would need to move to the next cell then
range1 = this.Range["A2", missing];
obj = New list(range1.Value2.ToString());

Categories