C# Excel add-in worksheet error 0x800A03EC - unrequested cell movement - c#

Alrighty guys I have rather a brain mangler for you; I'm trying develop a relatively simple add-in for excel that should read in data from Sheet A in a workbook, create a new or update sheet B to contain a simplified version of said data in sheet A at the press of a button. Below is some example code I'm working on:
Application.SendKeys("{ENTER}"); // Exit edit mode
Excel.Workbook wb = this.Application.ActiveWorkbook;
Excel.Worksheet sheetA = null;
Excel.Worksheet sheetB = null;
foreach (Excel.Worksheet sheet in wb.Worksheets) {
// Assume origin sheet we want to move from is same name as book name
if (sheet.Name == wb.Name)
sheetA = sheet;
// Sheet to move to will be called review. Clean if it exists.
else if (sheet.Name == "Review")
{
sheetB = sheet;
sheetB.Cells.ClearContents();
}
}
// If origin sheet cannot be found, assume it's the first sheet
if (sheetA == null)
sheetA = (Excel.Worksheet)wb.Worksheets[1];
// Add the review sheet after the origin sheet if it doesn't exist
if (sheetB == null)
{
sheetB = wb.Worksheets.Add(After: sheetA);
sheetB.Name = "Review";
}
// Simply copy across the value of the first cell
sheetB.Range["A1"].Value2 = sheetA.Range["A1"].Value2;
Now the outcomes of this code seem to be radically different depending on whether anything is in "edit mode" (cells are being edited) or not. If not, all is well as you'd expect, a new sheet is created in the correct position and the cell populated.
If a cell is being edited though, the edited cell is moved to another worksheet in the workbook. If there is no other sheet to move to a COMException with HRESULT: 0x800A03EC is thrown (error unknown).
This error shows up A LOT and it's really frustrating with it essentially telling you "be damned if I know", so any ideas would be appreciated. The most common thing seems to be "worksheet doesn't exist" which would be the case here, but I can't tell why it wants to move the edited cell in the first place?

Solution found. Simulating a return key stroke (first line of my example) does exit edit mode, but a delay is required for the application to process it. My implementation worked out as:
Application.SendKeys("{ENTER}"); // Exit edit mode
Timer timer = new Timer(100); // 100ms delay
timer.AutoReset = false; // Stop the timer looping and re-executing
timer.Elapsed += new ElapsedEventHandler((Sender, ent) =>
{
// Code you want to execute outside of edit mode
});
timer.Start(); // Start her up!
Hope that helps some lost wandering soul!

Related

Copy/Paste a listobject in excel lose the attached events

Let's create a ListObject and attach an event on it as msdn explain here: https://msdn.microsoft.com/en-us/library/eyfs6478.aspx
The code
In an application-level addin, code will look like above:
Worksheet worksheet = Globals.Factory.GetVstoObject(Globals.ThisAddIn.Application.ActiveWorkbook.Worksheets[1]);
Excel.Range cell = worksheet.Range["$A$1:$D$4"];
ListObject list1 = worksheet.Controls.AddListObject(cell, "list1");
list1.Selected += list1_SelectedDeselected;
list1.Deselected += list1_SelectedDeselected;
With something like that to see the triggered events :
private void list1_SelectedDeselected(Excel.Range Target)
{
Worksheet worksheet = Globals.Factory.GetVstoObject(Globals.ThisAddIn.Application.ActiveWorkbook.Worksheets[1]);
Excel.Range cell = worksheet.Range["$A$6"];
if (cell.Value2 == "foo")
{
cell.Value2 = "bar";
}
else
{
cell.Value2 = "foo";
}
}
Strange behavior
If you run this code within a add-in in excel, you'll see this and that
If you cut/paste this table within the same worksheet, every attached events will be triggered.
But if you cut/paste into another sheet, the events will not be attached anymore to the ListObject.
Are there a reason I don't know for this unpredictable behavior?
You need to use another VSTO object to subscribe to the events anew. The GetVstoObject should be used to get a new Worksheet object.

ExcelWriter.Style CellLocked - why won't it work?

