Ask Excel to execute C# code - c#

Is it possible to ask Excel to start a C# method?
How would you implement such a call in Excel?
(i.e. instead of programming in VB, I would like to program in C#)
I can imagine using a VB-macro to start a C# application in the background but maybe you know a nicer way?
For example, the C#-code shall be executed upon a click in a particular Excel cell.

Well you could open a program via VBA. This VBA script gets called by clicking on the Excel-Cell:
var Path = "MYPROGRAMPATH"
var Argument = "MYARGUMENT"
x = Shell("""" & Path & """ """ & Argument & """", vbNormalFocus)
To react on a cell change, use the following event:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'YOUR CODE
End Sub
Then program your C# application and let it determine the arguments.
Your program should react according to the filtered arguments.
This can be done with the Environment.GetCommandLineArgs-Method.
public static void Main()
{
Console.WriteLine();
// Invoke this sample with an arbitrary set of command line arguments.
String[] arguments = Environment.GetCommandLineArgs();
Console.WriteLine("GetCommandLineArgs: {0}", String.Join(", ", arguments));
//Handling of arguments here, switch-case, if-else, ...
}

I've found a solution to the VBA macro:
Sub Button1_Click()
Dim pathStr As String
Dim argumentStr As String
pathStr = "C:/Users/.../Desktop/temp/Trial02.exe"
argumentStr = "Any argument string"
Call Shell("""" & pathStr & """ """ & argumentStr & """", vbNormalFocus)
End Sub

Related

How to handle events from embedded Excel.OleObjects or Excel.Shapes

I'm working on C# and now VB.NET ports of an old VBA program. It has lots of MSForms/OleObjects embedded in it like CommandButton or even images.
My first thought was to declare all the buttons as Microsoft.Vbe.Interop.Forms.CommandButtonbut that leads to a COM exception that the System._COM type can't be cast to ...Forms.CommandButton. If I try a more generic version of this solution, I don't find any items, and if I try to go through all VBComponets I note that they are all the sheets in the workbook, but none of the controls:
foreach (VBComponent xxx in Globals.ThisWorkbook.VBProject.VBComponents) {
Interaction.MsgBox(xxx.Name);
Interaction.MsgBox(xxx.ToString);
}
Thus all of these controls are not in .VBComponets, but I can find them as OLEobjects in thisworkbook.worksheets(n).OLEobjects (this is counterintutive to me, but I probably don't understand the system to begin with).
How do I handle the Click action from such an object?
I'm assuming that I need to be using the Excel.OLEObjectEvents_Event interface, but I can't seem to figure out how. If I try to make custom events with delegates, I don't seem to be able to assign them to OleObjects. If I use ActionClickEventHandler.CreateDelegate I can get a huge variety of errors that makes me think that's a dead end.
The official documentation from MS doesn't seem that helpful, though it did introduce me to the idea of Verb, which I'm looking into. So far that has only produced COM errors along the lines of "Application Failed to start."
Even just trying to use one of the two standard events, .GotFocus, I always pull a 0x80040200 error.
Example:
Excel.OLEObject ButtonCatcher = Globals.ThisWorkbook.Worksheets(1).OLEObjects("CommandButton1");
ButtonCatcher.GotFocus += CommandButton1_Click;
Throws a COMException Exception from HRESULT: 0x80040200 at the second line. The button is enabled, which is I checked after looking up the code number from the office dev site.
Trying a more generic approach within the code for a sheet containing controls:
object CommandButtonStart = this.GetType().InvokeMember("CommandButton1", System.Reflection.BindingFlags.GetProperty, null, this, null);
Throws a Missing Method error.
Any help is greatly appreciated, this seems like this should be obvious and I'm missing it.
**Edit: I have also found that I can cast these controls into Excel.Shape but that doesn't actually get me any closer to running a function or sub from the VSTO. I'm playing with Excel.Shape.OnAction but this requires a VBA sub to be called. Presumably, I could call a VBA sub which calls a sub from the VSTO as long as the VSTO was COM visible. This seems really round-about and I'd only like to do it as a last resort.
Solution Type: VSTO Document-Level
Scenario:
1.) Excel.Worksheet created at run-time. (not a Worksheet Host Item)
2.) Add a button on the Worksheet at run-time that triggers C# code when clicked.
Assembly References:
Microsoft.Vbe.Interop (Microsoft.Vbe.Interop.dll)
Microsoft.Vbe.Interop.Forms (Microsoft.Vbe.Interop.Forms.dll)
Microsoft.VisualBasic (Microsoft.VisualBasic.dll)
Tested / Working Code:
using MSForms = Microsoft.Vbe.Interop.Forms;
using System.Windows.Forms;
...
Microsoft.Vbe.Interop.Forms.CommandButton CmdBtn;
private void CreateOLEButton()
{
Excel.Worksheet ws = Globals.ThisWorkbook.Application.Sheets["MyWorksheet"];
// insert button shape
Excel.Shape cmdButton = ws.Shapes.AddOLEObject("Forms.CommandButton.1", Type.Missing, false, false, Type.Missing, Type.Missing, Type.Missing, 500, 5, 100, 60);
cmdButton.Name = "btnButton";
// bind it and wire it up
CmdBtn = (Microsoft.Vbe.Interop.Forms.CommandButton)Microsoft.VisualBasic.CompilerServices.NewLateBinding.LateGet(ws, null, "btnButton", new object[0], null, null, null);
CmdBtn.Caption = "Click me!";
CmdBtn.Click += new MSForms.CommandButtonEvents_ClickEventHandler(ExecuteCmd_Click);
}
private void ExecuteCmd_Click()
{
MessageBox.Show("Click");
}
Have you tried using NewLateBinding.LateGet?
using MSForms = Microsoft.Vbe.Interop.Forms;
using Microsoft.VisualBasic.CompilerServices;
...
MSForms.CommandButton CommandButton1 = (MSForms.CommandButton)NewLateBinding.LateGet(Globals.ThisWorkbook.Worksheets(1), null, "CommandButton1", new object[0], null, null, null);
CommandButton1.Click += new Microsoft.Vbe.Interop.Forms.CommandButtonEvents_ClickEventHandler(CommandButton1_Click);
It's referenced on MSDN in the VSTO forums and in an old blog post.
Can you programmatically add code to a CodeModule in the Workbook, like this?
Private Sub CommonButton_Click(ByVal buttonName As String)
MsgBox "You clicked button [" & buttonName & "]"
End Sub
Private Sub CreateEventHandler(ByVal buttonName As String)
Dim VBComp As VBIDE.VBComponent
Dim CodeMod As VBIDE.CodeModule
Dim codeText As String
Dim LineNum As Long
Set VBComp = ThisWorkbook.VBProject.VBComponents(Me.CodeName)
Set CodeMod = VBComp.CodeModule
LineNum = CodeMod.CountOfLines + 1
codeText = codeText & "Private Sub " & buttonName & "_Click()" & vbCrLf
codeText = codeText & " Dim buttonName As String" & vbCrLf
codeText = codeText & " buttonName = """ & buttonName & "" & vbCrLf
codeText = codeText & " CommonButton_Click buttonName" & vbCrLf
codeText = codeText & "End Sub"
CodeMod.InsertLines LineNum, codeText
End Sub
Use the interop forms toolkit. It is free from Microsoft. It provides com wrappers and event messenger class that communicates the event data to and from .NET to VBA. I have used it to handle control events from VBA in .NET, and events from .NET to VBA. You would use the interop
http://www.codeproject.com/Articles/15690/VB-C-Interop-Form-Toolkit.
From the toolkit ducoumentation:
Interop UserControls provide a basic set of intrinsic events (Click, GotFocus, Validate, etc.) in Visual Basic 6.0. You can define your own custom events in Visual Studio .NET and raise them using RaiseEvent. In order to handle the events in Visual Basic 6.0, you need to add a project reference and add a WithEvents variable declaration in your code, and then handle the events using the WithEvents variable rather than the control itself.
How To Handle Interop UserControl Custom Events
1.
In Visual Basic 6.0, on the Project menu, click References. Note that there is already a reference to ControlNameCtl, where ControlName is the name of your Interop UserControl.
2.
In the Available References list, locate a reference for ControlName and check it, and then click OK.
3.
In the Code Editor, add a declaration for a WithEvents variable:
Dim WithEvents ControlNameEvents As ControlLibrary.ControlName
In the Form_Load event handler, add the following code to initialize the WithEvents variable:
Private Sub Form_Load()
Set ControlNameEvents = Me.ControlNameOnVB6Form
End Sub
Add code to handle your custom event in the WithEvents variable's event handler:
Private Sub ControlNameEvents_MyCustomEvent()
MsgBox("My custom event fired")
End Sub
Thanks Leo Gurdian
In VB.Net look like this:
Assembly References:
Microsoft.Vbe.Interop (Microsoft.Vbe.Interop.dll)
Microsoft.Vbe.Interop.Forms (Microsoft.Vbe.Interop.Forms.dll)
Microsoft.VisualBasic (Microsoft.VisualBasic.dll)
Imports Microsoft.Office.Interop.Excel
Imports Microsoft.VisualBasic
Imports MSForms = Microsoft.Vbe.Interop.Forms
Private Sub CreateOLEButton()
Dim ws As Excel.Worksheet = (CType(Application.ActiveSheet, Excel.Worksheet))
Dim cmdButton As Excel.Shape = ws.Shapes.AddOLEObject("Forms.CommandButton.1", Type.Missing, False, False, Type.Missing, Type.Missing, Type.Missing, 500, 5, 100, 60)
cmdButton.Name = "btnButton"
Dim CmdBtn As Microsoft.Vbe.Interop.Forms.CommandButton = CType(Microsoft.VisualBasic.CompilerServices.NewLateBinding.LateGet(ws, Nothing, "btnButton", New Object(-1) {}, Nothing, Nothing, Nothing), Microsoft.Vbe.Interop.Forms.CommandButton)
CmdBtn.Caption = "Click me!"
AddHandler CmdBtn.Click, AddressOf ExecuteCmd_Click
End Sub
Private Sub ExecuteCmd_Click()
'do something
End Sub

Control NAO with a remote control

I would like to control a NAO robot with a remote control by using C#.
There is only an example in Python on the Aldebaran website.
Unfortunately, I haven't learnt Python.
Could anyone help me convert this Python example into C#?
The following links may be helpful to you.
https://community.aldebaran-robotics.com/doc/1-14/naoqi/sensors/alinfrared-tuto.html#alinfrared-tuto
https://community.aldebaran-robotics.com/doc/1-14/naoqi/sensors/alinfrared-api.html
# -*- encoding: UTF-8 -*-
"""
Receive and display remote buttons event:
"""
import naoqi
import time
# create python module
class myModule(naoqi.ALModule):
def pythondatachanged(self, strVarName, value, strMessage):
"""callback when data change"""
print "Data changed on", strVarName, ": ", value, " ", strMessage
# call method
try:
lircProxy = naoqi.ALProxy("ALInfrared")
lircProxy.initReception(repeatThreshold = 10)
pythonModule = myModule("pythonModule")
memProxy = naoqi.ALProxy("ALMemory")
memProxy.subscribeToEvent("InfraRedRemoteKeyReceived", "pythonModule",
"pythondatachanged")
except Exception, e:
print "error"
print e
exit(1)
time.sleep(10)
exit(0)

Process not working when called from a web service VB.NET

I'm writing a program that moves docs from one app to another in ApplicationXtender (AX). The AX full-featured client already has such a program (Migration Wizard) that can handle the task, so I created a function that launches it using Process.Start() and supplying the arguments necessary to automate it. When I call the function from a console app or windows form app, the Migration Wizard works perfectly. But the process must be initiated by an event in a web-based workflow project, so I wrote a web service that contains the same function, and then I use an invoke web service control in the workflow to start it. When I consume the function from the web service, the process doesn't complete. I can see it hanging in the task manager. I'm pretty sure it has to do with the user settings in IIS, but I'm not familiar enough with IIS to make any significant difference. I've configured the anonymous authentication's user identity in IIS to launch with a specific user with full rights to AX, and set the DefaultAppPool to run as Local System, but neither worked. I'm thinking I may need to impersonate the user, but I don't know how to do that. Any suggestions?
For reference, here is my code:
Consume Service Code-
Sub Main()
Dim dbName As String
Dim appName As String
Dim preSalesNum As String
Console.WriteLine("Database: ")
dbName = Console.ReadLine
Console.WriteLine("")
Console.WriteLine("Application")
appName = Console.ReadLine
Console.WriteLine("")
Console.WriteLine("Pre-Sales Number:")
preSalesNum = Console.ReadLine
Console.WriteLine("")
MoveDocs.MoveDocs(dbName, appName, preSalesNum)
End Sub
MoveDocs Function (inside of a separate class)-
Public Shared Function MoveDocs(ByVal dbName As String, ByVal appName As String,
ByVal preSalesNum As String) As String
Try
Dim sourceApp As String
If appName = "PRE_SALES_PROJECTS" Then
sourceApp = "PROJECTS"
Else
sourceApp = "LOOSE-FURNITURE"
End If
Dim argsString As String = "/SD " & dbName & " /SU username /SP password /SA
" & appName & " /DD " & dbName & " /DU username /DP password /DA " &
sourceApp & " /S " & """" & preSalesNum & """" & " /A"
Dim procProp As New System.Diagnostics.ProcessStartInfo
With procProp
.FileName = "C:\Program Files (x86)\XtenderSolutions\Content
Management\MigrateWiz32.exe"
.Arguments = argsString
End With
Dim proc As System.Diagnostics.Process =
System.Diagnostics.Process.Start(procProp)
Return argsString
Catch ex As Exception
Return ex.ToString()
End Try
End Function
The MoveDocs() function in the service.asmx file is identical to the one above, minus the 'shared' modifier in the declaration. The app works, the web service doesn't.
ProcessStartInfo has properties for username, password and domain. There's more info on MSDN -
http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.password.aspx

How to execute VBScript command within textbox from C# application?

How could I execute VBScript's code from TextBox control placed in C# application?
like , let's assume that I have a C# Windows Application(form)
and has two controls!
textbox (txtCode)
button (btnExecute)
txtCode has VBScript code
and I want when clicking at btnExecute to execute the VBScript code!!
You can pass VBS/JS directly to the scripting runtime & pass code & objects around.
Add a ref to the Microsoft Scripting Control (COM) then you can;
MSScriptControl.ScriptControl sc = new MSScriptControl.ScriptControl();
object[] anyParams = { "Bob"};
string expr = #"function foo(arg)
dim x: x = ""Hello "" & arg
msgbox x
foo = 12345
End function";
sc.Language = "VBScript";
sc.AddCode(expr);
object result = sc.Run("foo", ref anyParams);
//also
sc.Reset();
result = sc.Eval("1 + 2 / 3 + abs(-99)");
You will need to use WebBrowser Control to do that.
And since i don't think you can inject VBScript code in the loaded page. So what you can do is Create a Temp .html page, save your TextBox's script in it and then Load it in the the WebBrowser Control.
Couldn't you simply write it to a file and execute it using the default Windows behavior or something like that? It's the only way I can think of.
Check out this two articles:
http://msdn.microsoft.com/en-gb/magazine/cc301954.aspx
http://msdn.microsoft.com/en-us/library/ms974577.aspx

How to backup a database in c# through code?

For example, I deleted a record on a table on the database and my database is MS Aaccess. Any backup mechanisms that I can refer to? So that when I need a rollback of the database I just restore it quickly from code.
MS Access is the file based database, right? in my understanding, that means, when the connection is closed and the file is not in use, you can copy that file to another location.
Here I assume the application has such privileges on the file system.
Also, I agree with Morten Martner's answer, if the database type is MS SQL Server, then you will definitely need SMO library use.
I'm using the following code to backup SQL server databases:
using System;
using System.Collections.Generic;
using System.Data;
using System.Collections;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
using System.Text;
namespace Codeworks.SqlServer.BackupDatabase
{
public class BackupCore
{
public static void Execute( string instance, string database, string outputFile )
{
BackupDeviceItem bdi = new BackupDeviceItem( outputFile, DeviceType.File );
Backup bu = new Backup();
bu.Database = database;
bu.Devices.Add( bdi );
bu.Initialize = true;
// add percent complete and complete event handlers
bu.PercentComplete += new PercentCompleteEventHandler(Backup_PercentComplete);
bu.Complete +=new ServerMessageEventHandler(Backup_Complete);
Server server = new Server( instance );
bu.SqlBackup( server );
}
protected static void Backup_PercentComplete( object sender, PercentCompleteEventArgs e )
{
// Console.WriteLine( e.Percent + "% processed." );
}
protected static void Backup_Complete( object sender, ServerMessageEventArgs e )
{
Console.WriteLine( e.ToString() );
}
}
}
You'll need the management libraries from MS for the correct SQL server version, but those are available for download.
If you're a single user of your database, you just need to close your connection and copy it with the file system.
If there are multiple users, then you should use a different method. If you actually have Access available, there's an undocumented command that will make a backup of the tables a Jet/ACE file:
Application.SaveAsText 6, vbNullString, strTargetMDB
Now, since this can only be done with the database open in the Access UI, it requires automating Access and operating on the CurrentDB. Here's an implementation that runs within Access:
Public Function CreateBackup(strMDBName As String, strBackupPath As String, _
Optional bolCompact As Boolean = False) As Boolean
On Error GoTo errHandler
Dim objAccess As Object
Dim strBackupMDB As String
Dim strCompactMDB As String
If Len(Dir(strBackupPath & "\*.*")) = 0 Then ' alternative: use File System Object for this
MkDir strBackupPath
End If
Set objAccess = New Access.Application
objAccess.Application.OpenCurrentDatabase strMDBName
strBackupMDB = "Backup" & Format(Now(), "YYYYMMDDhhnnss") & ".mdb"
Debug.Print strBackupPath & "\" & strBackupMDB
objAccess.Application.SaveAsText 6, vbNullString, strBackupPath & "\" & strBackupMDB
objAccess.Application.Quit
Set objAccess = Nothing
If bolCompact Then
strCompactMDB = strBackupPath & "\" & "c_" & strBackupMDB
Name strBackupPath & "\" & strBackupMDB As strCompactMDB
DBEngine.CompactDatabase strCompactMDB, strBackupPath & "\" & strBackupMDB
Kill strCompactMDB
End If
CreateBackup = (Len(Dir(strBackupPath & "\" & strBackupMDB)) > 0)
exitRoutine:
If Not (objAccess Is Nothing) Then
On Error Resume Next
objAccess.Application.Quit
On Error GoTo 0
Set objAccess = Nothing
End If
Exit Function
errHandler:
Select Case Err.Number
Case 75 ' Path/File access error -- tried to MkDir a folder that already exists
Resume Next
Case Else
MsgBox Err.Number & ": " & Err.Description, vbExclamation, "Error in CreateBackup()"
Resume exitRoutine
End Select
End Function
To run that from C# you'd have to automate Access, and you likely don't want a dependency on Access.
Since I work in Access exclusively, that's the method I use, so I've never programmed the more complicated methods.
If you have exclusive access to the database, you could use JRO CompactDatabase command to compact to a new filename, but if you have exclusive access, you can also use the file system.
So, basically, you've got choices about how to export the data tables to a backup database. You could use DoCmd.TransferDatabase to copy all the data tables, and then copy the relationships, or you could create an empty template database and append the data from each table in turn to a copy of the template (in an order that won't violate RI, of course).
Neither of those sounds anything but messy to me, and that's why I use the SaveAsText method! But if I wasn't running Access, the other two alternatives would be worth doing.

Categories

Resources