How can I get back a previously created Excel ListObject? - c#

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.

Related

c# read specific columns from excel file

i have a tables like this
and i added checkboxs elements to form like this
i want to add the checkbox element text to datagridview then read the checked columns from excel file
if Date, Time, Price are checked datagridview will be like this
then get full Date column from excel file and add it to Date column in datagrid
my code to add checked boxes text as a columns in datagridview
DataTable dt = new DataTable();
foreach (Control checkbox in pnl.Controls)
if (checkbox.GetType() == typeof(CheckBox) && ((CheckBox) checkbox).Checked)
{
string txt = ((CheckBox)checkbox).Text;
dt.Columns.Add(new DataColumn(txt, typeof(object)));
}
datagrid.DataSource = dt;
There are a few steps which are needed before being able to grab data from an Excel file. So without your code, I don't know how much of this you have done. But here is the full explanation.
First
You have to add a reference to the Microsoft.Office.Interop.Excel dll (this assumes you aren't using epplus or another Nuget package). This link describes how to do this: How to reference Microsoft.Office.Interop.Excel dll?
Second
Include this library in whichever source file it is needed, and initialize an excel application (you'll also want InteropServices included):
using Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
// Global excel app object to be used anywhere
public Application ExcelApp;
// Intitializes an excel application by looking for an active one,
// and creating a new one if none are active
public void InitExcelApp()
{
try
{
ExcelApp = (Application)Marshal.GetActiveObject("Excel.Application")
}
catch(COMException ex)
{
ExcelApp = new Application
{
Visible = true
};
}
}
Third
You must initialize a workbook object. Here is how I do it, but my solution assumes you know the path to the desired Excel workbook:
Workbook myWorkbook = null;
// Checks open workbooks first
// Note that the path must be windows style. Ex: "C:\\Desktop\\myWorkbook.xlsx"
foreach (Workbook openWorkbook in ExcelApp.Workbooks)
{
if (openWorkbook.FullName == "<path to workbook>")
{
myWorkbook = openWorkbook;
}
}
// If no open workbooks were found at the known path, try opening one
if(myWorkbook is null)
{
myWorkbook = Excelapp.Workbooks.Open("<path to workbook>", Editable: true);
}
Fourth
Get the data you want. There are several ways to do this, and mine might not be the most efficient, but it works. In the code below I have included parameter names to hopefully make it more understandable.
// Gets the names of the checked items from the DataTable with columns you already added
// You could get these names from your checkboxes' names if you preferred
List<string> checkItems = new List<string>();
foreach (System.Data.DataColumn column in dt.Columns)
{
checkItems.Add(column.ColumnName);
}
// This dictionary holds info helpful for getting the correct data from excel:
// Key: a string containing the name of the column header, i.e. Date, Time, Price, etc.
// Value: an integer containing the number of that column in excel
Dictionary<string, int> excelColumnsInfo = new Dictionary<string, int>();
for (int columnNum = 1; columnNum <= myWorkbook.UsedRange.Columns.Count; columnNum++)
{
string columnHeader = myWorkbook.Cells[RowIndex: 1, ColumnIndex: columnNum].Value2.ToString();
if (checkedItems.Contains(columnHeader))
{
excelColumnsInfo.add(columnHeader, columnNum);
}
}
// Populates the data table with the data you need
// Start at row 2 to ignore the excel sheet's column headers
for (int rowNum = 2; rowNum <= myWorkbook.UsedRange.Rows.Count; rowNum++)
{
System.data.dataRow newRow = dt.NewRow();
foreach (KeyValuePair<string, int> columnInfo in excelColumnsInfo)
{
newRow[columnName: columnInfo.Key] = myWorkbook.Cells[RowIndex: rowNum, ColumnIndex: columnInfo.Value].Value2.ToString();
}
dt.Rows.Add(newRow);
}

Aspose.Cells: initialize empty workbook, workbook.Worksheets[0].Cells has no cells in it

Here's my code:
var workbook = new Workbook();
var sheet = workbook.Worksheets[0];
var row = 1;
var column = 1;
sheet.Cells.GetCell(row, column).Value = "Date";
This causes a null reference error because GetCell() returned null. Is this normal behavior? How can I properly initialize a workbook with a single blank worksheet, such that sheet.Cells.GetCell(1, 1) will not be null?
GetCell() method returns null when the cell has not been created earlier. However, Worksheet.Cells[r, c] will always return cell object because it returns the cell object if it already exists, otherwise it first creates it and then returns the cell object. It means, Worksheet.Cells[r, c] will never return null.
Please execute the following code at your end. The code explains, first time, GetCell() methods return null but second time, it returns cell object because it has already been created by Worksheet.Cells[r, c].
Please read the comments for your more help.
C#
//Create empty workbook.
Workbook wb = new Workbook();
//Access first worksheet
Worksheet ws = wb.Worksheets[0];
//This will return null because the cell is not initialized before.
Cell c = ws.Cells.GetCell(2, 5);
//This will automatically create cell object, so it will never return null.
c = ws.Cells[2, 5];
//Now execute the previous statement, this time, GetCell() will also return cell object
//because it has been created by ws.Cells[2, 5]
c = null;
c = ws.Cells.GetCell(2, 5);
Note: I am working as Developer Evangelist at Aspose

ClosedXML iterate Worksheets programmatically.

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.

Excel: Correct way to create dynamic data validation drop downs

I would like to populate data validation dropdowns each time I launch a sheet. The data in the dropdowns may change from time to time as the dropdown contents is derived from a list of items I pull from a COM dll used in my addin. I create the dropdowns like this:
void AddFieldValidationToCells<T>(Excel.Range range)
{
if (range == null)
return;
List<string> list = Enum.GetValues(typeof(T))
.Cast<T>()
.Select(v => v.ToString())
.ToList();
list.Sort();
foreach (Excel.Range cell in range)
{
cell.Validation.Delete();
cell.Validation.Add(
Excel.XlDVType.xlValidateList,
Excel.XlDVAlertStyle.xlValidAlertInformation,
Excel.XlFormatConditionOperator.xlBetween,
string.Join(",", list),
Type.Missing);
cell.Validation.IgnoreBlank = true;
cell.Validation.InCellDropdown = true;
}
}
This works perfectly. The only issue is that if the sheet were to crash, when I reopen it, I get the following error which I believe relates to the fact that the data in the validation list no longer has a reference:
If the sheet is shut down properly, my code removed the validations and this error does not occur, but if the sheet for some reason hangs or otherwise crashes, is it necessary for me to write this data to the sheet to make it persistent to avoid this message?

How can I get the Range of filtered rows using Excel Interop?

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]
}
}

Categories

Resources