In my Workbook, I have 4 worksheets with different tab names.
Say if they are named as follows: First, Second, Third, Fourth.
I could not find online how to iterate through each of the worksheet with say for for loop. As I am iterating, I would also like to capture the text on the Worksheet's tab (First, Second, etc.).
You can either grab the worksheets by name or id such as:
int index = 1; // note indexes are 1 based in ClosedXML
var worksheet = workbook.Worksheet(index);
string name = "First";
var worksheet = workbook.Worksheet(name);
Note you'll only want to do the above in instances where you know the sheet name and max id (example)
or you can iterate through the collection of worksheets in a workbook as such:
foreach (IXLWorksheet worksheet in workbook.Worksheets)
{
Console.WriteLine(worksheet.Name); // outputs the current worksheet name.
// do the thing you want to do on each individual worksheet.
}
You can find this information in visual studio by hitting F12 on your workbook object, you'll see all of the public methods/variables you're given access too. IXLWorksheet and IXLWorksheets is what you're looking for.
Related
Is there a way to only select Sheet Lists in a Schedule?
Like in this picture, but instead of those fields, I want only the existent Sheet Lists in the project.
Creating a Sheet List, but I want all the Sheet Lists in the fields.
I actually found code that helped me out with this:
public IEnumerable<ViewSheet> GetAllSheets (Document doc)
{
IEnumerable<ViewSheet> sheets = new FilteredElementCollector(doc)
.OfClass(typeof(ViewSheet))
.OfCategory(BuiltInCategory.OST_Sheets)
.Cast<ViewSheet>()
;
return sheets;
}
e.g. a user selects multiple worksheets in a workbook using CTRL+Click. How can I determine which worksheets are selected and which not? I was playing around with that but I couldn't find any useful property to check.
Excel.Sheets abc = Globals.ThisAddIn.Application.ActiveWorkbook.Worksheets;
foreach (Excel.Worksheet sheet in abc)
{
if (sheet.EnableSelection != Excel.XlEnableSelection.xlNoSelection)
{
}
}
I was struggeling around for almost 2 hours. But shortly after I posted this question I found a solution. The ActiveWindow.SelectedSheets delivers what I was looking for.
Excel.Sheets abc = Globals.ThisAddIn.Application.ActiveWindow.SelectedSheets;
foreach (Excel.Worksheet sheet in abc)
{
string asdf = sheet.Name;
}
I haven't knwon the ActiveWindow before.
Still hoping this is useful for someone else, too.
I'm using the OpenXML SDK to update the contents of an Excel spreadsheet. When inserting cells into an Excel row they must be inserted in the correct order or the file will not open properly in Excel. I'm using the following code to find the first cell that will be after the cell I am inserting. This code comes almost directly from the OpenXML SDK documentation
public static Cell GetFirstFollowingCell(Row row, string newCellReference)
{
Cell refCell = null;
foreach (Cell cell in row.Elements<Cell>())
{
if (string.Compare(cell.CellReference.Value, newCellReference, true) > 0)
{
refCell = cell;
break;
}
}
return refCell;
}
When I edit files with this code and then open them in Excel, Excel reports that the file is corrupted. Excel is able to repair the file, but most of the data is removed from the workbook. Why does this result in file corruption?
Side note: I tried two different .NET Excel libraries before turning to the painfully low-level OpenXML SDK. NPOI created spreadsheets with corruption and EPPlus threw an exception whenever I tried to save. I was using the most recent version of each.
The code you are using is seriously flawed. This is very unfortunate, seeing as it comes from the documentation. It may work acceptably for spreadsheets that only use the first 26 columns but will fail miserably when confronted with "wider" spreadsheets. The first 26 columns are named alphabetically, A-Z. Columns 27-52 are named AA-AZ. Column 53-78 are named BA-BZ. (You should notice the pattern.)
Cell "AA1" should come after all cells with a single character column name (i.e. "A1" - "Z1"). Let's examine the current code comparing cell "AA1" with cell "B1".
string.Compare("B1", "AA1", true) returns the value 1
The code interprets this to mean that "AA1" should be placed before cell "B1".
The calling code will insert "AA1" before "B1" in the XML.
At this point the cells will be out of order and the Excel file is corrupted. Clearly, string.Compare by itself is not a sufficient test to determine the proper order of cells in a row. A more sophisticated comparison is required.
public static bool IsNewCellAfterCurrentCell(string currentCellReference, string newCellReference)
{
var columnNameRegex = new Regex("[A-Za-z]+");
var currentCellColumn = columnNameRegex.Match(currentCellReference).Value;
var newCellColumn = columnNameRegex.Match(newCellReference).Value;
var currentCellColumnLength = currentCellColumn.Length;
var newCellColumnLength = newCellColumn.Length;
if (currentCellColumnLength == newCellColumnLength)
{
var comparisonValue = string.Compare(currentCellColumn, newCellColumn, StringComparison.OrdinalIgnoreCase);
return comparisonValue > 0;
}
return currentCellColumnLength < newCellColumnLength;
}
If you wanted to place a new cell in column "BC" and you were comparing to cell "D5" you would use IsCellAfterColumn("D5", "BC5"). Substituting the new comparison function into the original code and simplifying with LINQ:
public static Cell GetFirstFollowingCell(Row row, string newCellReference)
{
var rowCells = row.Elements<Cell>();
return rowCells.FirstOrDefault(c => IsNewCellAfterCurrentCell(c.CellReference.Value, newCellReference));
}
I am creating a ListObject in Excel using VSTO as follows:
ListObject lo = ws_vsto.Controls.AddListObject(range, "MyList");
(The range variable is a previously defined range.)
If I then loop through the worksheet Controls collection I can find that ListObject.
However, if I save the workbook and reopen it, the Controls collection is empty. How can I get this ListObject back after re-opening so that I can continue working with it?
EDIT
I've got a bit further:
var wb = Globals.ThisAddIn.Application.ActiveWorkbook;
var wb_vsto = wb.GetVstoObject();
foreach (Excel.Worksheet ws in wb.Worksheets)
{
var wsv = ws.GetVstoObject();
foreach (Excel.ListObject l in ws.ListObjects)
{
MessageBox.Show(l.Name);
var lo = wsv.Controls.AddListObject(l);
Excel.Range range = lo.Range;
range.Activate();
}
}
When I get to the var lo = line, I have a ListObject added to the Controls collection and available for use. However, it's DataSource property holds null. Is there an easy way to get the original data source back?
I then thought about rebuilding the data source from the information in the range. The range.Activate() line selects the list in Excel (so I know it has the right thing). However, I can't for the life of me work out how to get the data out of that range and get the address of the range. The MSDN documentation talks about the Address property but this doesn't appear to actually exist. (MSDN documentation for VSTO seems ropey at best).
I made the following changes to the initial code. You need to get a VSTO Listobject back from the Factory.
var wb = Globals.ThisAddIn.Application.ActiveWorkbook;
var wb_vsto = wb.GetVstoObject();
foreach (Excel.Worksheet ws in wb.Worksheets)
{
var wsv = ws.GetVstoObject();
foreach (Excel.ListObject l in ws.ListObjects)
{
MessageBox.Show(l.Name);
//var lo = wsv.Controls.AddListObject(l);
Microsoft.Office.Tools.Excel.ListObject lo =
Globals.Factory.GetVstoObject(l);
// I can now get at the datasource if neede
var ds = lo.DataSource;
// In my case the datasource was DataTable.
DataTable t = (DataTable)d;
if (t.Rows.Count > 0)
{
foreach (DataRow r in t.Rows)
{
// Access row data.
}
}
//Excel.Range range = lo.Range;
//range.Activate();
}
}
Have you tried?
Microsoft.Office.Tools.Excel.ListObject lo= Microsoft.Office.Tools.Excel.ListObject.GetVstoObject(l) (it's C# I'm not sure in VB)
From MSDN GetVstoObject, be sure to read the remarks.
About your first question,
you created the listOject with the code
ListObject lo = ws_vsto.Controls.AddListObject(range, "MyList");
To recover the object after save/reope the workbook, try this line of code:
ListObject lo = Globals.Factory.GetVstoObject(Worksheet.ListObjects["MyList"]);
You simply can't get back a ListObject control that you created dynmically during run-time. this is from the MSDN documentation.
By default, dynamically created list objects are not persisted in the
worksheet as host controls when the worksheet is closed.
The only way I can get back my ListObject is to create them directly in the sheets of my template during design-time.
I'm using Excel Interop assemblies for my project,
if I want to use auto filter with then thats possible using
sheet.UsedRange.AutoFilter(1,SheetNames[1],Microsoft.Office.Interop.Excel.XlAutoFilterOperator.xlAnd,oMissing,false)
but how can I get the filtered rows ??
can anyone have idea??
Once you filtered the range, you can access the cells that pass the filter criteria by making use of the Range.SpecialCells method, passing in a valued of 'Excel.XlCellType.xlCellTypeVisible' in order to get the visible cells.
Based on your example code, above, accessing the visible cells should look something like this:
Excel.Range visibleCells = sheet.UsedRange.SpecialCells(
Excel.XlCellType.xlCellTypeVisible,
Type.Missing)
From there you can either access each cell in the visible range, via the 'Range.Cells' collection, or access each row, by first accessing the areas via the 'Range.Areas' collection and then iterating each row within the 'Rows' collection for each area. For example:
foreach (Excel.Range area in visibleCells.Areas)
{
foreach (Excel.Range row in area.Rows)
{
// Process each un-filtered, visible row here.
}
}
Hope this helps!
Mike
I used as mentioned below, similar to what Mike told,
foreach (Excel.Range area in visibleCells.Areas)
{
foreach(Excel.Range row in area.Rows)
{
int index = row.Row; // now index is the present Row index within the range
string test = Mysheet.Cells[index,4].Values //// Mysheet is my present working sheet. After this test will contain the values pointing to the values.cells[index,4]
}
}