Set value of cell in add in - c#

I will explain everything that I have found but in short I just want to set the value of cell A1 in code behind. I have tried Create Excel Add-in - get cell value plus other links and all those techniques work only if I run them on a macro but I want execute them from a function.
So let me start explaining:
I do not understand why I get a wierd behaviour when I run the same code on a function versus a Sub. Take the following example:
Dim TimeToRun
Sub Macro1()
'Dim addIn As COMAddIn
'Dim automationObject As Object
'Set addIn = Application.COMAddIns("ProbeAddIn")
'Set automationObject = addIn.Object
'automationObject.ImportData
MsgBox "Hello world"
End Sub
Sub Macro2()
TimeToRun = Now + TimeValue("00:00:01")
Application.OnTime TimeToRun, "Macro1" ' run Macro1 on 1 seconds
End Sub
Function Test()
TimeToRun = Now + TimeValue("00:00:01")
Application.OnTime TimeToRun, "Macro1" ' run Macro1 on 1 seconds
End Function
Note that Macro2 and function Test have the same code. Why is it that if I run Macro2 it works ok (message box shows up). But if I go to a a cell and type =Test() then I get an error even though I have the same code !
The reason why am I showing this example is because the code that I have commented out on Macro1 works great if I run it by running the macro directly. If I place that code inside the function Test it does not work. That code is executing the following method in my add-in project on visual studio:
The exception is:
System.Runtime.InteropServices.COMException was unhandled by user code
HResult=-2146827284 Message=Exception from HRESULT: 0x800A03EC
Source="" ErrorCode=-2146827284 StackTrace:
at System.RuntimeType.ForwardCallToInvokeMember(String memberName, BindingFlags flags, Object target, Int32[] aWrapperTypes,
MessageData& msgData)
at Microsoft.Office.Interop.Excel.Range.set_Value2(Object value)
at ReadWrite.ImportData() in C:\Users\Antonio\Dropbox_Temp\visual studio\Probe add in
test\ProbeAddIn\ProbeAddIn\Class1.cs:line 82 InnerException:
I got that exception because I ran:
Function Test()
Dim addIn As COMAddIn
Dim automationObject As Object
Set addIn = Application.COMAddIns("ProbeAddIn")
Set automationObject = addIn.Object
automationObject.ImportData
End Function
Instead of
Sub Test()
Dim addIn As COMAddIn
Dim automationObject As Object
Set addIn = Application.COMAddIns("ProbeAddIn")
Set automationObject = addIn.Object
automationObject.ImportData
End Function
How can I make the code inside a macro and function run the same way? I want to trigger the execution of that method when user types in a formula not when the user runs a macro. Even if I have the function run the macro I get the same exception The only way I do not get an exception is if the first thing that runs is a macro...

Not sure, but the issue you have may be due to the fact that when your function is fired the Excel application is in edit mode and so cannot accept any input from outside hence the exception when you try to insert data.
If you want to update a cell in a continuous way in a clean manner what you need is a RTD server.
From your cells you will simply call :
=RTD("my.super.rtdserver",,"mydata")
From time to time your RTD server will notify Excel of new data and lets Excel call-back to retrieve them when it is ready.

Related

MS Project - Disable resource