I'm trying to create a simple example with #officewriter #excelwriter and having trouble using the styles. Not sure why.
ExcelApplication XLAPP = new ExcelApplication();
Workbook WB = XLAPP.Create();
Worksheet WKST = WB.Worksheets[0];
DataSet ds = <...some valid dataset with 2 columns....>
DataView dv = ds.Tables[0].DefaultView;
DataImportProperties props = WB.CreateDataImportProperties();
SoftArtisans.OfficeWriter.ExcelWriter.Style dataStyle = WB.CreateStyle();
props.UseColumnNames = true;
dataStyle.BackgroundColor = Color.SystemColor.Red;
dataStyle.CellLocked = true;
Area importArea = WKST.ImportData(dv, WKST.Cells[0, 0],props);
importArea.ApplyStyle(dataStyle);
XLAPP.Save(WB, Page.Response, "Output.xls", false);
Here's the problem: the color style works in the output but the CellLocked style does not. Why?
Thanks for any help. Frustrated - I thought this was a simple example!
ExcelWriter is designed to mimic Excel, so usually the steps that you need to take in Excel to active certain properties are the same steps that you need to take in ExcelWriter.
In Excel, all the cells in a worksheet have the locked style property set, but that property will not take effect until the worksheet is protected. (Right click on a cell and go to Format Cells > Protection - the Locked property is checked, but the cells are not locked until you go to Review > Protect Sheet).
Similarly, in ExcelWriter, all the cells in Worksheet.Cells have the Style.CellLocked set to true by default, but that property will not take effect until Worksheet.Protect() is called. Once you protect the sheet, the cells should be locked.
ExcelApplication xlapp = new ExcelApplication();
Workbook wb = xlapp.Create(ExcelApplication.FileFormat.Xlsx);
Worksheet ws = wb.Worksheets["Sheet1"];
//Currently, all the cells have default "locked" style set to true
//Protecting the worksheet will activate this property
ws.Protect("MyPassword");
xlapp.Save(wb, "ProtectedWorksheet.xlsx");
Since all the cells default to Locked, if you want to lock only an area of cells and not the entire worksheet, you will need to set Style.CellLocked to false on any cells that you want to leave unlocked. When the worksheet is protected, these cells will remain editable.
ExcelApplication xlapp = new ExcelApplication();
Workbook wb = xlapp.Open("MyWorkbook.xlsx");
Worksheet ws = wb.Worksheets[0];
//Create a style that will leave certain cells unlocked
//when the worksheet is protected
Style unlockStyle = wb.CreateStyle();
unlockStyle.CellLocked = false;
//Select the area that will be left unprotected
//Apply the style to that area
Area unlockedArea = ws.PopulatedCells;
unlockedArea.ApplyStyle(unlockStyle);
//Protect the worksheet
ws.Protect("MyPassword");
xlapp.Save(wb, "MyNewWorkbook.xlsx");
For more about protecting worksheets, we have a guide in our documentation: Protecting Your Worksheet.
Note: I work for SoftArtisans, makers of OfficeWriter.

Adding formula to Excel with C# - making the formula shown

I wanted to add formulas to an Excel workSheet.
I managed to do so with the Formula property.
The problem is that when I open the worksheet in Excel, I can see that the formula works - but I can only see the result in the cell. I can't see the formula that was calculated in the Formula Bar at the top of Excel.
Obviously if I enter a formula in Excel itself I can see the result in the cell and the formula in the Formula Bar.
Some of my code:
for (int i = 0; i < nOfColumns / 3; i++)
{
Range cells = workSheet.Range[workSheet.Cells[2, i * 3 + 3], workSheet.Cells[lastRowNumber, i * 3 + 3]];
cells.FormulaR1C1 = "=IF(EXACT(RC[-2],RC[-1]),TRUE,ABS(RC[-2]/RC[-1]-1))";
}
below is a test code. even after I save the workbook - the FormulaHidden is false and I can successfully retrieve the formula insterted. really frustrated
Microsoft.Office.Interop.Excel.Application excelApp = null;
Workbooks workBooks = null;
Workbook workBook = null;
Worksheet workSheet;
try
{
excelApp = new Microsoft.Office.Interop.Excel.Application();
excelApp.DisplayAlerts = false;
workBooks = excelApp.Workbooks;
workBook = workBooks.Open(filePath, AddToMru: false);
workSheet = workBook.Worksheets.get_Item(1);
int nOfColumns = workSheet.UsedRange.Columns.Count;
int lastRowNumber = workSheet.UsedRange.Rows.Count;
Range rng = workSheet.Range["C1"];
rng.Formula = "=SUM(B2:B4)";
String formula = rng.Formula; //retrieve the formula successfully
rng.FormulaHidden = false;
workSheet.Unprotect();
workBook.SaveAs(filePath, AccessMode: XlSaveAsAccessMode.xlExclusive);
formula = rng.Formula; //retrieve the formula successfully
bool hidden = rng.FormulaHidden;
}
catch (Exception e)
{
throw;
}
finally
{
if (workBook != null)
{
workBook.Close();
workBook = null;
}
if (workBooks != null)
{
workBooks.Close();
workBooks = null;
}
if (excelApp != null)
{
excelApp.Quit();
excelApp = null;
}
}
}
Anyone know how to make the formula shown, when adding the formulas programatically ?
finally !!! figured it out. this behavior is caused by the SaveAs flags.
changed
workBook.SaveAs(filePath, AccessMode: XlSaveAsAccessMode.xlExclusive);
to
workBook.SaveAs(filePath, AccessMode: XlSaveAsAccessMode.xlShared);
now the only thing left is to understand what exactly is the different between the two flags. :)
Hiding the formula (by checking Hidden checkbox on Format Cells dialog) & protecting the worksheet (thereafter) will cause the formula to not show in the formula bar.
Example VBA code
Range("C1").FormulaHidden = True 'set this property to false to make formula visible.
Sheet1.Protect
EDIT: In order to see the formula in the formula bar
Range("C1").FormulaHidden = False
Sheet1.Unprotect
Go to the Formula tab on the tool bar, and click "Show Formulas".
I think localization could be involved in this weird behaviour.
Some time ago, working in Excel, I had the impression that formulas got stored in localized language (I was using italian), then undergo a conversion when compiled. This could make sense, because localized constants are an essential part of the spreadsheet data.
I'm sorry I haven't now Excel available, so I can't be more precise, but I think you could try to localize to english your spreadsheet, or set the formula text in your local language.

