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);
}
Related
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.
I have a certain Range, for example:
Range rng = activeWorksheet.Range["A1", "B50"];
I've been retrieving the maximum row and column number used in this Range by looping through it and assigning a row number to my variable, but there must be a better way? I've been doing this:
int maxRow = 0;
foreach (Range row in rng.Rows)
maxRow = row.Row;
I think I found the most elegant solution for this, something like this:
int maxRow = rng.Row + rng.Rows.Count - 1;
int maxColumn = rng.Column + rng.Columns.Count - 1;
rng.Row will retrieve the first used row number in the range, rng.Rows.Count - 1 will retrieve the total amount of rows used in this range and we also deduct 1 to get the correct maximum row number.
How about you search for the last used Row?
public int FindLastFilledRowInRange(Range range)
{
var cell = range.Find("*", SearchOrder: Excel.XlSearchOrder.xlByRows, SearchDirection: Excel.XlSearchDirection.xlPrevious);
return cell.Row;
}
If you start later than row 1 you can just do some additional math on the returned row...
If you don't mind evaluating the string Address...
by using ".EntireRow.Address(False, False)" we get a string that's easily parsed...
C#...
int maxRow = range.EntireRow.Address(false, false).Split(",:".ToCharArray()).Select(n => int.Parse(n)).Max();
string maxCol = range.EntireColumn.Address(false, false).Split(",:".ToCharArray()).Select(s => s.PadLeft(3)).Max().Trim();
VB... ;
Dim maxRow As Integer = -1
Dim maxCol As String = ""
range = ws.Range("$BB9:$C11")
maxRow = range.EntireRow.Address(False, False).Split(",:".ToCharArray()).Select(Function(n) Integer.Parse(n)).Max() ' 9:11
maxCol = range.EntireColumn.Address(False, False).Split(",:".ToCharArray()).Select(Function(s) s.PadLeft(3)).Max().Trim() ' C:BB
given range BB9:C11
maxRow is 11 from 9:11 ...if not parsed to int, then the Max() would be 9 due to string sorting
maxCol is "BB" from C:BB ...if not padded left, then the Max() would be "C" due to string sorting
range = ws.Range("A1:A3,AE15:AE9,C4:C7")
maxRow = range.EntireRow.Address(False, False).Split(",:".ToCharArray()).Select(Function(n) Integer.Parse(n)).Max() ' 1:3,9:15,4:7
maxCol = range.EntireColumn.Address(False, False).Split(",:".ToCharArray()).Select(Function(s) s.PadLeft(3)).Max().Trim() ' 1:3,9:15,4:7
and given range non-contiguous range: A1:A3,AE15:AE9,C4:C7
maxRow is 15 from 1:3,9:15,4:7 ...even with non-contiguous range, and referenced out of order= AE15:AE9
maxCol is "AE" from C:AE ...even with non-contiguous range
this DOES work with whole COLUMN selection "A:A"
this does NOT work with whole row selection "3:3"
I had this on a previous project:
wb = excel.Workbooks.Open(fileName);
worksheet = wb.ActiveSheet;
Range usedRange = worksheet.UsedRange;
this.lastRow = usedRange.Rows.Count;
this.lastCell = usedRange.Columns.Count;
Using lastRow will give you the emptylines which in my case we not useful.
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");
}
}
}
I have the following code:
Excel.Range range = workSheet.UsedRange;
for (rowCount = 2; rowCount <= range.Rows.Count; rowCount++)
{
//Do something here
}
However, I encounter the following problem. If I have the following excel data:
cell1, cell2, cell3
cell4, cell5, cell6
..............cell7
range.Rows.Count will return 3. However, I don't care about column C - it has some data that is used for drop-down lists within say column 1. How can I get the range.Rows.Count only for columns A and B?
For example:
Assuming there are no blanks in column A, just walk down rows until you hit a blank row using range.Offset(x,y): (sorry, I'm more fluent with Excel VBA, you'll have to translate to C#)
Dim myCell as Range
set myCell = workSheet.Range("A2");
Do Until myCell.Formula = ""
'Do something here
set myCell = myCell.Offset(1,0) 'Moves down a row
Loop
Or just loop through numerically using Worksheet.Cells(row,col):
Dim myRow as Integer
myRow = 2
Do Until cells(myrow,2).Formula = ""
'Do something here
myRow = myRow + 1
Loop
Edit: You could also use Range("A1").End(xlDown) to find the last populated cell of the first column. This should work with your current logic. Something like (I'll try it in C#):
Excel.Range range = workSheet.Range("A1", Range("A1").End(xlDown));
for (rowCount = 2; rowCount <= range.Rows.Count; rowCount++)
{
//Do something here
}
I am using Windows Application. In that Application i exported the DataGrid into Excel Successfully... Now the problem is , When i exported from Grid to the Excel Sheet, The cell values are having some green color mark on left top corner in the Excel Sheet... I thought that is type cast problem . How Shall i avoid that Problem.... and How to change the cell value from text to Number ...(i.e)Convert To Number....
Can Anyone tell me the solution of this problem?
My Code for Formatting That Excel Sheet For Some Range of Values,
wksheet.Range[GetRanges[0].ToString(), GetRanges[GetRanges.Count-2].ToString()].Merge();
wksheet.get_Range(GetRanges[0].ToString(), GetRanges[GetRanges.Count-].ToString()).Interior.Color = Color.FromArgb(192, 0, 0);
I haven't a Windows machine to test on at the moment, but perhaps you would want to try changing the cell format, e.g.:
my_range.NumberFormat = "0.0"; // change number of decimal places as needed
Here's a full example from Microsoft: How to automate Microsoft Excel from Microsoft Visual C#.NET.
The following routine will dynamically fill a spreadsheet from data (text and numbers) in a text file.
The kicker is using an object array to set the values.
StreamReader STRead;
String[] STCells;
object[] cellData;
int temp;
Excel.Range tempRange;
for (int i = 0; i < FileList.Length; i++)
{
STRead = FileList[i].OpenText();
int j = 0;
while (!STRead.EndOfStream)
{
STCells = STRead.ReadLine().Split(',');
cellData = new object[STCells.Length];
for (int k = 0; k < STCells.Length; k++)
{
if (Int32.TryParse(STCells[k], out temp))
cellData[k] = temp;
else
cellData[k] = STCells[k];
}
tempRange = sheetList[i].get_Range(ReturnCellID(j, 0),
ReturnCellID(j, STCells.Length - 1));
j++;
tempRange.Value2 = cellData;
}
STRead.Close();
}
I had this problem with a excel sheet containing both regular numbers and percent (a result of an export of strings from a Crystal Report).
To change all of them at once I made a loop somewhat like this:
//example range
Excel.Range rng = mWorkSheet.get_Range("A1", "H25");
foreach (Excel.Range range in rng)
{
if (range.Value != null)
{
int number;
bool isNumber = int.TryParse(range.Value.ToString().Trim(), out number);
if (isNumber)
{
range.NumberFormat = "#,##0";
range.Value = number;
}
else
{
//the percent values were decimals with commas in the string
string temp = range.Value.ToString();
temp = temp.Replace(",", ".");
range.Value = temp;
}
}
}
Result was that both the percentage strings and the numbers got converted into the correct Excel format.
When you assign the value to the cells, try to get all the values in an 2 dimensional array first and then assign the array to the Range. this way its going to be very fast and also i guess the values in the cell will be number. try it out ...hopefully it should work
Change the cell value from text to Number is easy with Excel Jet Cell .NET component.
Cell["A1"].Style.StringFormat = "##.#"; // or "0.0" same as in MS Excel