Excel Interop, iterating workbook for worksheet and chart - c#

I have a scenario while working with Microsoft Excel Interop.
System.Collections.IEnumerator wsEnumerator = excelApp.ActiveWorkbook.Worksheets.GetEnumerator();
while (wsEnumerator.MoveNext())
{
wsCurrent = (Excel.Worksheet)wsEnumerator.Current;
//Worksheet operation Follows
}
I am operating on Worksheets, so i can not have Chart in this. What i want to do achieve is operate on Sheets and check if it is a Worksheet or Chart, and act accordingly.
As sheets contain both Worksheet, Chart and "Excel 4.0 Macro", so what is the type of Sheets each entry as it can hold any of the type mentioned.
System.Collections.IEnumerator wsEnumerator = workBookIn.Sheets.GetEnumerator();
while (wsEnumerator.MoveNext())
{
//Identify if its a chart or worksheet
}

Solved it by checking type of Current Enumerator
var item = wsEnumerator.Current;
if (item is Excel.Chart)
{
//Do chart operations
}
else if (item is Excel.Worksheet)
{
//Do sheetoperations
}

You can re-write the entire code (without enumerator):
foreach (dynamic sheet in workBookIn.Sheets)
{
if (sheet is Chart)
// Write your code
else if (sheet is Worksheet)
// Write your code
}

Related

Is there a way to retrieve the codename of an Excel sheet in C#?

I'm working with a template where sheet names might change, but codenames will not.
Is there a way to retrieve the codename of an Excel sheet in C#?
shCodeName = SH_parent.CodeName; does not seem to work, although shName = SH_parent.Name; works just fine
//Definition of SH_parent
Excel.Worksheet SH_parent;
//Loading SH_parent
foreach (Excel.Name N in WB.Names)
{
if (!N.Name.Contains("!"))
{
//GLOBAL
string SH_Name = N.RefersTo.ToString().Split('!')[0].Replace("=", "").Replace("'", "");
SH_parent= WB.Worksheets[SH_Name];
}
else
{
//SHEET
SH_parent = N.Parent;
}

How To Import and Display Excel (.xlsx) File to Blazor WASM Client Side (Without Uploading to Server)

Like the title says. Lots of examples online for uploading files to backend but I'm after verification client side/offline followed by an upload of data only (no files). In theory, anything that runs on .Net5 should be able to run in WASM at a close performance. I'm trying to unload those hefty operations to clients' machines.
I've had some success with ClosedXML but when the file is a few 1000 rows it becomes incredibly slow. Using ClosedXML in Blazor Server-Side loads 100,000's rows with ease.
Notes:
Using MudBlazor UI Components
I have a stream from file instead of a file path for ClosedXML (like I would on a console app), I think this is the only way in WASM but I may be wrong.
I've ran the same in NPOI with similar results (slow in WASM, fast in Server Side).
I'd prefer to avoid EPPlus unless it has a magical fix.
Page:
#page "/upload"
#inject HttpClient Http
<h1>Upload Data</h1>
<p>This component demonstrates uploading data from Excel.</p>
<InputFile id="fileInput" OnChange="UploadFiles" hidden single />
<MudButton HtmlTag="label"
Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="#Icons.Filled.CloudUpload"
for="fileInput">
Upload Files
</MudButton>
#if (dataTable == null)
{
<p><em>Please upload Excel File</em></p>
}
else
{
<MudTable Items="#dataTable.AsEnumerable().Take(500)" Hover="true" Breakpoint="Breakpoint.Sm" T="DataRow" RowsPerPage="100">
<HeaderContent>
#foreach (DataColumn col in dataTable.Columns)
{
<MudTh>#col.ColumnName</MudTh>
}
</HeaderContent>
<RowTemplate>
#foreach (var cell in context.ItemArray)
{
<MudTd>#cell.ToString()</MudTd>
}
</RowTemplate>
</MudTable>
}
#code {
//private IList<IBrowserFile> files = new List<IBrowserFile>();
private DataTable dataTable;
protected override async Task OnInitializedAsync()
{
}
private async Task UploadFiles(InputFileChangeEventArgs e)
{
dataTable = await ExcelHelper.GetDataTableFromExcel(e.File);
}
}
Function:
public static async Task<DataTable> GetDataTableFromExcel(IBrowserFile file)
{
DataTable dtTable = new DataTable();
using (MemoryStream memStream = new MemoryStream())
{
await file.OpenReadStream(file.Size).CopyToAsync(memStream);
using (XLWorkbook workBook = new XLWorkbook(memStream, XLEventTracking.Disabled))
{
//Read the first Sheet from Excel file.
IXLWorksheet workSheet = workBook.Worksheet(1);
//Loop through the Worksheet rows.
bool firstRow = true;
foreach (IXLRow row in workSheet.Rows())
{
//Use the first row to add columns to DataTable.
if (firstRow)
{
foreach (IXLCell cell in row.Cells())
{
dtTable.Columns.Add(cell.Value.ToString());
}
firstRow = false;
}
else
{
//Add rows to DataTable.
dtTable.Rows.Add();
int i = 0;
foreach (IXLCell cell in row.Cells(row.FirstCellUsed().Address.ColumnNumber, row.LastCellUsed().Address.ColumnNumber))
{
dtTable.Rows[dtTable.Rows.Count - 1][i] = cell.Value.ToString();
i++;
}
}
}
}
}
return dtTable;
}
The line causing the delay is:
using (XLWorkbook workBook = new XLWorkbook(memStream, XLEventTracking.Disabled))
I need help fixing the slow reading of the xlsx file, or a completely different approach if there's a better way! A better way achieving this goal, no cheating/uploading files to the server :)

