I need to search an Excel file for a particular text.
I found this article at MS.
https://learn.microsoft.com/en-us/visualstudio/vsto/how-to-programmatically-search-for-text-in-worksheet-ranges?view=vs-2019
I modified it for searching the whole workbook.
It works fine till the time there is only one sheet with search value. If any other sheet also have the value, then Excel freezes with hourglass pointer. Ultimately I need to kill the process.
Here is my code:
public int searchcount(string srchtrm)
{
Excel.Range currentFind = null;
Excel.Range firstFind = null;
int stcount = 0;
foreach (Excel.Worksheet w in Globals.ThisAddIn.Application.ActiveWorkbook.Worksheets)
{
w.Select();
Excel.Range Fruits = w.UsedRange;
currentFind = Fruits.Find(srchtrm, Type.Missing,
Excel.XlFindLookIn.xlValues, Excel.XlLookAt.xlPart,
Excel.XlSearchOrder.xlByRows, Excel.XlSearchDirection.xlNext, false,
Type.Missing, Type.Missing);
while (currentFind != null)
{
if (firstFind == null)
{
firstFind = currentFind;
}
else if (currentFind.get_Address(Excel.XlReferenceStyle.xlA1)
== firstFind.get_Address(Excel.XlReferenceStyle.xlA1))
{
break;
}
currentFind.Font.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Red);
currentFind.Font.Bold = true;
currentFind = Fruits.FindNext(currentFind);
stcount = stcount + 1;
}
}
return stcount;
}
You are not resetting the currentFind and the firstFind variables. This is causing the endless loop, since you are using the currentFind and the firstFind values from the previous sheet once you have more that 1 sheet in the workbook.
The easiest solution would be to declare these variables in the inner loop:
int stcount = 0;
foreach (Excel.Worksheet w in Globals.ThisAddIn.Application.ActiveWorkbook.Worksheets)
{
Excel.Range currentFind = null;
Excel.Range firstFind = null;
w.Select();
Excel.Range Fruits = w.UsedRange;
// REST of the code....
}
Related
I'm generating an excel file in C# and excel file is format is as follows.
And Columns From C1 will be dynamic since there can be several dates.
And what I need to do is only allow editing for rows with Ctype
YYY
Is there any way to identify rows with Ctype = YYY
The code I use to generate excel is as follows
public string ExcelGenerator()
{
DataTable dt = GetDataTable();
var fileName = "ExcelFile";
var excelApp = new Application();
var workbooks = excelApp.Workbooks;
var excelWorkBook = workbooks.Add(XlWBATemplate.xlWBATWorksheet);
var sheets = excelWorkBook.Sheets;
var excelWorkSheet = sheets[1];
excelWorkSheet.Name = "ExcelFile";
int iCol = 1;
// Add column headings...
foreach (DataColumn c in dt.Columns)
{
excelWorkSheet.Cells[1, iCol] = c.ColumnName; ;
((Range)excelWorkSheet.Cells[1, iCol]).Interior.Color = ColorTranslator.ToOle(Color.DarkGray);
iCol++;
}
// for each row of data...
for (int j = 0; j < dt.Rows.Count; j++)
{
for (int k = 0; k < dt.Columns.Count; k++)
{
excelWorkSheet.Cells[j + 2, k + 1] = dt.Rows[j].ItemArray[k].ToString();
}
}
string fullpath = Path.Combine(excelPath, fileName);
if (File.Exists(fullpath))
{
try
{
File.Delete(fullpath);
}
catch (IOException e)
{
Console.Write(e.Message);
}
}
excelApp.DisplayAlerts = false;
excelWorkBook.SaveAs(fullpath, Microsoft.Office.Interop.Excel.XlFileFormat.xlWorkbookDefault, Type.Missing, Type.Missing, true, false, XlSaveAsAccessMode.xlNoChange, XlSaveConflictResolution.xlLocalSessionChanges, Type.Missing, Type.Missing);
excelWorkBook.Close(false, Type.Missing, Type.Missing);
excelApp.Quit();
Marshal.ReleaseComObject(workbooks);
Marshal.ReleaseComObject(excelWorkSheet);
Marshal.ReleaseComObject(sheets);
Marshal.ReleaseComObject(excelWorkBook);
Marshal.ReleaseComObject(excelApp);
excelWorkSheet = null;
excelWorkBook = null;
excelApp = null;
workbooks = null;
GC.WaitForPendingFinalizers();
GC.Collect();
return fileName;
}
Excel.Office.Interop don't allow range unprotection by programmatically.
For more information, you may visit this website.
https://msdn.microsoft.com/en-us/library/dkcs53f3.aspx
But if you use EPPlus library, you can do like Excel users.
if you allow selective editable column, excel requires sheet protection = true and some unprotected range.
(using EPPlus)
excelWorkSheet.Protection.IsProtected = true;
excelWorkSheet.ProtectedRanges.Add("editable", new ExcelAddress("B"));
I am reading data from numerous excel worksheets, but the performance is slow since I am fetching each col directly. Is there a way I can read the entire UsedRange into memory with one call? Then process the rows/cols locally?
The code I have is basically this:
xlWorkSheet = (Worksheet)_xlWorkBook.Worksheets.get_Item(1);
var range = xlWorkSheet.UsedRange;
for (var rCnt = 2; rCnt <= range.Rows.Count; rCnt++)
{
// Process column entries
}
I had the same problem while handling very large excel
I managed to read it as range and then transformed it to List> using AsParallel() on each row
It made it to run much faster
Here is the code:
private List<List<string>> ReadExcelFile(string fileName)
{
Excel.Application xlApp = null;
Workbook xlWorkbook = null;
Sheets xlSheets = null;
Worksheet xlSheet = null;
var results = new List<List<string>>();
try
{
xlApp = new Microsoft.Office.Interop.Excel.Application();
xlWorkbook = xlApp.Workbooks.Open(fileName, Type.Missing, true, Type.Missing, Type.Missing, Type.Missing, true, XlPlatform.xlWindows, Type.Missing,false, false, Type.Missing, false, Type.Missing, Type.Missing);
xlSheets = xlWorkbook.Sheets as Sheets;
xlSheet = xlSheets[1];
// Let's say your range is from A1 to DG5200
var cells = xlSheet.get_Range("A1", "DG5200");
results = ExcelRangeToListsParallel(cells);
}
catch (Exception)
{
results = null;
}
finally
{
xlWorkbook.Close(false);
xlApp.Quit();
if (xlSheet != null)
Marshal.ReleaseComObject(xlSheet);
if (xlSheets != null)
Marshal.ReleaseComObject(xlSheets);
if (xlWorkbook != null)
Marshal.ReleaseComObject(xlWorkbook);
if (xlApp != null)
Marshal.ReleaseComObject(xlApp);
xlApp = null;
}
return results;
}
private List<List<String>> ExcelRangeToListsParallel(Excel.Range cells)
{
return cells.Rows.Cast<Excel.Range>().AsParallel().Select(row =>
{
return row.Cells.Cast<Excel.Range>().Select(cell =>
{
var cellContent = cell.Value2;
return (cellContent == null) ? String.Empty : cellContent.ToString();
}).Cast<string>().ToList();
}).ToList();
}
I want to make changes to an existing file named "Original" in the directory "C:\Users\Twiga\Documents\VisualStudio2010\Projects\MarkSheetSystem\Original.xls".
Currently, I am only able to save the data that I have exported from my datagridview1 to a new file but that is not my intention.
Below is the code:
private void btnexcel_Click(object sender, EventArgs e)
{
Microsoft.Office.Interop.Excel._Application excel = new Microsoft.Office.Interop.Excel.Application();
Microsoft.Office.Interop.Excel._Workbook workbook = excel.Workbooks.Add(Type.Missing);
Microsoft.Office.Interop.Excel._Worksheet worksheet = null;
try
{
worksheet = workbook.ActiveSheet;
worksheet.Name = "Marksheet 1";
worksheet.Cells[12, 1] = dataGridView1.Rows[0].Cells[0].Value.ToString();
int cellRowIndex = 10;
int cellColumnIndex = 1;
//Loop through each row and read value from each column.
for (int i = 0; i < dataGridView1.Rows.Count; i++)
{
for (int j = 1; j < dataGridView1.Columns.Count - 1; j++)
{
// Excel index starts from 1,1. As first Row would have the Column headers, adding a condition check.
if (cellRowIndex == 0)
{
worksheet.Cells[cellRowIndex, cellColumnIndex] = dataGridView1.Columns[j].HeaderText;
}
else
{
worksheet.Cells[cellRowIndex, cellColumnIndex] = dataGridView1.Rows[i].Cells[j].Value.ToString();
}
cellColumnIndex++;
}
cellColumnIndex = 1;
cellRowIndex++;
}
//Getting the location and file name of the excel to save from user.
SaveFileDialog saveDialog = new SaveFileDialog();
saveDialog.Filter = "Excel files (*.xlsx)|*.xlsx|All files (*.*)|*.*";
saveDialog.FilterIndex = 2;
if (saveDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
workbook.SaveAs(saveDialog.FileName);
MessageBox.Show("The marksheet has been saved successfully!");
}
}
catch (System.Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
excel.Quit();
workbook = null;
excel = null;
}
}
Pertinent to your task, it would be better to export data to Excel Workbook from the DataGridView underlying DataTable (e.g. DataTable _dt = (DataTable)(datagridview1.DataSource)) as shown in the following C# sample code snippet:
/// <summary>
/// export DataTable to Excel (C#)
/// </summary>
internal static void Export2Excel(DataTable dataTable)
{
object misValue = System.Reflection.Missing.Value;
Microsoft.Office.Interop.Excel.Application _appExcel = null;
Microsoft.Office.Interop.Excel.Workbook _excelWorkbook = null;
Microsoft.Office.Interop.Excel.Worksheet _excelWorksheet = null;
try
{
// excel app object
_appExcel = new Microsoft.Office.Interop.Excel.Application();
// excel workbook object added
// can be replaced with _appExcel.Workbooks.Open(...)
_excelWorkbook = _appExcel.Workbooks.Add(misValue);
_excelWorksheet = _appExcel.ActiveWorkbook.ActiveSheet as Microsoft.Office.Interop.Excel.Worksheet;
// column names row (range obj)
Microsoft.Office.Interop.Excel.Range _columnsNameRange;
_columnsNameRange = _excelWorksheet.get_Range("A1", misValue).get_Resize(1, dataTable.Columns.Count);
// column names array to be assigned to _columnNameRange
string[] _arrColumnNames = new string[dataTable.Columns.Count];
for (int i = 0; i < dataTable.Columns.Count; i++)
{
// array of column names
_arrColumnNames[i] = dataTable.Columns[i].ColumnName;
}
// assign array to column headers range, make 'em bold
_columnsNameRange.set_Value(misValue, _arrColumnNames);
_columnsNameRange.Font.Bold = true;
// populate data content row by row
for (int Idx = 0; Idx < dataTable.Rows.Count; Idx++)
{
_excelWorksheet.Range["A2"].Offset[Idx].Resize[1, dataTable.Columns.Count].Value =
dataTable.Rows[Idx].ItemArray;
}
// Autofit all Columns in the range
_columnsNameRange.Columns.EntireColumn.AutoFit();
}
catch { throw; }
}
This will significantly improve the performance by exporting the entire data row at once instead of doing it cell-by-cell.
The only changes you have to make is to replace the line that adds new Workbook:
_excelWorkbook = _appExcel.Workbooks.Add(misValue);
with another one, opening the existing Excel File (e.g. "ExcelFilePath"), like:
_excelWorkbook = _appExcel.Workbooks.Open("ExcelFilePath",
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing);
Hope this may help.
I'm trying to compare a cell with a string to replace it if it is equal. But when I try to do the code below the 0x800A03EC error occurs.
int cont = 0;
string cell;
do
{
cont++;
cell = rCol.ToUpper() + cont.ToString(); // = "D1"
string cellData = ((Excel.Range)sheet.Cells[cell]).Value2.ToString();
if (cellData == from)
{
sheet.Cells[cell] = to;
}
} while (sheet.Cells[cell] == null);
How can I do this?
If you know the cell you want to check, for example A1, you can do it like this:
using System;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;
namespace Test
{
class Program
{
static void Main(string[] args)
{
// create app
var excelApp = new Excel.Application();
// open workbook
var workbook = excelApp.Workbooks.Open(
#"C:\Users\Home\Documents\Book1.xlsx",
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing);
// open sheet
var sheet = (Excel.Worksheet)workbook.Sheets[1];
// create some variables
var from = "Pete";
var to = "Dave";
// compare cell A1 [1,1] with 'from'
if (string.Equals(sheet.Cells[1,1].Value, from))
{
sheet.Cells[1, 1].Value = to;
}
// save the workbook
workbook.Save();
// close the workbook and release resources
workbook.Close(true, workbook.Path);
Marshal.ReleaseComObject(workbook);
workbook = null;
}
}
}
Try this to get at a simple range:
int row = 1;
string col = "D";
string text = sheet.get_Range(col + row.ToString()).Value;
The 0x800A03EC error is a value returned by Excel, which means NAME_NOT_FOUND (see this SA question). Looks like you were passing a parameter that Excel could not find, probably because you were passing a string ("D1"), rather than two integer parameters (4,1).
It's impossible to tell where you are getting the rCol.ToUpper() value, without seeing more of your code. However, if you are attempting to go through a series of columns and rows to check for an equality condition (that's what it looks like you are attempting), you will quickly run into the pesky problem of how to increment the column value using capital letters (just try it; not much fun!).
One solution I did in VB recently was to use the native Excel function index, which uses numeric values to get at a particular cell. You would need to cast an object of the type Excel.WorksheetFunction to use that function. But I have since discovered there are easier solutions than using an Excel function:
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;
namespace exceltest
{
class Program
{
static void Main(string[] args)
{
Microsoft.Office.Interop.Excel.Application xl = new Microsoft.Office.Interop.Excel.Application();
Microsoft.Office.Interop.Excel.Workbook workbook = xl.Workbooks.Open(#"C:\test.xlsx");
Microsoft.Office.Interop.Excel.Worksheet sheet = workbook.Sheets[1];
xl.Visible = true;
//use this if you want to use native Excel functions (such as index)
Microsoft.Office.Interop.Excel.WorksheetFunction wsFunc = xl.WorksheetFunction;
int maxNum = 100; // set maximum number of rows/columns to search
string from = "blah";
string to = "blum";
//this is pretty slow, since it has to interact with 10,000 cells in Excel
// just one example of how to access and set cell values
for (int col = 1; col <= maxNum; col++)
{
for (int row = 1; row <= maxNum; row ++)
{
Range cell = (Range)sheet.Cells[row, col];
if ((string)cell.Value == from) //cast to string to avoid null reference exceptions
{
cell.Value = to;
}
}
}
}
}
}
I'm using Excel Interop for C# to export a datagridview to Excel and print it. I'm using this code:
try
{
Microsoft.Office.Interop.Excel.Application excel = new Microsoft.Office.Interop.Excel.Application();
excel.Application.Workbooks.Add(true);
int ColumnIndex = 0;
int rowIndex = -1;
foreach (DataGridViewRow row in dataGridView1.Rows)
{
rowIndex++;
ColumnIndex = 0;
foreach (DataGridViewColumn col in dataGridView1.Columns)
{
ColumnIndex++;
excel.Cells[rowIndex + 1, ColumnIndex] = row.Cells[col.Name].Value;
}
}
excel.Visible = true;
excel.DisplayAlerts = false;
Worksheet worksheet = (Worksheet)excel.ActiveSheet;
worksheet.Activate();
worksheet.Cells.Style.HorizontalAlignment = Microsoft.Office.Interop.Excel.XlHAlign.xlHAlignCenter;
worksheet.Range[worksheet.Cells[1, 1], worksheet.Cells[1, 7]].Merge();
worksheet.Range[worksheet.Cells[2, 1], worksheet.Cells[2, 7]].Merge();
worksheet.Range[worksheet.Cells[3, 1], worksheet.Cells[3, 7]].Merge();
worksheet.Range[worksheet.Cells[4, 1], worksheet.Cells[4, 4]].Merge();
worksheet.Cells[1, 1].Font.Bold = true;
worksheet.Range["A1"].Cells.Font.Size = 15;
worksheet.Range["A4"].Cells.Font.Size = 15;
worksheet.Range["B7"].Cells.Font.Size = 15;
worksheet.Range["B8"].Cells.Font.Size = 15;
worksheet.Range["B9"].Cells.Font.Size = 15;
worksheet.Range["A11"].Cells.Font.Size = 15;
worksheet.Range["B13"].Cells.Font.Size = 15;
worksheet.PrintOut(Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
GC.Collect();
GC.WaitForPendingFinalizers();
excel.Quit();
}
catch (Exception ex)
{
MessageBox.Show("Error de exportación.");
}
It works fine, but I need to open an existing file to do this same thing to avoid reconfiguration of the printing margins. I checked other similar questions and tried to apply the "workBook = oXL.Workbooks.Open("path", ...);" but it can make it work. Any thoughts?
To open a file use the open method.
Microsoft.Office.Interop.Excel.Workbooks wkbks = null;
Microsoft.Office.Interop.Excel.Workbook wkbk = null;
wkbks = excel.Workbooks;
wkbk = wkbks.Open(xlsFileName);
At the end of your method you should do a cleanup on all your interop variables:
if (wkbk != null)
{
try
{
wkbk.Close(false);
}
catch
{
}
Marshal.FinalReleaseComObject(wkbk);
wkbk = null;
}