Excel Interop right to left document

I created a new Excel file with C#.
When I open the document all the worksheets are align right to left.
How can I align the worksheet/workbook/window to display left to right grammatically?
Sub leftToRight()
Application.DefaultSheetDirection = xlLTR
ActiveSheet.DisplayRightToLeft = False
End Sub
You can also change the setting through Tools->Options->International. Note that you need to set/unset the Checkbox "View current sheet right-to-left" to change currently open sheets.
Edit: Sorry I accidentally interpreted your question as VBA.
Here is a c# Solution:
Excel.Application xlApp = new Excel.Application();
xlApp.Visible = true;
xlApp.Workbooks.Add(System.Type.Missing);
Excel.Worksheet active = (Excel.Worksheet)xlApp.ActiveSheet;
xlApp.DefaultSheetDirection = (int)Excel.Constants.xlLTR; //or xlRTL
active.DisplayRightToLeft = false;
I would like to introduce my implementation of this feature after i used marg concept and changed it to the right syntax for me:
public void SetWorksheetDirection(Application excel, bool isRTL)
{
Worksheet active = (Worksheet)excel.ActiveSheet;
if (isRTL)
excel.DefaultSheetDirection = (int)XlDirection.xlToRight;
else
excel.DefaultSheetDirection = (int)XlDirection.xlToLeft;
active.DisplayRightToLeft = isRTL;
}
Do this one time to change the default direction:
Alt+F11 to open the VBA editor
Ctrl+G to open the Immediate window
in the Immediate window type Application.DefaultSheetDirection = xlLTR and press Enter
Alt+Q to close the VBA editor
create a new workbook to test it
If you just wanted to organize some of the columns, you could use a line like this:
oWS.Range["A1:B2000"].HorizontalAlignment = Microsoft.Office.Interop.Excel.Constants.xlLeft;

How to "clean up" Microsoft.Office.Interop.Excel.Workbook

So I have a Microsoft.Office.Interop.Excel.Workbook object. It basically uses a template Excel file to construct itself. The Excel file contains a template column color for the results section, etc. and then the code basically just prints over those template columns, it doesn't actually customize the look of the file itself, only puts the data into it.
However, this is an issue because after it's done, our template accounts for the most POSSIBLE rows it can, but a lot of the times (most of the time), we use not even half of them.
What's the easiest way to remove all rows that DO NOT have cell data in them after the file has been created, working directly with the Microsoft.Office.Interop.Excel.Workbook object. We already have a "cleanup" method that runs after creation, but I want to add that logic to it. Here's our current cleanup:
private void CleanupExcel()
{
if (!_visible && _workbook != null)
{
_workbook.Close(false, Missing.Value, Missing.Value);
}
_workbook = null;
_sheet = null;
if (_excel != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(_excel);
// WW, 5/26/09: not sure if a problem here, but it probably is since the code was taken from here
// but in the indicator, Excel exists in the process even after the app is closed. The code here seems to fix it.
GC.Collect();
GC.WaitForPendingFinalizers();
}
_excel = null;
}
P.S. It's the first of two sheets in the document by the way. I also have access to the Microsoft.Office.Interop.Excel.Worksheet object if it's easier to do that way.
Assuming that all the empty rows are at the bottom of the sheets, you should be able to select them as a range and then delete them all, something like this I think:
Excel.Range range = _sheet.get_Range("A501", "A60000");
Excel.Range row = range.EntireRow;
rowDelete(Type.Missing);
If they're not at the bottom, maybe you could do a sort so that they all end up at the bottom and then use something similar to my code.
Try the following. It basically goes through a range (which I've hard-coded to be A1:A10), checks which rows are empty, marks them for deletion, then sweeps though and deletes them.
public void RemoveRows()
{
Excel.Range rng = Application.get_Range("A1", "A10");
List<int> rowsMarkedForDeletion = new List<int>();
for(int i = 0; i < rng.Rows.Count; i++)
{
if(Application.WorksheetFunction.CountA(rng[i + 1].EntireRow) == 0)
{
rowsMarkedForDeletion.Add(i + 1);
}
}
for(int i = rowsMarkedForDeletion.Count - 1; i >= 0; i--)
{
rng[rowsMarkedForDeletion[i]].EntireRow.Delete();
}
}
To give full credit, using COUNTA is a technique I learned from OzGrid.

Categories

Resources