I am creating a Microsoft Project add-in. Depending on the value of a resource's custom text column (let's say Text30), can I disable a resource from showing up when trying to assign it to a task?
So for example, if a row has a Text30 value of Inactive, it shouldn't be available to the Project file user to assign it to a task, but it should still be in the Resource Sheet. Below, row 1 would be available as a resource, and row 2 wouldn't show up when assigning resources to a task.
With the help of Rachel's linked question I managed to find a workaround. I added a Text column called Active Status (Text30 in my case). Then, using Application.ProjectBeforeTaskChange I checked if the new value is a resource that has the column Text30 of value Active. Here is the link to the MS Project event. Below is the code:
Imports Microsoft.Office.Interop.MSProject
Private Sub Application_ProjectBeforeTaskChange(tsk As Task, Field As PjField, NewVal As Object, ByRef Cancel As Boolean) Handles Application.ProjectBeforeTaskChange
CheckResourceValidity(NewVal, Cancel)
End Sub
Private Sub CheckResourceValidity(NewVal As Object, ByRef Cancel As Boolean)
Dim res As Resource
Dim newValList = NewVal.Split(",")
If Not IsStartup(newValList) Then
For Each splitNewVal In newValList
' currentProject is the currently active Project
res = currentProject.Resources.Item(splitNewVal)
If splitNewVal = res.Name Then
If res.Text30 <> "Active" Then
MessageBox.Show("You are trying to assign an inactive resource to a task. Please choose an active resource.",
"Assigning inactive resource", MessageBoxButtons.OK, MessageBoxIcon.Error)
Cancel = True
End If
End If
Next
End If
End Sub
' The event ProjectBeforeTaskChange is called when a .mpp file with data inside is opened
Private Function IsStartup(newValList As Object) As Boolean
Dim res As Resource
For Each x In newValList
Try
res = GlobalVariables.currentProject.ProjResources.Item(x)
Catch ex As System.Runtime.InteropServices.COMException
Return True
End Try
Next
Return False
End Function
The program will not allow the user to add a resource to a task if the resource doesn't have a Text30 value of 'Active'.
ATTENTION:
This event will trigger only if you assign the resource through the Gantt Chart. If you assign through the Resource tab -> Assign Resource, the event won't be triggered. The event ProjectBeforeAssignmentNew will be triggered through the Assign Resource and through the Gantt Chart. The only downside is that I didn't find a way to access the value being changed through the ProjectBeforeAssignmentNew event. You can find the event here.

Show Access Report in dialog

