I've been working with ClosedXML for the past 24 hours, I feel as if I've gotten everything out of it I need except for one bug. I have an existing workbook that has 3 sheets in it currently. Similar data will be on each sheet but it will be different values. I've attached my pseudo code below (Specific variable names have been changed to protect the innocent).
The code below is inside a foreach loop as I'm looping thru a large set of SQL tables. The problem is, ClosedXML or something I have done is writing the same data to all sheets and overwriting the sheets in front and behind it. I feel perhaps I've done something obvious but I've been staring at the same code for awhile and can no longer see the glaring error. Any help would be appreciated!
//Workbook = new workbook and filepath are initialized outside of the loop
var worksheet = workbook.Worksheet(1);
//This is used to help me identify what column it is in, it references an array of alphabet characters later
int column = 0;
if(loopCt <= 2)
{
worksheet.Cell("A1").Value = "Identifier";
worksheet.Cell("B1").Value = "XYZ Day 1";
worksheet.Cell("C1").Value = "XYZ Day 2";
worksheet.Cell("D1").Value = "XYZ Day 3";
worksheet.Cell("E1").Value = "XYZ Day 4";
worksheet.Cell("F1").Value = "XYZ Day 5";
worksheet.Cell("G1").Value = "XYZ Day 6";
worksheet.Cell("H1").Value = "XYZ Day 7";
worksheet.Cell("I1").Value = "XYZ Day 8";
worksheet.Cell("J1").Value = "XYZ Day 9";
worksheet.Cell("K1").Value = "XYZ Day 10";
worksheet.Cell("L1").Value = "XYZ Weekly Total";
}
worksheet.Cell($"A{loopCt}").Value = item.identifier;
for (int i = 0; i < XYZDaily.Count(); i++)
{
column += 1;
worksheet.Cell($"{alphabet[column]}{loopCt}").Value = XYZDaily[i];
}
worksheet.Cell($"L{loopCt}").Value = XYZWeek;
workbook.Save();
//QRS Export
var worksheetQRS = workbook.Worksheet(2);
int columnQRS = 0;
if (QRSCt <= 2)
{
worksheetQRS.Cell("A1").Value = "Identifier";
worksheetQRS.Cell("B1").Value = "QRS Day 1";
worksheetQRS.Cell("C1").Value = "QRS Day 2";
worksheetQRS.Cell("D1").Value = "QRS Day 3";
worksheetQRS.Cell("E1").Value = "QRS Day 4";
worksheetQRS.Cell("F1").Value = "QRS Day 5";
worksheetQRS.Cell("G1").Value = "QRS Day 6";
worksheetQRS.Cell("H1").Value = "QRS Day 7";
worksheetQRS.Cell("I1").Value = "QRS Day 8";
worksheetQRS.Cell("J1").Value = "QRS Day 9";
worksheetQRS.Cell("K1").Value = "QRS Day 10";
worksheetQRS.Cell("L1").Value = "QRS Weekly Total Test";
}
worksheetQRS.Cell($"A{loopCt}").Value = item.Identifier;
for (int i = 0; i < QRSDaily.Count(); i++)
{
columnQRS += 1;
worksheetQRS.Cell($"{alphabet[columnQRS]}{loopCt}").Value = QRSDaily[i];
}
worksheetQRS.Cell($"L{loopCt}").Value = QRSWeek;
workbook.Save();
TL:DR;
Writing data to an existing spreadsheet with multiple sheets,
Written inside a foreach loop
Problem: I'm attempting to target one sheet at a time, write data, move on to the next sheet and write data, but its writing the same data to all sheets within the workbook and I cant find anything in the documentation about this particular scenario.
Thanks for reading,
You are always using the first Worksheet in the Workbook:
var worksheet = workbook.Worksheet(1);
^
You need to use a variable for the worksheet number or use the workbook.AddWorksheet() method. This is covered under Creating Multiple Worksheets in the documentation.
Related
I am trying to pick string values from different Excel worksheets and write it into Word. However, each text line will have different font size, bold and underline.
I managed to pull all values into Word with StringBuilder, however I am not able to format each line way I wanted, therefore trying to make other solution without StringBuilder.
Please see code below. Unfortunately, the end result has only the last row of Excel sheet value.
Anyone has idea, what I can do here:
for (int c = 0; c < aList.Count; c++)
{
Worksheet excelWorksheet = excelWorkbook.Worksheets[aList[c]];
Microsoft.Office.Interop.Excel.Range lastRow = excelWorksheet.Cells.SpecialCells(XlCellType.xlCellTypeLastCell);
int lastUsedRow = lastRow.Row;
currentItemNumber = "Item " + (c + 1).ToString();
docRange.Text = currentItemNumber;
docRange.Font.Bold = 1;
docRange.Font.Size = 12;
docRange.Font.Underline = (WdUnderline)1;
docWholeRange.Text = docRange.Text;
for (int d = 0; d < lastUsedRow; d++)
{
if (d == 0)
{
currentRowValue = excelWorksheet.Range["A" + (d + 1)].Value2 + "\t" + excelWorksheet.Range["D"+(d+1)].Value2;
docRange.Text = currentRowValue;
docRange.Font.Bold = 0;
docRange.Font.Size = 12;
docRange.Font.Underline = (WdUnderline)0;
docWholeRange.Text = docWholeRange.Text + docRange.Text;
}
else
{
currentRowValue = excelWorksheet.Range["A" + (d + 1)].Value2+ excelWorksheet.Range["B" + (d + 1)].Value2+ excelWorksheet.Range["C" + (d + 1)].Value2;
docRange.Text = currentRowValue;
docRange.Font.Bold = 0;
docRange.Font.Size = 10;
docRange.Font.Underline = (WdUnderline)0;
docWholeRange.Text = docWholeRange.Text + docRange.Text;
}
}
wordDocument.Bookmarks.Add(bookmarkPlace, docWholeRange);
}
I tried using StringBuilder, however it is not possible to format each line separately and keep it formatted.
Then tried using Selection object in similar manner as shown in code and then tried to use Selection.TypeText. Instead of being entered at Bookmark place, it was actually starting from the beginning of word file. The size of the letter was way too small.
Managed to get all lines correctly to be written in Word without using StringBuilder.
However, trying to get all text lines between two bookmarks and all samples advising to use Bookmarks.get_Item and this does not exists in Visual Studio 2019 and C#.
Anyone has idea, how to store it into Range?
Tried following:
object start = bm.Range.End;
object end = ebm.Range.Start;
Microsoft.Office.Interop.Word.Range textRange = wordDocument.Range(ref start, ref end);
bm and ebm are two bookmarks.
I am trying to store all text line into range, then iterate line by line and change font size/bold in each text line.
Anyone has better idea how it can be done.
Regards,
I need to add underlined column headers to the header objects in a document. I am using C# and Microsoft.Office.Interop.Word
The pertinent parts of the code look like this...
foreach (Word.HeaderFooter header in wordSection.Headers)
{
int[] fiscalYears = RetrieveFiscalYears(docProfile);
string paddingFY = new String(' ', 8);
Word.Paragraph colParagraph = wordRng.Paragraphs.Add();
int year;
for (int i = fiscalYears.Length - 1; i >= 0; i--)
{
year = fiscalYears[i];
colParagraph.Range.InsertAfter(MARKUP_WORD_TAB);
//begin underline
colParagraph.Range.InsertAfter(paddingFY + year.ToString() + paddingFY);
//end underline
}
colParagraph.Range.InsertAfter(MARKUP_WORD_TAB);
colParagraph = wordRng.Paragraphs.Add();
colParagraph.set_Style(wordDoc.Styles["ColumnHeadings"]);
}
Basically it needs to look similar to ...
Expended Estimated Budgeted
2015 2016 2017
--------- ---------- --------
In the body of the document, my for loop looks like
foreach (int year in fiscalYears)
{
wordApp.Selection.TypeText(MARKUP_WORD_TAB);
wordApp.Selection.Font.Underline = Word.WdUnderline.wdUnderlineSingle;
wordApp.Selection.TypeText(paddingFY + year.ToString() + paddingFY);
wordApp.Selection.Font.Underline = Word.WdUnderline.wdUnderlineNone;
}
But the when I use the selection object, it writes to the body of the document, not to the header/footer objects. I might be able to get around this by using the SeekHeader and making it the focus, but that offers its own challenges...
I've tried using the colParagraph.Range.Font.Underline object, but that underlines the entire line, not just the words that make up the column headings.
I've tried using a find object, but the execute doesn't find the text for some reason.
Appreciate any guidance you can provide.
I had to move the set style above the for loop and set a new range based on the paragraph range and move its start and end positions. Then apply the underlining to the new range.
So now it looks similar to ....
colParagraph.Range.InsertAfter(MARKUP_WORD_TAB);
colParagraph = wordRng.Paragraphs.Last; //reset the range to include the tab so the style can be applied.
colParagraph.set_Style(wordDoc.Styles["ColumnHeadings"]);
int year;
int start = colParagraph.Range.Text.Length - 1;
string yrHeading = string.Empty;
Word.Range underlineRange = null;
for (int i = 0 ; i < fiscalYears.Length; i++)
{
year = fiscalYears[i];
colParagraph = wordRng.Paragraphs.Last; //reset the range to include the last fiscal year that was entered.
start = colParagraph.Range.Text.Length - 1;
colParagraph.Range.InsertAfter(yrHeading);
colParagraph.Range.InsertAfter(MARKUP_WORD_TAB);
underlineRange = colParagraph.Range.Duplicate;
underlineRange.MoveStart(Word.WdUnits.wdCharacter, start);
underlineRange.MoveEnd(Word.WdUnits.wdCharacter, -2); //-2 = /t/r for tab & paragraph characters
underlineRange.Font.Underline = Word.WdUnderline.wdUnderlineSingle;
}
colParagraph = wordRng.Paragraphs.Add();
I have a text file as shown below
Name:xxxx Address:xxxxx Contact No: xxx NIC No: xxxx
in a single string.
I want to read the text file and extract only the name address contact number and NIC no using c# into an excel sheet.
I was able to read the whole string and save it into an excel sheet.
Apparently, you already know how to read a textfile and how to write to Excel. Remains the problem of how to split the line into separate values.
IF all those lines have the same field labels and field order, then you could use a regex to parse the line:
string line = "Name: xx xx Address:yyy yyYY Contact No: 1234 NIC No: xXxX";
var regex = new Regex(#"Name:\s*(.*)\s*Address:\s*(.*)\s*Contact No:\s*(.*)\s*NIC No:\s*(.*)\s*");
var match = regex.Match(line);
if (match.Success)
{
var name = match.Groups[1].Value;
var address = match.Groups[2].Value;
var contactNo = match.Groups[3].Value;
var nic = match.Groups[4].Value;
// TODO write these fields to Excel
}
This regex uses the field labels ("Name:", "Address:", etc) to find the values you need. The \s* parts mean that any whitespace around the values is ignored. The (.*) parts capture values into Groups in the match class, counting from 1.
If your Name, Address Contact No etc. fields are separated using a tab delimiter (\t) then you can split the string using tab delimiter like this:
string.Split('\t');
Instead of \t you can use whatever delimiter is there is the text file.
If you have a space then you might have a problem because the fields and field values may have spaces in between.
It is not clear if you have only one record in each file.
Let's suppose your data in a single file is:
Name:N1 Address:A1 W. X, Y - Z Contact No: C1 NIC No: I1 Name:N2 Address:A2 W. X, Y - Z Contact No: C2 NIC No: I2
So there are 2 records on a single line (but there could be more)
Name:N1 Address:A1 W. X, Y - Z Contact No: C1 NIC No: I1
Name:N2 Address:A2 W. X, Y - Z Contact No: C2 NIC No: I2
I don't think splitting by white spaces is practical because fields like name and address may contain spaces. Ideally colon symbol (:) is used only as delimiter between keys and values and it's not used in any value. Otherwise the solution gets more complicated.
Also, I assume that the order of keys is guaranteed to be as in the example above:
Name
Address
Contact No
NIC No
Use a list of custom objects or a DataTable to hold your structured data.
In this example I will use DataTable:
var separators = new char[] { ':' };
var data = new DataTable();
data.Columns.Add("Name", typeof(string));
data.Columns.Add("Address", typeof(string));
data.Columns.Add("ContractNo", typeof(string));
data.Columns.Add("NICNo", typeof(string));
For each file with records, open the file, read the file content and "process" it:
foreach (string fileName in fileNames)
{
//read file content
string fileContent = ...;
string[] tokens = fileContent.Split(separators);
//we skip first token. It will always be 'Name'.
for(int i = 0; i < (tokens - 1) / 4; i++)
{
var record = data.NewRow();
string token = tokens[i * 4 + 1];
record["Name"] = token.Substring(0, token.Lenght - 7).Trim(); // Remove 'Address' from end and trim spaces
token = tokens[i * 4 + 2];
record["Address"] = token.Substring(0, token.Length - 10).Trim(); //Remove 'Contact No' from end and trim spaces
token = tokens[i * 4 + 3];
record["ContractNo"] = token.Substring(0, token.Length - 6).Trim(); //Remove 'NIC No' from end and trim spaces
token = tokens[i * 4 + 4];
if (token.EndsWith('Name')) //if there are multiple records
token = token.Substring(0, token.Length - 4);
record["NICNo"] = token.Trim();
data.Rows.Add(record);
}
}
This will also work if each file contains only one record.
Now that you have the structured data in a data table it should be easy to insert them in excel worksheet.
static void Main(string[] args)
{
//Application.WorkbookBeforeSave += new Excel.AppEvents_WorkbookBeforeSaveEventHandler(Application_WorkbookBeforeSave);
string mydocpath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
System.Data.DataTable dt = new System.Data.DataTable();
dt.Columns.Add("Name");
dt.Columns.Add("Address");
dt.Columns.Add("Contact No");
dt.Columns.Add("NIC");
foreach (string txtName in Directory.GetFiles(#"D:\unityapp\tab02", "*.txt"))
{
StreamReader sr = new StreamReader(txtName);
//string line = "Name: Address: Contact No: NIC No:";
string[] token1 = sr.ReadLine().Split(new string[] { "Name: ", "Address: ", "Contact No: ", "NIC No:" }, StringSplitOptions.None);
dt.Rows.Add(token1[1], token1[2], token1[3], token1[4]);
}
Microsoft.Office.Interop.Excel.Application x = new Microsoft.Office.Interop.Excel.Application();
// Workbook wb = x.Workbooks.Open(#"C:\Book1.xlsx");
Workbook wb = x.Workbooks.Add();
Worksheet sheet = (Microsoft.Office.Interop.Excel.Worksheet)wb.Worksheets.get_Item(1);
// Microsoft.Office.Interop.Excel.Workbook wb = new Microsoft.Office.Interop.Excel.Workbook();
// Microsoft.Office.Interop.Excel.Worksheet sheet = new Microsoft.Office.Interop.Excel.Worksheet();
sheet.Cells[1, 1] = "Name";
sheet.Cells[1, 1].Interior.ColorIndex = 10;
sheet.Cells[1, 2] = "Address";
sheet.Cells[1, 2].Interior.ColorIndex = 20;
sheet.Cells[1, 3] = "Contact No";
sheet.Cells[1, 3].Interior.ColorIndex = 30;
sheet.Cells[1, 4] = "NIC";
sheet.Cells[1, 4].Interior.ColorIndex = 40;
int rowCounter = 2;
int columnCounter = 1;
foreach (DataRow dr in dt.Rows)
{
sheet.Cells[rowCounter, columnCounter] = dr["Name"].ToString();
columnCounter += 1;
sheet.Cells[rowCounter, columnCounter] = dr["Address"].ToString();
columnCounter += 1;
sheet.Cells[rowCounter, columnCounter] = dr["Contact No"].ToString();
columnCounter += 1;
sheet.Cells[rowCounter, columnCounter] = dr["NIC"].ToString();
rowCounter += 1;
columnCounter = 1;
}
wb.SaveAs(#"D:\Unity.xlsx");
wb.Close();
x.Quit();
Process.Start(#"D:\Unity.xlsx");
}
}
}
well I have a mini program changing all excel cells beginning with ='C:\, but I have a problem. I use range.replace for do this, but my program work wrong, because it don't change all the cells contain ='C:\, only change the first find it and I don't know for what reason.
My code is :
foreach (Excel.Worksheet sheet in xlWorkBook.Sheets)
{
string sheetName = sheet.Name;
Console.WriteLine(sheetName);
//seleccion rango activo
range = sheet.UsedRange;
//leer las celdas
int rows = range.Rows.Count;
int cols = range.Columns.Count;
//Excel.Range startCell = sheet.Cells[1, 1];
//Excel.Range endCell = sheet.Cells[rows, cols];
sheet.Range["A1:J1000"].Replace(#"='C:\", #"='T:\");
//range = sheet.UsedRange;
// leer las celdas
//int rows = range.Rows.Count;
//int cols = range.Columns.Count;
//}
// liberar
releaseObject(sheet);
}
You need to do another foreach-Loop for every cell in the selected range.
I have no way of testing this right now, but this should work:
object whatToReplace = "what you want to replace";
object replacement = "what you want to replace it with";
foreach(Range cell in range)
{
cell.Replace(whatToReplace, replacement);
}
im programming in C# and i need to add a simple phrase on the last page of each ms-word doc.
i have tried this
Word.Paragraph paragraph;
int totalPages;
Word.Range range;
range = doc.Content.Duplicate;
totalPages = range.ComputeStatistics(Word.WdStatistic.wdStatisticPages);
range = range.GoTo(Word.WdGoToItem.wdGoToPage, Type.Missing, totalPages, Type.Missing);
paragraph = doc.Paragraphs.Add(range);
paragraph.Range.Font.Name = "Arial";
paragraph.Range.Font.Size = 7;
paragraph.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter;
paragraph.Range.Text = encrypt;
but this code are adding a pharse in the last but one page .
How about something like this (you may need to fix the C# syntax):
doc.Content.InsertParagraphAfter();
Word.Paragraph paragraph = doc.Paragraphs(doc.Paragraphs.Count());
paragraph.Range.Font.Name = "Arial";
paragraph.Range.Font.Size = 7;
paragraph.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter;
paragraph.Range.Text = encrypt;