Selecting a specific sheet in VSTO Excel Add-In - c#

I'm trying to select a specific sheet (by name or index) with my excel Add-In with no avail.
My addin file ThisAddIn.cs has:
public Excel.Workbook GetActiveWorkbook()
{
return (Excel.Workbook)Application.ActiveWorkbook;
}
And my Ribbon1.cs has:
namespace Test3
{
public partial class Ribbon1
{
private void Ribbon1_Load(object sender, RibbonUIEventArgs e)
{
Debug.WriteLine("Hello");
Workbook currentwb = Globals.ThisAddIn.GetActiveWorkbook();
Worksheet scratch = currentwb.Worksheets.Item[1] as Worksheet; // Error blocks here
if (scratch == null)
return;
// Worksheet scratch = currentwb.Worksheets["Sheets1"];
scratch.Range["A1"].Value = "Hello";
}
}
}
But I get a System.NullReferenceException: 'Object reference not set to an instance of an object.'
I'm new to c# (come from Python) and am very confused why this doesn't work. Any help would be greatly appreciated.

The issue was I was trying to load the Workbook instance before it was loaded. In other words, trying to grab the instance in Ribbon Load was too early. Grabbing the instance on a ButtonClick event worked fine!

Try
Excel.Application ExApp = Globals.ThisAddIn.Application as Excel.Application;
Worksheet sheet = Globals.Factory.GetVstoObject(ExApp.ActiveWorkbook.Worksheets[1]);
Worksheet vsheet = Globals.Factory.GetVstoObject(ExApp.ActiveWorkbook.Worksheets[2]);
sheet.Cells[1, 1] = "A1";
vsheet.Cells[1, 1] = "A1 from second sheet";

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.

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

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!

System.DBNull to String error in excel

I have my own custom file type similar to .CSV except with custom delimiters. The delimiter for the comma is (char)20 (looks like a square) and for the quotes it is (char)254 (looks like þ). I have created an Excel 2010 Add-in in Visual studio to parse through the document that replaces all custom delimiters with commas and double-quotes so that it is in .CSV format.
The program also creates a new toolbar and button that will start the process. It works fine on some documents but not on others and if you try to do it twice in one instance of Excel it comes up with the error "Cannot implicitly convert type 'System.DBNull' to 'string'". This is because the row.Text property is being read as {}.
Now my question is what is causing the row.Text property to be read as {} instead of the text that is inside the cell? Also why does this occur on some documents but not others even though they use the same encoding?
An example of what is in a cell is (NOTE-the comma symbol won't print here):
þITEM_IDþþBEGDOCþþENDDOCþþBEGATTþþENDATTþþPARENT_ATTACHMENTþþATTACHMENT_BATESþþ etc.
EDIT Here is my code:
public partial class ThisAddIn
{
Office.CommandBarButton toolbarCommand;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Office.CommandBar toolbar = Application.CommandBars.Add("My Toolbar",Office.MsoBarPosition.msoBarTop,false,true);
toolbarCommand = (Office.CommandBarButton)
toolbar.Controls.Add(
Office.MsoControlType.msoControlButton,
missing,
missing,
missing,
true);
toolbarCommand.Caption = "Toolbar Button";
toolbarCommand.FaceId = 59;
toolbarCommand.Click += new Office._CommandBarButtonEvents_ClickEventHandler(toolbarCommand_Click);
toolbar.Visible = true;
}
void toolbarCommand_Click(Office.CommandBarButton Ctrl, ref bool CancelDefault)
{
Excel.Worksheet activeWorksheet = ((Excel.Worksheet)Application.ActiveSheet);
try
{
IterateRows(activeWorksheet);
}
catch(Exception e)
{
MessageBox.Show(e.ToString());
}
Ctrl.Click+=new Office._CommandBarButtonEvents_ClickEventHandler(toolbarCommand_Click);
}
public void IterateRows(Excel.Worksheet worksheet)
{
//Get the used Range
Excel.Range usedRange = worksheet.UsedRange;
Excel.Worksheet activeWorksheet = ((Excel.Worksheet)Application.ActiveSheet);
//Iterate the rows in the used range
if (usedRange.Rows.Count > 1)
{
foreach (Excel.Range row in usedRange.Rows)
{
//MessageBox.Show(row.Text);
char quote = (char)254;
string data = row.Text;
row.Columns[1] = data.Replace(quote, '"').Replace((char)20, ',');
row.TextToColumns(Type.Missing, Excel.XlTextParsingType.xlDelimited, Excel.XlTextQualifier.xlTextQualifierDoubleQuote, Type.Missing, Type.Missing, Type.Missing, true);
}
}
}

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.

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