How to create a Excel Row and insert it in Excel Range? - c#

I have an excel worksheet as below (just an example)
I have created a Microsoft.Office.Interop.Excel.Range object referring the range from Item1 to Category5 (selected cells in the above image).
Now I want to create a new Row (Market1, Market2, Market3, Market4, Market5) and add it below the range ie., below Category row.
I am using the Microsoft.Office.Interop.Excelclasses for the first time.
Can someone help me in figuring out how to create and add a new row to an existing range object.
Here is the code which I have written -
public class Class1
{
static void Main(string[] args)
{
Application appExcel = new Application();
WorkBook workBook = appExcel.Workbooks.Open(#"C:\Data.xlsx", true, false);
workSheet = (Worksheet)workBook.Sheets["Export"];
Range usedRange = workSheet.UsedRange;
Range itemCatRange = GetSection(usedRange, "Item1","Group1"); //Gets the selected range as shown in pic
//Here I want to create a new row of cells and add the newly created row at the end of the above range "itemCatRange"
}
private static Range GetSection(Range usedRange, string startHeader, string endHeader)
{
string str = string.Empty;
string end = String.Empty;
Range algAlmRange;
foreach (Range row in usedRange.Rows)
{
object firstColumnValue = row.Columns.Value2[1, 1];
if (firstColumnValue != null)
{
if (firstColumnValue.ToString() == startHeader)
{
str = row.Address;
}
else if (firstColumnValue.ToString() == endHeader)
{
end = row.Address;
}
}
}
algAlmRange = workSheet.Range[str, end];
return algAlmRange;
}
}

Something like
Range itemCatRange = GetSection(usedRange, "Item1","Group1");
Range lastRow = itemCatRange[itemCatRange.Rows, 1].EntireRow;
lastRow.Insert(XlDirection.xlDown, XlInsertFormatOrigin.xlFormatFromLeftOrAbove);
You may have to go down another row, or use xlUp. I haven't actually tried this.

Related

Find linked formula values from worksheets and replace with actual cell value

In a OOXML spreadsheet .xlsx you can through a linking formula fecth values from another spreadsheet and have them in your worksheet as values, that will always be updated when those values in another spreadsheet are updated.
I am using Open Xml SDK and I basically want to do what this does: https://www.e-iceblue.com/Tutorials/Spire.XLS/Spire.XLS-Program-Guide/Formula/Remove-Formulas-from-Cells-but-Keep-Values-in-Excel-in-C.html
How do I:
Find a value that has formula linking value to a cell in another spreadsheet
Replace the formula value with the actual cell value
Do this foreach cell in each worksheet in a spreadsheet
I have tried this so far: https://learn.microsoft.com/en-us/office/open-xml/how-to-retrieve-the-values-of-cells-in-a-spreadsheet
But I am recieving a NullRefereceneException each time the cell does not contain a formula or just any value. I have tried try-catch and several other ways to escape this exception, but it is not working.
But back to the challenge as outlined above; can anyone help me out?
Basic stuff such as using SOME DIRECTIVE, foreach loop, Open(), Save() I know how to do.
This worked for me:
public void Remove_CellReferences(string filepath)
{
using (SpreadsheetDocument spreadsheet = SpreadsheetDocument.Open(filepath, true))
{
// Delete all cell references in worksheet
List<WorksheetPart> worksheetparts = spreadsheet.WorkbookPart.WorksheetParts.ToList();
foreach (WorksheetPart part in worksheetparts)
{
Worksheet worksheet = part.Worksheet;
var rows = worksheet.GetFirstChild<SheetData>().Elements<Row>(); // Find all rows
foreach (var row in rows)
{
var cells = row.Elements<Cell>();
foreach (Cell cell in cells)
{
if (cell.CellFormula != null)
{
string formula = cell.CellFormula.InnerText;
if (formula.Length > 0)
{
string hit = formula.Substring(0, 1); // Transfer first 1 characters to string
if (hit == "[")
{
CellValue cellvalue = cell.CellValue; // Save current cell value
cell.CellFormula = null; // Remove RTD formula
// If cellvalue does not have a real value
if (cellvalue.Text == "#N/A")
{
cell.DataType = CellValues.String;
cell.CellValue = new CellValue("Invalid data removed");
}
else
{
cell.CellValue = cellvalue; // Insert saved cell value
}
}
}
}
}
}
}
// Delete all external link references
List<ExternalWorkbookPart> extwbParts = spreadsheet.WorkbookPart.ExternalWorkbookParts.ToList();
if (extwbParts.Count > 0)
{
foreach (ExternalWorkbookPart extpart in extwbParts)
{
var elements = extpart.ExternalLink.ChildElements.ToList();
foreach (var element in elements)
{
if (element.LocalName == "externalBook")
{
spreadsheet.WorkbookPart.DeletePart(extpart);
}
}
}
}
// Delete calculation chain
CalculationChainPart calc = spreadsheet.WorkbookPart.CalculationChainPart;
spreadsheet.WorkbookPart.DeletePart(calc);
}
}

Getting the last filled excel row using C# per specific column

Getting the last Excel row in a given worksheet has lots of nice answers here.
However, sometimes all we need is the last row of a given column and not of the whole spreadsheet.
I thought of a solution, which seems a bit slow:
find the last row in Excel for all columns;
start looping from there to 1, in the specific column, trying to find the first cell which is not empty. This is the result;
for empty first row and only first row in a given column, always return 1;
This is the implementation:
namespace ExcelTest
{
using System;
using Excel = Microsoft.Office.Interop.Excel;
public class Startup
{
const string filePath = #"C:\Users\gropc\Desktop\Sample.xlsx";
static void Main()
{
Excel.Application excel = new Excel.Application { Visible = true, EnableAnimations = false };
Excel.Workbook wkb = Open(excel, filePath);
foreach (Excel.Worksheet wks in wkb.Worksheets)
{
int lastRowA = LastRowPerColumn(1, wks);
int lastRowB = LastRowPerColumn(2, wks);
int lastRowC = LastRowPerColumn(3, wks);
Console.WriteLine($"{lastRowA} - {lastRowB} - {lastRowC}");
}
wkb.Close(true);
excel.Quit();
}
static int LastRowPerColumn(int column, Excel.Worksheet wks)
{
int lastRow = LastRowTotal(wks);
while (((wks.Cells[lastRow, column]).Text == "") && (lastRow != 1))
{
lastRow--;
}
return lastRow;
}
static int LastRowTotal(Excel.Worksheet wks)
{
Excel.Range lastCell = wks.Cells.SpecialCells(Excel.XlCellType.xlCellTypeLastCell, Type.Missing);
return lastCell.Row;
}
static Excel.Workbook Open(Excel.Application excelInstance,
string fileName, bool readOnly = false,
bool editable = true, bool updateLinks = true)
{
return excelInstance.Workbooks.Open(fileName, updateLinks, readOnly);
}
}
}
Dependencies:
using Excel = Microsoft.Office.Interop.Excel;
const string filePath = #"C:\Users\gropc\Desktop\Sample.xlsx";
Question:
Any ideas to avoid the looping? In vba the solution is quite charming in 1 line:
lastRow = ws.Cells(ws.Rows.Count, columnToCheck).End(xlUp).Row
Anything similar for the C# Excel Interop?

How retrieve each specific column's values by looping through rows using C# from excel?

I am editing uploaded excel workbooks using C# with the same logic I used to do using VBA. I am using SyncFusion to open the workbooks but however, the code below is not letting me read the whole column to apply the logic. Why?
public void AppendID(string excelFilePath, HttpResponse response)
{
using (ExcelEngine excelEngine = new ExcelEngine())
{
IApplication application = excelEngine.Excel;
application.DefaultVersion = ExcelVersion.Excel2007;
IWorkbook workbook = application.Workbooks.Open(excelFilePath);
workbook.Version = ExcelVersion.Excel97to2003;
workbook.Allow3DRangesInDataValidation = true;
//Accessing worksheet via name
IWorksheet worksheet = workbook.Worksheets[2];
When I try to define the range, the error will appear "Two names not allowed".
var prismaID = worksheet.UsedRange["C15:C"].Value;
var type = worksheet.UsedRange["F15:F"].Value;
var placements = worksheet.UsedRange["I15:I"].Value;
if (!type.Contains("PKG"))
{
placements = placements + prismaID;
}
worksheet.Range["G7"].Text = "Testing";
workbook.SaveAs(excelFilePath);
workbook.Close();
}
}
Logic:
Let's say I have three columns and how to use the following logic to manipulate usedRange cells?
ID Condition Name Output
1 Yes Sarah Sarah(1)
2 No George George
3 Yes John(3) John(3)
The logics to apply:
Move the first column 'ID' to the end of the column 'Name' but
if Column 'Condition' contains 'No'then don't move the first column
or if it contains the same 'ID' already.
Here is the VBA code:
With xlSheet
LastRow = xlSheet.UsedRange.Rows.Count
Set target = .Range(.Cells(15, 9), .Cells(LastRow, 9))
values = target.Value
Set ptype=.Range(.Cells(15,6),.Cells(LastRow,6))
pvalues=ptype.Value
For i = LBound(values, 1) To UBound(values, 1)
'if Statement for test keywords
If InStr(1,pvalues(i,1),"Package")= 0 AND InStr(1,pvalues(i,1),"Roadblock")= 0 Then
If Instr(values(I,1),.Cells(i + 15 - LBound(values, 1), 3)) = 0 Then
'If InStr(1,values(i,1),"(")=0 Then
values(i, 1) = values(i, 1) & "(" & .Cells(i + 15 - LBound(values, 1), 3) & ")"
End If
End If
Next
target.Value = values
End With
Your requirement can be achieved by appending column ID with column Name using XlsIO.
Please refer below code snippet for the same.
Code Snippet:
for(int row = 1; row<= worksheet.Columns[1].Count; row++)
{
if (worksheet[row, 2].Value == "yes" && !worksheet[row, 3].Value.EndsWith(")"))
worksheet[row, 4].Value = worksheet[row, 3].Value + "(" + worksheet[row, 1].Value + ")";
else
worksheet[row, 4].Value = worksheet[row, 3].Value;
}
We have prepared simple sample and the sample can be downloaded from the following link.
Sample Link: http://www.syncfusion.com/downloads/support/directtrac/general/ze/Sample859524528.zip
I work for Syncfusion.
So I am working with templates in excel, and I developed this logic.
I create a coupling of the first row of column names and the rows using the first cell as the key to bind the data in groups to a multi value dictionary.
I use the below function, which can be adapted to skip rows before parsing allowing you to target the proper row for binding. Book is ExcelDataReader.AsDataSet()
public static MultiValueDictionary<string, ILookup<string, string>> ParseTemplate(string Sheet, ref List<string> keys)
{
int xskip = 0;
MultiValueDictionary<string, ILookup<string, string>> mvd = new MultiValueDictionary<string, ILookup<string, string>>();
var sheetRows = Book.Tables[Sheet];
//Parse First row
var FirstRow = sheetRows.Rows[0];
for (var Columns = 0; Columns < sheetRows.Columns.Count; Columns++)
{
if (xskip == 0)
{
xskip = 1;
continue;
}
keys.Add(FirstRow[Columns].ToString());
}
//Skip First Row
xskip = 0;
//Create a binding of first row and all subsequent rows
foreach (var row in sheetRows.Select().Skip(1))
{
//Make the key the first cell of each row
var key = row[0];
List<string> rows = new List<string>();
foreach (var item in row.ItemArray)
{
if (xskip == 0)
{
xskip = 1;
continue;
}
rows.Add(item.ToString());
}
mvd.Add(key.ToString(), keys.Zip(rows, (m, n) => new { Key = m, Value = n }).ToLookup(x => x.Key, y => y.Value));
xskip = 0;
}
return mvd;
}
}
//This is example of what a function to parse this could do.
foreach(var Key in mvd.Keys)
{
var KeywithValues = mvd[Key];
foreach(ColumnName in Keys)
{
KeywithValues[ColumnName].
}
}
Hope it helps.