Changing color of Excel cell throws "'System.__ComObject' does not contain a definition for 'Interior'"

I am currently facing when I want to change the color of my excel sheet.
With my code I am already inserting entries into my excel file. Some specific cells should receive a special color.
When I run my code, I always get the same error.
Analyzing the Google/Stackoverflow results, I did not find a solution, even though there have been some complaints about this.
Workbooks wbs = excel.Workbooks;
Workbook sheet = wbs.Open(fileName);
excel.DisplayAlerts = false;
Worksheet y = sheet.ActiveSheet;
y.Copy(y, Type.Missing);
int index = y.Index;
int addRow = 2;
Worksheet x = (Worksheet)excel.Worksheets[index];
//...
//this line throws the error
x.Cells[addRow++, 1].Interior.Color = System.Drawing.Color.Blue;
//...
I am using Microsoft Office Interop which was very useful and did its job...until now.
You have to use the XlRgbColor to implement Color (for Excel interop 14.0):
x.Cells[addRow++, 1].Interior.Color = XlRgbColor.xlBlue;
if you have older version : you have to use translator
x.Cells[addRow++, 1].Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Silver

Excel Worksheet.Copy method copies some links in chart as external

I use following code to copy one Excel sheet to a different workbook. The source Excel sheet contains chart with series in hidden rows. (theese series are hidden by code in c# before following code)
Worksheet w; //My source worksheet with chart
w.Activate();
w.Name = CreateValidWorksheetName(targetSheetName);
w.get_Range("a1").EntireRow.EntireColumn.Copy();
w.get_Range("a1").EntireRow.EntireColumn.PasteSpecial(XlPasteType.xlPasteValues);
w.Range["A1:A1"].Select();
if (targetWorkbook != null)
{
w.Copy(targetWorkbook.Sheets[1], Type.Missing);
targetWorkbook.RefreshAll();
}
....
targetWorkbook.SaveAs(...
Now the visible series are copied correctly, but hidden series are copied as external links, e.g.:
='C:\X[sourceWorkbook.xlsx]PDK 0-32 kv'!$D$23:$D$100
Now comes the problematic part. When I open the "targetWorkbook" I see the ugly chart including hidden series. But as soon as I open manually in Excel also the "sourceWorkbook", the charts gets automatically fixed and hidden series disapear.
How to achieve this programatically?
I have now lost several hours of my life working around this bug in Worksheet.Copy method. Sometimes I wonder why I was not worse student and I could be doing something more useful now...
First I tried:
//Break links
System.Array links = (System.Array) ((object)targetWorkbook.LinkSources(XlLink.xlExcelLinks));
if (links != null)
{
for (int i = 1; i <= links.Length; i++)
{
try
{
targetWorkbook.UpdateLink((string)links.GetValue(i),
XlLinkType.xlLinkTypeExcelLinks);
targetWorkbook.BreakLink((string)links.GetValue(i),
XlLinkType.xlLinkTypeExcelLinks);
}
catch (Exception ex)
{
Tools.LogException(ex, "targetWorkbook.BreakLink");
}
}
}
No success and I got even HRESULT error. It was not possible to delete external links even using visual interface of Excel, which probably only swallowed this exception.
Finally this did the trick:
w.Activate();
w.Name = CreateValidWorksheetName(targetSheetName);
w.get_Range("a1").EntireRow.EntireColumn.Copy();
w.get_Range("a1").EntireRow.EntireColumn.PasteSpecial(XlPasteType.xlPasteValues);
w.Range["A1:A1"].Select();
if (targetWorkbook != null)
{
//Unhide all rows and then copy!!!
w.get_Range("a1").EntireRow.EntireColumn.Hidden = false;
w.get_Range("a1").EntireColumn.EntireRow.Hidden = false;
w.Copy(targetWorkbook.Sheets[1], Type.Missing);
//Then hide all rows again
HideRows(true, targetWorkbook.Sheets[1].UsedRange, "***HIDETHISROW***");
HideRows(false, targetWorkbook.Sheets[1].UsedRange, "***HIDETHISCOL***");
It is also important to check that the filetypes of the source workbook the target workbook match (both are xlsx), otherwise you may get:
"Excel cannot insert the sheets into the destination workbook, because it contains fewer rows and columns than the source workbook. To move or copy the data to the destination workbook, you can select the data, and then use the Copy and Paste commands to insert it into the sheets of another workbook."
_ExcelApp.DefaultSaveFormat = Microsoft.Office.Interop.Excel.XlFileFormat.xlOpenXMLWorkbook;

Unable to cast transparent proxy to type 'Microsoft.Office.Interop.Excel.Worksheet'

Iterating over all sheets in a Spreadsheet I ran into this error message:
Unable to cast transparent proxy to type 'Microsoft.Office.Interop.Excel.Worksheet'.
This spreadsheet has some chart tabs that obviously cant be cast to a sheet. So when I run this code I get the above error:
foreach (Microsoft.Office.Interop.Excel.Worksheet activeSheet in xlApp.Sheets)
{
}
I've looked around to find a solution and a few people have encountered it - in different scenario's to me eg:
http://social.msdn.microsoft.com/forums/en-US/vsto/thread/ab2e917a-d3bf-4e6a-84dc-7f8a9440fe0a
I am not happy with the workaround I have so far. I feel that exceptions should be used exceptionally and this isn't one of those places:
for (int i = 0; i < xlApp.Sheets.Count; i++)
{
try
{
Worksheet wk = (Worksheet)xlApp.Sheets[i];
}
catch (Exception)
{
//This isn't a sheet - avoid sheet operations
}
}
I'm wondering if anyone knows how to iterate through the Sheets (skipping non-sheets) without the need for a Try-Catch? Is there some method or property in the Excel object model that can tell you if a sheet is not really a sheet?
What about:
foreach (object possibleSheet in xlApp.Sheets)
{
Microsoft.Office.Interop.Excel.Worksheet aSheet = possibleSheet as Microsoft.Office.Interop.Excel.Worksheet;
if (aSheet == null)
continue;
// your stuff here
}
Of course it would work only if you get the exception only for the chart tabs and not for the other sheets.

Categories