Excel Open xml sdk - Copy formula between cells with range modification - c#

I have a cell with formula (CellFormula). I don't know the formula content (Might be Sum, ifs or any other calculation).
The formula might include range.
When you drag the cell to other cells (manually at excel application itself) the formula range get updated.
I want the formula programatic copy to behave the same way.
If the formula contains range specifiers I want to update them to the current row.
Is there some built-in way to do so without regular expression manipulations?

I don't know if it's a good solution that cover all possibilities but...
//Formula update
if (cloneFromCell.CellFormula != null && (cloneFromCell.CellFormula.FormulaType == null || !cloneFromCell.CellFormula.FormulaType.HasValue || cloneFromCell.CellFormula.FormulaType.Value == CellFormulaValues.Normal))
{
uint cloneRowIndex = OXMLTools.GetRowIndex(cloneFromCell.CellReference);
uint offset = rowIndex - cloneRowIndex;
exCell.CellFormula.Text = OXMLTools.GetUpdatedFormulaToNewRow(cloneFromCell.CellFormula.Text, offset);
}
public static string GetUpdatedFormulaToNewRow(string formula, uint offset)
{
return Regex.Replace(formula, #"[A-Za-z]+\d+", delegate(Match match)
{
//Calculate the new row for this cell in the formula by the given offset
uint oldRow = GetRowIndex(match.Value);
string col = GetColumnName(match.Value);
uint newRow = oldRow + offset;
//Create the new reference for this cell
string newRef = col + newRow;
return newRef;
});
}

Related

How to copy a list in a Word table cell to Excel cell