Open XML not saving when adding data C#

I did a complete copy from the following link: https://msdn.microsoft.com/en-us/library/dd452407(v=office.12).aspx
The copy from template works fine, the FixChartData() method works fine. However, the output File does not contain any data. I do see that the contentRow contains the data via the debugger, but the excel sheet does not have the data in it when I open the file.
Very frustrating. Any help would be appreciated.
public void Create()
{
string appPath = System.IO.Path.GetDirectoryName(System.IO.Path.GetDirectoryName(System.IO.Directory.GetCurrentDirectory()));
string templateFile = appPath + #"\Templates\ChartExample.xlsx";
string saveFile = appPath + #"\Documents\Generated.xlsx";
File.Copy(templateFile, saveFile, true);
//open copied template.
using(SpreadsheetDocument myWorkbook = SpreadsheetDocument.Open(saveFile, true))
{
//this is the workbook contains all the worksheets
WorkbookPart workbookPart = myWorkbook.WorkbookPart;
//we know that the first worksheet contains the data for the graph
WorksheetPart worksheetPart = workbookPart.WorksheetParts.First(); //getting the first worksheet
//the shhet data contains the information we are looking to alter
SheetData sheetData = worksheetPart.Worksheet.GetFirstChild<SheetData>();
int index = 2;//Row the data for the graph starts on
//var qry = from t in db.SEL_SE_DEATHS()
FudgeData fudge = new FudgeData();
var qry = fudge.Fudged();
foreach(var item in qry)
{
int Year = item.EventYear;
int PSQ = item.PSQReviewable;
int death = item.Deaths;
Row contentRow = CreateContentRow(index, Year, PSQ, death);
index++;
//contentRow.RowIndex = (UInt32)index;
sheetData.AppendChild(contentRow);
}
//(<x:c r="A2" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><x:v>2014</x:v></x:c><x:c r="B2" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><x:v>21</x:v></x:c><x:c r="C2" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><x:v>4</x:v></x:c>)
FixChartData(workbookPart, index);
worksheetPart.Worksheet.Save();
myWorkbook.Close();
myWorkbook.Dispose();
}
}
string[] headerColumns = new string[] { "A", "B", "C" }; //the columns being accessed
public Row CreateContentRow(int index, int year, int pSQ, int death)
{
Row r = new Row();
r.RowIndex = (UInt32)index;
//skipping the text add function
//we are createing a cell for each column (headerColumns),
//for each cell we are adding a value.
//we then append the value to the cell and append the cell to the row - wich is returned.
for(int i =0; i <headerColumns.Length; i++)
{
Cell c = new Cell();
c.CellReference = headerColumns[i] + index;
CellValue v = new CellValue();
if(i == 0)
{
v.Text = year.ToString();
}else if(i == 1)
{
v.Text = pSQ.ToString();
}else if(i == 2)
{
v.Text = death.ToString();
}
c.AppendChild(v);
r.AppendChild(c);
}
return r;
}
//Method for when the datatype is text based
public Cell CreateTextCell(string header, string text, int index)
{
//Create a new inline string cell.
Cell c = new Cell();
c.DataType = CellValues.InlineString;
c.CellReference = header + index;
//Add text to the text cell.
InlineString inlineString = new InlineString();
Text t = new Text();
t.Text = text;
inlineString.AppendChild(t);
c.AppendChild(inlineString);
return c;
}
//fix the chart Data Regions
public void FixChartData(WorkbookPart workbookPart, int totalCount)
{
var wsparts = workbookPart.WorksheetParts.ToArray();
foreach(WorksheetPart wsp in wsparts)
{
if(wsp.DrawingsPart != null)
{
ChartPart chartPart = wsp.DrawingsPart.ChartParts.First();
////change the ranges to accomodate the newly inserted data.
foreach (DocumentFormat.OpenXml.Drawing.Charts.Formula formula in chartPart.ChartSpace.Descendants<DocumentFormat.OpenXml.Drawing.Charts.Formula>())
{
if (formula.Text.Contains("$2"))
{
string s = formula.Text.Split('$')[1];
formula.Text += ":$" + s + "$" + totalCount;
}
}
chartPart.ChartSpace.Save();
}
}
//ChartPart chartPart = workbookPart.ChartsheetParts.First().DrawingsPart.ChartParts.First();
////change the ranges to accomodate the newly inserted data.
//foreach(DocumentFormat.OpenXml.Drawing.Charts.Formula formula in chartPart.ChartSpace.Descendants<DocumentFormat.OpenXml.Drawing.Charts.Formula>())
//{
// if (formula.Text.Contains("$2"))
// {
// string s = formula.Text.Split('$')[1];
// formula.Text += ":$" + s + "$" + totalCount;
// }
//}
//chartPart.ChartSpace.Save();
}
David,
I got your code to work fine. Here is a link to my Console Application.. I uploaded it to Github with some minor changes. I made 2 changes:
1) I was not able to download the samples from the link you provided. So I created a blank empty spreadsheet with Excel2016 and saved it in that directory.
2) The Fudge data was missing, so I generated some sample data via self mocked object.
The spreadsheet copies fine from the template and your code populates it with the fudge data. Here is what the final result looks like:
After downloading, you will need to make a Template and Document subdirectory. Then place my ChartExample.xslx file in the Template directory and run.

Iterate Through rows in Excel

I am attempting to get all the numbers from the cells in column A from an excel spreadsheet, but I am only getting my header line returned. What am I doing wrong?
static void Main(string[] args)
{
var excel = new Microsoft.Office.Interop.Excel.Application();
Workbook workbook = excel.Workbooks.Open(#"C:\Documents\ANIs.xlsx");
Worksheet worksheet = workbook.Worksheets[1];
Range a1 = worksheet.get_Range("$A1");
object rawValue = a1.Value;
string Text = a1.Text;
foreach (Range item in a1.Cells)
{
Console.WriteLine("{1}", rawValue, Text);
}
Console.Read();
}
You are getting only the cell A1, you can get all the cells in column A by using :
Range firstCol = workSheet.Range("A:A");
Because your worksheet.get_Range is only returning the one cell. You need to specify the lower-right cell also to get multiple cells, like this:
worksheet.get_Range("$A1", "$D9");
Check out MSDN

Categories