应我老板的要求,我创建了一小组脚本,用于定期监控某些设备和进程的状态。随后使用相对复杂的VBA模块处理此信息,该模块收集所有信息,应用公式,设置范围并生成图表等。
但是有两个问题: 我是一名业余程序员,所以我的VBA例程效率很低。这对我来说不是一个大问题(我只是在跑步时起床喝咖啡),但对于其他用户来说,这可能很麻烦,因为不知道为什么这么长时间。我想要一个进度的图形表示。 应用程序的配置是通过文本文件完成的。我想提供一个合适的GUI来设置配置。
为了实现这一目标,我正在寻找一种让我的C#WinForms应用程序与我的VBA应用程序通信的方法。我知道如何从我的C#应用程序运行VBA例程,但我不知道如何让它们实时进行操作。
以下是我特别想要实现的两件事:
我已经考虑过通过标准输出进行通信。我知道如何使用C#/ .Net读取标准输出,但我不确定如何使用VBA写入StdOut流。
我相信很多人会指出我想要达到的目标是愚蠢和老式(或完全没必要),但作为业余程序员,对我来说这似乎是一个非常有挑战性和有趣的项目,可以教我是一两件事。
答案 0 :(得分:9)
创建一个C#COM-visible类非常简单,并且(正如您在评论中所说)很有趣。这是一个小样本。
在新的C#Library项目中,添加:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace CSharpCom
{
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
//The 3 GUIDs in this file need to be unique for your COM object.
//Generate new ones using Tools->Create GUID in VS2010
[Guid("18C66A75-5CA4-4555-991D-7115DB857F7A")]
public interface ICSharpCom
{
string Format(string FormatString, [Optional]object arg0, [Optional]object arg1, [Optional]object arg2, [Optional]object arg3);
void ShowMessageBox(string SomeText);
}
//TODO: Change me!
[Guid("5D338F6F-A028-41CA-9054-18752D14B1BB")] //Change this
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
interface ICSharpComEvents
{
//Add event definitions here. Add [DispId(1..n)] attributes
//before each event declaration.
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(ICSharpComEvents))]
//TODO: Change me!
[Guid("C17C5EAD-AA14-464E-AD32-E9521AC17134")]
public sealed class CSharpCom : ICSharpCom
{
public string Format(string FormatString, [Optional]object arg0, [Optional]object arg1, [Optional]object arg2, [Optional]object arg3)
{
return string.Format(FormatString, arg0, arg1, arg2, arg3);
}
public void ShowMessageBox(string SomeText)
{
MessageBox.Show(SomeText);
}
}
}
您需要进入项目属性,进入“签名”选项卡,选中框以对程序集进行签名,并创建一个新的“强名称密钥文件”。这有助于防止已注册的DLL出现版本问题。
编译它,并在Visual Studio命令提示符中使用regasm
注册DLL。您将使用32或64位regasm,具体取决于您使用的Office版本...您将在C:\windows\Microsoft.NET\Framework
或C:\windows\Microsoft.NET\Framework64
(分别为32位和64位)中找到RegAsm:
C:\windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe /codebase /tlb CSharpCom.dll
这将在Windows中注册您的DLL,因此现在在VBA编辑器中,您应该能够转到Tools-> References并找到您的COM命名空间“CSharpCom”。选中该框,现在您应该能够在VBA中创建COM对象:
Sub TestCom()
Dim c As CSharpCom.CSharpCom
Set c = New CSharpCom.CSharpCom
c.ShowMessageBox c.Format("{0} {1}!", "Hello", "World")
End Sub
您可以向COM对象添加表单,并在VBA调用特定方法时打开它们;您应该可以使用它来创建带有进度条的表单。但要考虑的一件事是VBA是单线程的。这意味着在代码运行时其他所有内容都会被冻结,所以事情可能会有点棘手。
在我的脑海中,您可以在COM-visible类中创建3个方法:一个用于打开表单,一个用于更新进度(在VBA调用update()方法之后立即调用VBA的DoEvents()方法您的COM对象,以允许Office处理屏幕更新),以及一个关闭它。你应该在表单上调用Dispose();这可以在“关闭”方法中完成,但我认为如果您的VBA崩溃,它可能会导致内存泄漏/问题,并且您的close方法永远不会被调用 - 只需要考虑其他事项。
答案 1 :(得分:5)
您可以将.NET DLL与VB6项目一起使用,并创建在程序集中定义的任何对象的实例。这是通过创建类型库文件(.tlb)为COM interop(在VB6中使用)注册.NET程序集(.dll)来完成的。 .tlb文件包含额外信息,以便您的VB6项目可以使用.dll。请注意,您的VB6项目不会引用.dll而是相应的.tlb文件。您可以使用Regasm.exe实用程序生成并注册类型库并注册托管程序集的位置。然后它只是创建一个.NET对象的实例,无论是WinForm还是其他一些很酷的类。 =)。
Dim MyDotNetObject As MyDotNetClass
Set MyDotNetObject = New MyDotNetClass
MyDotNetObject.SomeMethod(value1, value2)
''end of execution
Set MyDotNetObject = Nothing
请参阅:http://support.microsoft.com/default.aspx?scid=kb;en-us;817248
答案 2 :(得分:4)
我相信此链接中的代码大致符合您的要求:
C#代码创建一个COM对象(在示例中它是IE,但是根据您的描述,您已经设法创建了VBA例程的实例)
它将.Net事件处理程序(使用+ =)附加到COM对象引发的事件(标题更改时)
它定义了样本事件处理代码
http://msdn.microsoft.com/en-us/library/66ahbe6y.aspx
我不自称理解这段代码,我的目标是表明这个解决方案会错综复杂!你应该只用一个解决方案来构建它。一个完整的VBA解决方案将更快开发(你只需要学习简单的VBA表格),但旧技术。一个完整的C#解决方案开发速度会慢一些,但是你会学习C#。
性能实际上是一个问题。如果它现在执行效率低下,当你有5倍的记录需要处理时会发生什么?你应该在之前解决性能问题,你得到那么多记录。
答案 3 :(得分:0)
这个问题的其他答案涉及调用外部方法的 VBA 宏。但是有一个更简单的方法。您的 GUI 应用程序可以附加到工作簿 SheetSelectionChange
事件或工作表 Change
事件,这将在任何值更改时触发。
因为您可能不想减慢一切,我建议添加一个额外的工作表,添加命名范围来描述每个值的含义,然后挂钩工作表更改事件以在这些值更改时进行拦截并更新您的 GUI .