I have the following test table in Word, with one cell having a multilevel list:
Using the code below, I can copy cells from the Word Table to a corresponding cell in an Excel worksheet:
foreach (Microsoft.Office.Interop.Word.Table table in objDoc.Tables)
{
for (int row = 1; row <= table.Rows.Count; row++)
{
for (int col = 1; col <= table.Columns.Count; col++)
{
string text = table.Cell(row, col).Range.Text;
worksheet.Cells[row, col] = text;
}
}
}
However, I get the following result where the Word cell containing the list is not copied properly into Excel:
I have also tried the following:
worksheet.Cells[row, col] = table.Cell(row, col).Range.FormattedText;
But I get the same results.
I also tried converting the list in the Word file by copying and pasting with Keep Text Only to remove Word's automatic formatting, and manually deleting the tabs. That yielded this result:
Although I can get the text with the list numbers, I do not get a carriage return, line break, or line feed to separate the items the list.
At the very least, I would like to preserve the list numbering and line breaks without having to manually cut/paste with Keep Text Only; and I want to avoid having to parse the text for the list numbers (which could be numbers or letters) and inserting line feeds.
There are multiple problems involved with achieving the stated result:
Excel doesn't use the same character as Word for new lines or new paragraphs. (In this case it must be new paragraphs since the numbering is being generated.) Excel wants ANSI 10; Word is using ANSI 13. So that needs to be converted.
Automatic Line numbering is formatting. Passing a string loses formatting; it can only be carried across using Copy. Or the numbering has to be converted to plain text.
Another issue is the "dot" at the end of the cell content, which is again ANSI 13 in combination with ANSI 7 (end-of-cell marker). This should also be removed.
The following bit of sample code takes care of all three conversions. (Note: this is VBA code that I've converted off the top of my head, so watch out for small syntax "gotchas")
Word.Range rng = table.Cell[rowCounter, colCounter].Range;
//convert the numbers to plain text, then undo the conversion
rng.ListFormat.ConvertNumbersToText();
string cellContent = rng.Text;
objDoc.Undo(1);
//remove end-of-cell characters
cellContent = TrimCellText2(cellContent);
//replace remaining paragraph marks with the Excel new line character
cellContent.Replace((char)13, (char)10);
worksheet.Cells[rowCounter, colCounter].Value = cellContent;
//cut off ANSI 13 + ANSI 7 from the end of the string coming from a
//Word table cell
private string TrimCellText2(s As String)
{
int len = s.Length;
while (len > 0 && s.Substring(len - 1) == (char)13 || s.Substring(len - 1) == (char)7);
s = s.Substring(0, Math.Min(len-1, len));
return s;
}
With the help of Cindy Meister, combined with the answer from Paul Walls in this other question for replacing characters in a C# string, here is the resulting answer.
foreach (Microsoft.Office.Interop.Word.Table table in objDoc.Tables)
{
for (int row = 1; row <= table.Rows.Count; row++)
{
for (int col = 1; col <= table.Columns.Count; col++)
{
// Convert the formatted list number to plain text, then undo the conversion
table.Cell(row, col).Range.ListFormat.ConvertNumbersToText();
string cellContent = table.Cell(row, col).Range.Text;
objDoc.Undo(1);
// remove end-of-cell characters
cellContent = trimCellText2(cellContent);
// Replace remaining paragraph marks with the excel newline character
char[] linefeeds = new char[] { '\r', '\n' };
string[] temp1 = cellContent.Split(linefeeds, StringSplitOptions.RemoveEmptyEntries);
cellContent = String.Join("\n", temp1);
// Replace tabs from the list format conversion with spaces
char[] tabs = new char[] { '\t', ' ' };
string[] temp2 = cellContent.Split(tabs, StringSplitOptions.RemoveEmptyEntries);
cellContent = String.Join(" ", temp2);
worksheet.Cells[row, col] = cellContent;
}
}
}
private static string trimCellText2(string myString)
{
int len = myString.Length;
string charString13 = "" + (char)13;
string charString7 = "" + (char)7;
while ((len > 0 && myString.Substring(len - 1) == charString13) || (myString.Substring(len - 1) == charString7))
myString = myString.Substring(0, Math.Min(len - 1, len));
return myString;
}
And here is the resulting output in Excel: Excel Output

EPPLUS multiple columns configuration and conditional formatting in Excel C#

In my tool, user can choose one configuration (through combobox->multiple datatable) and the respective table will reflect in the excel sheet as per below.Columns (data in rows will differ) that will remain the same for all configuration are Product Name, Serial Name and Length 1 and Total Length. Different configuration will have added columns such as Length 2, Length 3,Length 4 (user will add the data in these rows)etc.
I want to add conditional formatting formula in Total Length column where background cell will turn green if it is in range (minval to maxval) and red when it is out of range. I am stuck with my code without solution.It did not change any color when the user add the data in the excel. Help. Thanks!
Table
private void ManualFormatExcelandAddRules(ExcelWorksheet WS, DataTable DTtoFormat, int ColStartAddOfDT, int RowStartAddOfDT)
{
int colCountofDT = DTtoFormat.Columns.Count;
int rowCountofDT = DTtoFormat.Rows.Count;
double minval = 0;
double maxval = 0;
int flag = 0;
for (int Colno = ColStartAddOfDT; Colno < ColStartAddOfDT + colCountofDT; Colno++)
{
WS.Cells[RowStartAddOfDT, Colno].Style.Border.BorderAround(ExcelBorderStyle.Thin);
for (int RowNo = RowStartAddOfDT + 1; RowNo <= RowStartAddOfDT + rowCountofDT; RowNo++)
{ if (WS.Cells[RowNo, Colno].Text.Contains("to") && WS.Cells[RowNo, ColStartAddOfDT].Text.Contains("DRAM"))
{
string[] GuidelineVal = WS.Cells[RowNo, Colno].Text.Split("to".ToArray(), StringSplitOptions.RemoveEmptyEntries).ToArray();
if (GuidelineVal[0].Trim() != "NA" && GuidelineVal[1].Trim() != "NA")
{
minval = Convert.ToDouble(GuidelineVal[0].Trim());
maxval = Convert.ToDouble(GuidelineVal[1].Trim());
flag = 0;
}
else
flag = 1;
}
else if (WS.Cells[RowNo, Colno].Text == "" && WS.Cells[RowStartAddOfDT + 1, Colno].Text.Contains("to"))
{
if (flag == 0)
{
string _statement = "AND(Convert.ToDouble(WS.Cells[RowNo, Colno].Text) >= minval,Convert.ToDouble(WS.Cells[RowNo, Colno].Text) <= maxval)";
var _cond = WS.ConditionalFormatting.AddExpression(WS.Cells[RowNo, Colno]);
_cond.Formula = _statement;
_cond.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
_cond.Style.Fill.BackgroundColor.Color = Color.Green;
}
else
WS.Cells[RowNo, Colno].Style.Fill.BackgroundColor.SetColor(Color.Red);
WS.Cells[RowNo, Colno].Style.Border.BorderAround(ExcelBorderStyle.Thin);
}
}
}
The conditional formatting expression you use is wrong/contains syntax errors/uses functions that don't exist and that makes that Excel will ignore it as it doesn't understand what it needs to do.
Looking at your code you have 4 variables that make up that expression:
RowNo and ColNo to indicate the cell to apply the conditional formattig to
minval and maxval to be the lower and upper bound of the condition
The following code uses those variables to build up the correct expression:
string _statement = string.Format(
CultureInfo.InvariantCulture,
"AND({0}>={1},{0}<={2})",
new OfficeOpenXml.ExcelCellAddress(RowNo, ColNo).Address,
minval,
maxval );
var _cond = WS.ConditionalFormatting.AddExpression(WS.Cells[RowNo, ColNo]);
_cond.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
_cond.Style.Fill.BackgroundColor.Color = Color.Green;
_cond.Formula = _statement;
Notice that the expression uses only valid Excel functions. You can't mixin .Net statements like Convert.ToDouble. It is also important to use the InvariantCulture for the number conversion, otherwise the separators might get interpreted as an extra parameter in your function.
When you debug this _statement will contain this: AND(A2>=40.2,A2<=44.5) and when applied to the A2 cell, that works as advertised.

Getting maximum row/column number from Excel Range

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.

Formula in Excel - Logarithmic Average

I need to implement this equation:
In c# it is pretty straightforward:
static double LogarithmicSummed(IReadOnlyList<double> values)
{
double outVal = 0;
for (int i = 0; i < values.Count; i++)
{
outVal = outVal + Math.Pow(10, values[i] / 10);
}
return 10 * Math.Log10(outVal);
}
I need to verify the data in an excel sheet where I print out the data programmatically. That is the raw data, my c# calculations on the raw data AND the excel formual that should match my c# calculations. I just do not know how to write the formula in excel.
I know the rows and columns where the data are and they are next to each other. So for example, if I needed the arithmetic average, ie:
I could print out for each row:
// If row = 1, startColumn = A, endColumn = D, noOfColumn = 4. This would print: =SUM(A1:D1)/4
mean[row] = #"=SUM(" + startColumn + row + ":" + endColumn + row + ")/" + noOfColumns;
What would I print out to match the first formula I wrote? Or what would I need to write in Excel?
without VBA:
Put your data in A1 through A10 and in B1 enter:
=10^(A1/10)
and copy down. Then in another cell enter:
=10*LOG10(SUM(B1:B10))
You can avoid the "helper" column (column B) by using:
=10*LOG10(SUMPRODUCT(10^((A1:A10)/10)))
If that's how you enter your formulas, then why not use the literal form of the sum? Ie use a for loop over the columns and fill in:
10*log(10^(A1/10)+10^(A2/10)+10^(A3/10)+10^(A4/10))

Convert Excel Cell Value From Text To Number Using C#

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

Categories

Resources