I made a C# winforms project which allows the user to pick a cell in a spreadsheet and write an integer value "total" into the selected cell. This takes place on an existing spreadsheet and thus this action changes the value of the cell. What is the best way to change some more cells with particular similarities?
My code is below:
private void button5_Click(object sender, EventArgs e)
{
MySheet = (Excel.Worksheet)MyBook.Sheets[1]; // Explicit cast is not required here
bpRow = excelApp.ActiveCell.Row;
bpColumn = excelApp.ActiveCell.Column;
MySheet.Cells[bpRow, bpColumn] = total;
Excel.Range colRangeH = MySheet.Columns["R:R"];//get the range object where you want to search from
Excel.Range resultRangeH = colRangeH.Find(
What: MySheet.Cells[bpRow, 18],
LookIn: Excel.XlFindLookIn.xlValues,
LookAt: Excel.XlLookAt.xlPart,
SearchOrder: Excel.XlSearchOrder.xlByRows,
SearchDirection: Excel.XlSearchDirection.xlNext
);
textBox3.Text = " " + resultRangeH;
if (MySheet.Cells[resultRangeH, 16] == MySheet.Cells[bpRow, 16])
{
if (MySheet.Cells[resultRangeH, 22] == "T.PLATE")
MySheet.Cells[resultRangeH, bpColumn] = 3 * MySheet.Cells[bpRow, bpColumn];
else if (MySheet.Cells[resultRangeH, 22] == "STUD")
MySheet.Cells[resultRangeH, bpColumn] = (int)(Math.Ceiling(MySheet.Cells[bpRow, bpColumn] / 1.33));
}
MyBook.Save();
}
The spread sheet is made of about 90 entries and each row has a 21 numbers and a written description. I added a find method in my code to give me the row number of an entry with its 18th number equal to the 18th number in the entry corresponding to the selected cell. I then wrote a few if statements to check another number value and the written description. This is so that i may change the values in the column corresponding to the selected cell accordingly.
Related
I am wanting to write a C# program to copy a specific range of cells below a specific keyword. The code will identify a keyword in Excel and then copy the values of all the cells below the keyword to copy into another range.
I am using Aspose. I attempted to write code to find the keyword and can successfully return the cell the keyword is located in. What I am trying to figure out how to do is copy the range specifically below any keyword into another range. I can successfully copy one range to another range but cannot do it from below a specific keyword.
Cells cellsOne = worksheet.Cells;
FindOptions findOptions = new FindOptions();
findOptions.LookAtType = LookAtType.StartWith;
Cell cell = cellsOne.Find("Accounting", null, findOptions);
//Printing the name of the cell found after searching worksheet
Console.WriteLine("Name of the cell containing String: " + cell.Name);
//if cell is found/value is returned
if (cell.Name.Contains("Accounting"))
{
//return cell value ?
//copy all below values (will need the cell keyword is in to do that)
//paste below values into specific columns
//doing it manually
Aspose.Cells.Range range1 = cellsOne.CreateRange("A2:A10");
Aspose.Cells.Range range2 = cellsOne.CreateRange("B28:B34");
range1.Copy(range2);
}
I have visited Aspose website but am struggling to copy a range BELOW a specific keyword. Thank you.
I don't know how to do it in C#, but I would advise you to find the cell which has the value, then in your "Range" statement below you could start at that cell you found before.
For example,
Cell cell = cellsOne.Find("Accounting", null, findOptions).Row;
You get for example "320", then your next line goes to that 320. Using your code:
Aspose.Cells.Range range1 = cellsOne.CreateRange("A2:A10");
Aspose.Cells.Range range2 = cellsOne.CreateRange("B" & cell & ":B34");
range1.Copy(range2);`
That's what I would do in VBA.
Hope it helps!
Checking your code segment a bit, I thought your destination range is "A2:A10". Your code needs some tweaks. See the updated (complete) sample code with comments to accomplish your task for your reference. I evaluated the source range (below the searched keyword) dynamically using CellsHelper static class.
e.g.
Sample code:
Workbook workbook = new Workbook("e:\\test2\\Book1.xlsx");
Worksheet worksheet = workbook.Worksheets[0];
Cells cellsOne = worksheet.Cells;
FindOptions findOptions = new FindOptions();
findOptions.LookAtType = LookAtType.StartWith;
Cell cell = cellsOne.Find("Accounting", null, findOptions);
//Printing the name of the cell found after searching worksheet
Console.WriteLine("Name of the cell containing String: " + cell.Name);
//if cell is found/value is returned
if (cell != null)
{
//I thought this is your destination range
Aspose.Cells.Range range1 = cellsOne.CreateRange("A2:A10");
//Evaluate the the next cell after (found) cell's row and column indices.
int startRow = cell.Row +1; //we add "1" to get the next in the same column
int startCol = cell.Column;
string startCell = CellsHelper.CellIndexToName(startRow, startCol);
//Set and evaluate your end cell for the range.
string endCell = CellsHelper.CellIndexToName(startRow + 6, startCol);
//Create your dynamic source range based on your startCell and endCell values
Aspose.Cells.Range range2 = cellsOne.CreateRange(startCell, endCell);
//Copy the source range to destination range
range1.Copy(range2);
}
workbook.Save("e:\\test2\\out1.xlsx");
Hope, this helps a bit.
You may also see the document on copying ranges for your further reference.
PS. I am working as Support developer/ Evangelist at Aspose.
I'm looking some help with my code for writing data to an Excel worksheet.
I'm currently writing data to a single cell at a time which is taking around three seconds per cell. As you can imagine, this time really starts to add up when you start to get into hundreds of cells.
I'm aware that there is a method of saving to a range of cells but not entirely sure how to do this.
The document I am writing to is a questionnaire style spread sheet with columns: Number, Question, Yes, No, NA.
I loop through a list of user controls which hold the users selected answer from my form and then write it to the cell.
Any help appreciated, thanks.
if (QuestionList.Count > 0)
{
//loop round rows
for (int i = 0; i < QuestionList.Count; i++)
{
//check that index is not null
if (!String.IsNullOrEmpty(QuestionList[i].Index))
{
//if the char in index is a letter
if (Char.IsLetter(QuestionList[i].Index[0]))
{
worksheet2.Cells[i + 1, 4].Value2 = QuestionList[i].Yes;
worksheet2.Cells[i + 1, 5].Value2 = QuestionList[i].No;
worksheet2.Cells[i + 1, 6].Value2 = QuestionList[i].NA;
}
}
}
}
The simple way to read multiple cell values in one go is to create a new Excel range, specifying the range of cells that you want to read.
You can then take the values within the range and put them into a multidimensional object array.
Then simply refer back to the cell position within the array to get values.
This will do one single read to get multiple cell values rather than a single read for each cell.
string name;
string dob;
string address;
//excel range
Excel.Range range = (Excel.Range)xlWorksheet1.Range["A1", "I10"];
//range array
object[,] rangeValueArray = range.Value2;
//cell 1A
if (rangeValueArray[1,1] != null)
{
name = rangeValueArray[1, 1].ToString();
}
//cell 1B
if (rangeValueArray[1, 2] != null)
{
dob = rangeValueArray[1, 2].ToString();
}
//cell 1C
if (rangeValueArray[1, 3] != null)
{
address = rangeValueArray[1, 3].ToString();
}
I have a simple excel sheet:
Now, I filter it such that cell value > 1. Now my data looks like:
Now, I select the data that I require:
Note that I have selected all the Mobile Numbers.
Now in my code, I am trying to retrieve all the selected data as follows:
Range selection = (Range)Globals.ThisAddIn.Application.ActiveWindow.Selection;
But, it gives me the cells from starting to ending. I think excel selects the non-visible rows also. Because row no 4 that contains 0 is also retrieved. Look at the image below:
So, now I created another Range and tried to add all the values of cells that are visible as follows:
Range onlyFilteredSelection = selection.Cells.SpecialCells(XlCellType.xlCellTypeVisible);
Now, I can see that c# shows me only the two rows. Why is it not displaying the last row, which is after the non-filtered row. Take a look at the values here:
Update:
After posting this question, I got a thought in my mind that I might be getting multiple ranges instead of 1 and so, I started exploring. And look what I have found. I found that I was exactly right. I get multiple ranges.
Here is the code that I have tried:
Range selection = (Range)Globals.ThisAddIn.Application.ActiveWindow.Selection;
List<Range> onlyFilteredSelection = new List<Range>();
foreach (Range range in selection.Cells.SpecialCells(XlCellType.xlCellTypeVisible))
{
onlyFilteredSelection.Add(range);
}
Now, I get 4 items in selection variable. And in onlyFilteredSelection has got 3 items.
Now, I am in another trouble:
Previously, I was getting a Range, so I converted it to a Comma-Separated String very much easily using the below mentioned code:
string[] AllRecepientMobileNumberValues = ((Array)(selection.Cells.Value2)).OfType<object>().Select(o => o.ToString()).ToArray();
string RecepientMobileNumberValue = AllRecepientMobileNumberValues.Aggregate((a, x) => a + ", " + x);
But now, I get a List. So, now my big question is how to Convert a List to Comma-Separated string?????????
You can use one more Select to get the values out of a list.
string[] AllRecepientMobileNumberValues = onlyFilteredSelection.Select(x => x.Cells.Value2).OfType<object>().Select(o => o.ToString()).ToArray();
string RecepientMobileNumberValue = AllRecepientMobileNumberValues.Aggregate((a, x) => a + ", " + x);
I tried around a bit and had problems too. But I think I may have found a possible workaround for your situation. First some things I found out.
I was able to reproduce your behaviour
the selection of all visible cells fails, because a filter (seems?) not to hide the rows, but it set the row height to 0!
I could not find any other useful method/property when looking around on the Application Member or the Range Member.
I created a macro to record what VBA code would be generated on a copy action of your selection´too. Strange thing is that there is nothing special in the code as you can see.
vba macro code
Range("A3:A5").Select ' correct as three lines are selected,
' but only two of them have a rowHeight > 0
Selection.Copy
Range("F8").Select
ActiveSheet.Paste ' and here is the magic?? why does vba only paste 2 cells??
So I decided to come up with a workaround. Why not simulation what VBA seemingly does too. Only handle those cells whose rowHeight > 0.
exampleCode.cs
private static void readFilteredCells()
{
Excel.Application xlApp = (Excel.Application)Marshal.GetActiveObject("Excel.Application");
Workbook xlBook = (Excel.Workbook)xlApp.ActiveWorkbook;
Worksheet wrkSheet = xlBook.Worksheets[1];
Range selection = xlApp.Selection;
for (int rowIndex = selection.Row; rowIndex < selection.Row + selection.Rows.Count; rowIndex++)
{
if (wrkSheet.Rows[rowIndex].EntireRow.Height!=0)
{
// do something special
}
}
}
I hope my answer is of any use for you. If you need any further assistance please let me know.
I am trying to get the last row of an excel sheet programatically using the Microsoft.interop.Excel Library and C#. I want to do that, because I am charged with looping through all the records of an excel spreadsheet and performing some kind of operation on them. Specifically, I need the actual number of the last row, as I will throw this number into a function. Anybody have any idea how to do that?
Couple ways,
using Excel = Microsoft.Office.Interop.Excel;
Excel.ApplicationClass excel = new Excel.ApplicationClass();
Excel.Application app = excel.Application;
Excel.Range all = app.get_Range("A1:H10", Type.Missing);
OR
Excel.Range last = sheet.Cells.SpecialCells(Excel.XlCellType.xlCellTypeLastCell, Type.Missing);
Excel.Range range = sheet.get_Range("A1", last);
int lastUsedRow = last.Row;
int lastUsedColumn = last.Column;
This is a common issue in Excel.
Here is some C# code:
// Find the last real row
nInLastRow = oSheet.Cells.Find("*",System.Reflection.Missing.Value,
System.Reflection.Missing.Value, System.Reflection.Missing.Value, Excel.XlSearchOrder.xlByRows,Excel.XlSearchDirection.xlPrevious, false,System.Reflection.Missing.Value,System.Reflection.Missing.Value).Row;
// Find the last real column
nInLastCol = oSheet.Cells.Find("*", System.Reflection.Missing.Value, System.Reflection.Missing.Value,System.Reflection.Missing.Value, Excel.XlSearchOrder.xlByColumns,Excel.XlSearchDirection.xlPrevious, false,System.Reflection.Missing.Value,System.Reflection.Missing.Value).Column;
found here
or using SpecialCells
Excel.Range last = sheet.Cells.SpecialCells(Excel.XlCellType.xlCellTypeLastCell, Type.Missing);
Excel.Range range = sheet.get_Range("A1", last);
[EDIT] Similar threads:
VB.NET - Reading ENTIRE content of an excel file
How to get the range of occupied cells in excel sheet
Pryank's answer is what worked closest for me. I added a little bit towards the end (.Row) so I am not just returning a range, but an integer.
int lastRow = wkSheet.Cells.SpecialCells(XlCellType.xlCellTypeLastCell, Type.Missing).Row;
The only way I could get it to work in ALL scenarios (except Protected sheets):
It supports:
Scanning Hidden Row / Columns
Ignores formatted cells with no data / formula
Code:
// Unhide All Cells and clear formats
sheet.Columns.ClearFormats();
sheet.Rows.ClearFormats();
// Detect Last used Row - Ignore cells that contains formulas that result in blank values
int lastRowIgnoreFormulas = sheet.Cells.Find(
"*",
System.Reflection.Missing.Value,
InteropExcel.XlFindLookIn.xlValues,
InteropExcel.XlLookAt.xlWhole,
InteropExcel.XlSearchOrder.xlByRows,
InteropExcel.XlSearchDirection.xlPrevious,
false,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value).Row;
// Detect Last Used Column - Ignore cells that contains formulas that result in blank values
int lastColIgnoreFormulas = sheet.Cells.Find(
"*",
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
InteropExcel.XlSearchOrder.xlByColumns,
InteropExcel.XlSearchDirection.xlPrevious,
false,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value).Column;
// Detect Last used Row / Column - Including cells that contains formulas that result in blank values
int lastColIncludeFormulas = sheet.UsedRange.Columns.Count;
int lastColIncludeFormulas = sheet.UsedRange.Rows.Count;
For questions involving the Excel object model, it's often easier to try it out in VBA first, then translating to C# is fairly trivial.
In this case one way to do it in VBA is:
Worksheet.UsedRange.Row + Worksheet.UsedRange.Rows.Count - 1
The ActiveSheet.UsedRange.Value returns a 2 dimensional object array of [row, column]. Checking the length of both dimensions will provide the LastRow index and the LastColumn index. The example below is using C#.
Excel.Worksheet activeSheet;
Excel.Range activeRange;
public virtual object[,] RangeArray
{
get { return ActiveRange.Value; }
}
public virtual int ColumnCount
{
get { return RangeArray.GetLength(1); }
}
public virtual int RowCount
{
get { return RangeArray.GetLength(0); }
}
public virtual int LastRow
{
get { return RowCount; }
}
This issue is even worse when there are possibly empty cells. But you have to read a row even if only one value is filled. It can take a while when there are a lot of unfilled cells but if the input is close to correct it is rather fast.
My solution ignores completely empty rows and returns the longest column's row count:
private static int GetLastRow(Worksheet worksheet)
{
int lastUsedRow = 1;
Range range = worksheet.UsedRange;
for (int i = 1; i < range.Columns.Count; i++)
{
int lastRow = range.Rows.Count;
for (int j = range.Rows.Count; j > 0; j--)
{
if (lastUsedRow < lastRow)
{
lastRow = j;
if (!String.IsNullOrWhiteSpace(Convert.ToString((worksheet.Cells[j, i] as Range).Value)))
{
if (lastUsedRow < lastRow)
lastUsedRow = lastRow;
if (lastUsedRow == range.Rows.Count)
return lastUsedRow - 1;
break;
}
}
else
break;
}
}
return lastUsedRow;
}
For those who use SpecialCells method, (I'm not sure about others), Please Note in case your last cell is merged, you won't be able to get last row and column number using Range.Row and Range.Column to get the last row and column as numbers.
you need to first Unmerge your range and then Again get the last cell.
It cost me a lot.
private int[] GetLastRowCol(Ex.Worksheet ws)
{
Ex.Range last = ws.Cells.SpecialCells(Ex.XlCellType.xlCellTypeLastCell, Type.Missing);
bool isMerged = (bool)last.MergeCells;
if (isMerged)
{
last.UnMerge();
last = ws.Cells.SpecialCells(Ex.XlCellType.xlCellTypeLastCell, Type.Missing);
}
return new int[2] { last.Row, last.Column };
}
As previously discussed, the techniques above (xlCellTypeLastCell etc.) do not always provide expected results. Although it's not difficult to iterate down through a column checking for values, sometimes you may find that there are empty cells or rows with data that you want to consider in subsequent rows. When using Excel directly, a good way of finding the last row is to press CTRL + Down Arrow a couple of times (you'll end up at row 1048576 for an XLSX worksheet) and then press CTRL + Up Arrow which will select the last populated cell. If you do this within Excel while recording a Macro you'll get the code to replicate this, and then it's just a case of tweaking it for C# using the Microsoft.Office.Interop.Excel libraries. For example:
private int GetLastRow()
{
Excel.Application ExcelApp;
ExcelApp = new Excel.Application();
ExcelApp.Selection.End(Excel.XlDirection.xlDown).Select();
ExcelApp.Selection.End(Excel.XlDirection.xlDown).Select();
ExcelApp.Selection.End(Excel.XlDirection.xlDown).Select();
ExcelApp.Selection.End(Excel.XlDirection.xlUp).Select();
return ExcelApp.ActiveCell.Row;
}
It may not be the most elegant solution (I guess instead you could navigate to the final row within the spreadsheet first directly before using XlUp) but it seems to be more reliable.
As CtrlDot and Leo Guardian says, it is not very acuarate the method, there some files where formats affect the "SpecialCells".
So I used a combination of that plus a While.
Range last = sheet.Cells.SpecialCells(XlCellType.xlCellTypeLastCell, Type.Missing);
Range range = sheet.get_Range("A1", last);
int lastrow = last.Row;
// Complement to confirm that the last row is the last
string textCell= "Existe";
while (textCell != null)
{
lastrow++;
textCell = sheet.Cells[lastrow + 1, 1].Value;
}
In case of using OfficeOpenXml nowadays:
using OfficeOpenXml;
using System.IO;
FileInfo excelFile = new FileInfo(filename);
ExcelPackage package = new ExcelPackage(excelFile);
ExcelWorksheet sheet = package.Workbook.Worksheets[1];
int lastRow = sheet.Dimension.End.Row;
int lastColumn = sheet.Dimension.End.Column;
I don't know if using Microsoft.Office.Interop.Excel is still state of the art or more a legacy library. In my opinion I'm doing well replacing with OfficeOpenXml. So this answer might be usefull for future search results.
I have an excel sheet, in which i have multiple cells selected, which are not adjacent to each other. When the user click on the button, we need to read all the cells data, process it and write it to some other cell.
If the cells are adjacent to each other, i was able to get the range and able to perform the operation. But if the cells are not adjacent to each other, i am not able to get the range.
The Selection.Range is always giving the address of the last cell we selected.
But we need to get the addresses of all Cells, which i am not able to do it.
Please can anybody suggest me a way to handle this scenario.
Sample code:
Range objRange = (Range) Globals.ThisAddIn.Application.Selection;
int nColCount = objRange.Columns.Count;
int nRowCount = objRange.Rows.Count;
Vinay,
I have tried this code based on your suggestion,
Range objRange = (Range) Globals.ThisAddIn.Application.Selection;
foreach (Range cell in objRange)
{
MessageBox.Show("" + cell.Value2);
}
But it didn't worked. Always it's giving the last selected cell. i have selected A1, A4, A13, A16 cells. But this code is returning the A16 Cell Value Only.
Range inherits from IEnumerable. So you can use for each iterator to enumerate via all cells.
See the equivalent VBA code below:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim result As String
result = ""
Dim c As Range
For Each c In Me.Application.Selection
result = result & ", " & c.Text
Next c
Me.Cells.Item(1, 1).Value = result
End Sub
You can always use Range.Row, Range.Column for getting the cell address.
As said in comments, use foreach synatx:
foreach(Range cell in objRange)
{
// now access cell's properties - c.Value will give value
}
After trying alot, I got the answer.
Here is the working code,
Areas objAreas = (Areas)objRange.Areas;
foreach (Range area in objAreas)
{
string CellAddress = (GetExcelColumnName(area.Column) + "" + area.Row);
MessageBox.Show(CellAddress);
}
GetExcelColumnName is the custom function u have written to convert Column number to Column Code(like a, b,... aa, ab.. etc)