I want to show a Access Report without showing the MainForm from Access. I want to show it in a Dialog or something like that. I can show a report but with opening the Main Window of Access:
_access.DoCmd.OpenReport(
"myReport", //ReportName
AcView.acViewReport, //View
System.Reflection.Missing.Value,
"my Where Condition"
);
But this opens Access all the time. I only want to see my Report in a Dialog. Even when I set the AcWindowMode.acDialog property it opens Access behind it.
I also know that I can save it as PDF and open this. But this is not possible in my case.
Is there a way to show a Access Report in a Dialog without showing (or hiding) the Access Main Window behind it?
You can do it this way:
First, in the access application, set the interface to tabbed. and un-check show tabs.
Now write a code stub in a standard code module in Access.
Say like this:
Sub RunMyReport(s As String)
DoCmd.ShowToolbar "Ribbon", acToolbarNo
' now launch report
DoCmd.OpenReport "qryHotels", acViewReport
End Sub
Now, your .net code will look like this
(I not even bothering with interop - it don't help much).
You have this .net code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim objAccess As Object = CreateObject("Access.Application")
objAccess.OpenCurrentDatabase("C:\test\test44.accdb")
objAccess.Run("RunMyReport", "qryHotels")
objAccess.Visible = True
End Sub
And the results will now look like this:
note the vb form that launched the form.
Note the Access report,
it shows no ribbon or background interface.
And note that we get to pass the report name to the given routine.
Now you likely could move the VBA code of this:
DoCmd.ShowToolbar "Ribbon", acToolbarNo
' now launch report
DoCmd.OpenReport "qryHotels", acViewReport
To the .net side, and not even have to call a VBA routine.
Dim objAccess As Object = CreateObject("Access.Application")
objAccess.OpenCurrentDatabase("C:\test\test44.accdb")
objAccess.DoCmd.ShowToolbar("Ribbon", 2) ' acToolbarNo = 2
objAccess.DoCmd.OpenReport("qryHotels", 5) ' acViewReport = 5
objAccess.Visible = True
So, in fact we don't need the VBA helper function, and we can open any report. The command to hide the ribbon works, and the other settings (hide nav pane, and tabbed interface is to be set in the access application).
So above should work. Given it only a few lines of vb.net code, then as c#, it should be easy to convert. Eg this should work:
{
var objAccess = Interaction.CreateObject("Access.Application");
objAccess.OpenCurrentDatabase(#"C:\test\test44.accdb");
objAccess.DoCmd.ShowToolbar("Ribbon", 2); // acToolbarNo = 2
objAccess.DoCmd.OpenReport("qryHotels", 5); // acViewReport = 5
objAccess.Visible = true;
}
You can consider the inter-op assemblies, as that can help in intel-sense, but above is written without any references to office or use of the inter-op assemblies.

Can't run macro via interop if it's in a worksheet?

I'm using the following code to run a VBA macro via C# Excel interop:
public void macroTest()
{
Excel.Application xlApp = new Excel.Application();
xlApp.Visible = true;
string bkPath = #"C:\somePath\someBk.xlsm";
Excel.Workbook bk = xlApp.Workbooks.Open(bkPath);
string bkName = bk.Name;
string macroName = "testThisMacro_m";
string runString = "'" + bkName + "'!"+macroName;
xlApp.Run(runString);
bk.Close(false);
xlApp.Quit();
}
testThisMacro_m is in a module testMacro, and this runs successfully. When I replace it with:
string macroName = "testThisMacro_s";
where testThisMacro_s has its code in Sheet1, the xlApp.Run() line gives the following COM Exception:
Cannot run the macro ''someBk.xlsm'!testThisMacro_s'.
The macro may not be available in this workbook or all macros may be disabled.
I checked macro security settings, and they are indeed set to "Disable with notification", but being able to run a macro from a module and not from a worksheet seems to indicate that this is a different issue than application-level macro security.
Is there something different that I have to do when making an interop call to a macro in a worksheet?
UPDATE: I was able to get the macro to execute by changing the call to:
string macroName = "Sheet1.testThisMacro_s"
but it seems that this hands control back to C# before the macro completes, so now I need to figure out how to check for macro completion (probably a different question).
A Worksheet object is an object - and objects are defined with class modules. Worksheets, workbooks, user forms; they're all objects. And you can't just call a method on an object, if you don't have an instance of that object.
Macros work off standard modules, which aren't objects, and don't need to be instantiated.
Application.Run can't call methods of an object, that's why macros need to be in standard modules.
I was able to get the macro to execute by changing the call to:
string macroName = "Sheet1.testThisMacro_s"
Wouldn't a helper sub solve both of your problems, re: Mat's Mug's reply concerning instantiation?
In some standard module:
Sub testHelperSubToBeCalledFromInterop
Call Sheet1.testThisMacro_s
End Sub
EDIT:

Screenshot of VBA editor in debug mode in terminal, or use Excel DOM to find error line and desc in VBA editor?

An Excel workbook is called by a command line, which itself is launched from c# (3.5). The workbook runs, but there is an error in the VBA. For example, a column is missing in a pivot and Excel prompts the user with an error message, with the option to "debug".
From the process in C#, we can detect that the error window is open, and we take a screenshot of the error message, and then we close the error box.
If this was an interactive session, Excel would then present the VBA editor, in debug mode, with the line where the error occurred highlighted.
However, because this is running in an unattended terminal session, we are then unable to take a screenshot of the VBA editor (the screenshot is a black screen).
We are then able to close the excel program, using the windows PID.
The questions is: how do we either get a screenshot of the VBA editor, or how do we bind (with COM or interop) with the Excel in debug mode, and traverse the dom to find the error line, and possibly error message?
(1) If you have authoring control of the Excel workbook(s), you can insert line numbers and a error handler to write to file Err.Number, Err.Description, Err.Source, Erl.
(2) If (1) is not an option, but you can set Macro Security on the host to allow programmatic control of the Visual Basic project, then you can get the active line number:
Dim xl As excel.Application
Dim StartLine As Long, StartColumn As Long, EndLine As Long, EndColumn As Long
Set xl = GetObject(, "Excel.Application")
xl.VBE.ActiveCodePane.GetSelection StartLine, StartColumn, EndLine, EndColumn
Debug.Print StartLine, StartColumn, EndLine, EndColumn
(3) If neither (1) nor (2) is an option, it is a bit hairy, but you can copy out the bitmap contents of the window: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183402%28v=vs.85%29.aspx
The simplest solution if you have access to modify the VBA code might be to use On Error GoTo and a line label. This will stop the Debug message from popping up and instead, when a runtime error occurs, execution will jump to the specified label at which point you can access the information available in the global Err object. VBA does not have the concept of a try/catch block so this is pretty much the nearest proximally.
Sub Main()
On Error GoTo Catch
' Code that may trigger errors here...
GoTo Finally
Catch:
' Log Err info, etc...
Debug.Print "Error " & Err.Number & " - " & Err.Description
Finally:
' This is always reached last (error or no error)
End Sub
Snapshot of data structures, VBA excel
It sounds a lot like the question above in which I suggested to write the state of your code to a separate txt file (in the example, using a FileSystemObject) where you can afterwards analyze where you got stuck:
booleans on conditions / instantiation of objects, number on succesful loops, values contained in variables, well - whatever you want...).
In combination with an error handler (as suggested before) you can see where the code stopped + code / description of the error.
A copy-paste for your sake:
dim sFile As string
Dim FSO As FileSystemObject
Dim FSOFile As TextStream
sFile = "U:/Log.txt"
Set FSO = New FileSystemObject
Set FSOFile = FSO.OpenTextFile(sFile, 2, True)
FSOFile.writeline (<Descriptions+Property/variable value>)
FSOFile.Close set FSO = nothing
I agree that it is a more work, but you'll know where to find the bug.
All depends on how hard and often you need this, in combination with how long the code is that you need to describe. Since I am not aware of your situation, I cannot judge on that.
In the end, it seems a lot of work, but actually it's quite easy since it's just describing the code that is already there.
Hope it helps.

Writing to a cell turns on ScreenUpdating in my VSTO add-in

I have a weird problem where Excel is behaving differently on my development machine and a testing machine.
In my add-in, I've turned off ScreenUpdating in several places for long running processes. On my machine this works fine. On the testing machine, Excel sets ScreenUpdating = true as soon as I write to a cell.
The following code demonstrates the issue for me.
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Microsoft.Office.Interop.Excel.Application excel = Globals.ThisAddIn.Application;
MessageBox.Show(excel.ScreenUpdating.ToString());
excel.ScreenUpdating = false;
MessageBox.Show(excel.ScreenUpdating.ToString());
Workbook workbook = Globals.ThisAddIn.Application.ActiveWorkbook;
Worksheet w = (Worksheet)workbook.Worksheets[1];
((Range)w.Cells[1, 1]).Value = "Test";
MessageBox.Show(excel.ScreenUpdating.ToString());
}
On my machine, opening Excel gives three message boxes saying
"True", "False", "False".
On the test machine they say
"True", "False" and "True".
I've also stepped through with a remote debugger and watched the ScreenUpdating property change immediately after the cell value is set. Further, this isn't the only thing that resets ScreenUpdating. Adding or removing a Worksheet or Workbook will also do this.
The Excel version on each system is the same (14.0.6112.5000 (32-bit)).
What could be causing this? How can I fix it so that Excel respects my settings?
I am experincing the same problem with ScreenUpdating (and other settings) being reset to true by the Bloomberg add-in with VSTO add-in. Their support are working on a solution but in the meanwhile the following solution works ok:
By hiding each sheet when interacting with its cells the screen won't update and you'll get the performance benefits. Make sure to not try to hide the last sheet as it will raise an error. Not sure if this is suitable for your project but it is a workable solution for mine.
Here is some example code in VB.NET to hide a sheet:
' Create a book with a single worksheet
Dim Book As Excel.Workbook
Book = Globals.ThisAddIn.Application.Workbooks.Add(Excel.XlWBATemplate.xlWBATWorksheet)
'% Create sheet variable
Dim Sheet As Excel.Worksheet
Sheet = TryCast(Book.ActiveSheet, Excel.Worksheet)
' Visible
Sheet.Visible = Excel.XlSheetVisibility.xlSheetVisible
' Hidden but user able to show
Sheet.Visible = Excel.XlSheetVisibility.xlSheetHidden
' Hidden for user as well
Sheet.Visible = Excel.XlSheetVisibility.xlSheetVeryHidden
Other addins in Excel can interfere with that single global setting.
It is for that reason you are supposed to save the current ScreenUpdating state to a local variable before, and restore it after, each use.
Ignore the changing of that setting in the ThisAddIn_Startup event (as you would not normally do your work there anyway).
If an add-in is interfering with your ScreenUpdating somehow, you can stop them form doing it by disabling events temporarily. using EnableEvents. It is possible that this will break the functionality of that addon, but it worked fine for my needs.

Categories