如何在Visual Studio 2010创build的Excel加载项中执行.Onkey事件?

我正在使用Visual Studio 2010创build一个Excel加载项。我想在用户单击一组键时运行一些代码。

这是我得到的代码

Public Class CC Private Sub ThisAddIn_Startup() Handles Me.Startup EnableShortCut() End Sub Sub A1() MsgBox "A1" End Sub Sub A2() MsgBox "A2" End Sub Sub A3() MsgBox "A3" End Sub Public Sub EnableShortCut() With Application .OnKey "+^{U}", "A1" 'action A1 should be performed when user clicks Ctrl + Shift + U .OnKey "+^{L}", "A2" 'action A2 should be performed when user clicks Ctrl + Shift + L .OnKey "+^{P}", "A3" 'action A3 should be performed when user clicks Ctrl + Shift + P End With End Sub End Class 

安装的加载项在点击快捷方式时显示错误。 它说特定的macros不能被发现。 Sub EnableShortCut()下的代码在excel vba模块中运行良好。 将其添加到使用Visual Studio创build的Excel加载项中时,这同样不起作用。 有一个在那里请帮我解决这个问题。

“我想在用户按下组合键时运行一些代码。”

它的棘手,没有任何外部的依赖性,它会通过VSTO Excel加载项来实现:

 Imports System Imports System.Runtime.CompilerServices Imports System.Runtime.InteropServices Imports System.Windows.Forms Friend Class KeyboardHooking ' Methods <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _ Private Shared Function CallNextHookEx(ByVal hhk As IntPtr, ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr End Function <DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _ Private Shared Function GetModuleHandle(ByVal lpModuleName As String) As IntPtr End Function Private Shared Function HookCallback(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer If ((nCode >= 0) AndAlso (nCode = 0)) Then Dim keyData As Keys = DirectCast(CInt(wParam), Keys) If (((BindingFunctions.IsKeyDown(Keys.ControlKey) AndAlso BindingFunctions.IsKeyDown(Keys.ShiftKey)) AndAlso BindingFunctions.IsKeyDown(keyData)) AndAlso (keyData = Keys.D7)) Then 'DO SOMETHING HERE End If If ((BindingFunctions.IsKeyDown(Keys.ControlKey) AndAlso BindingFunctions.IsKeyDown(keyData)) AndAlso (keyData = Keys.D7)) Then 'DO SOMETHING HERE End If End If Return CInt(KeyboardHooking.CallNextHookEx(KeyboardHooking._hookID, nCode, wParam, lParam)) End Function Public Shared Sub ReleaseHook() KeyboardHooking.UnhookWindowsHookEx(KeyboardHooking._hookID) End Sub Public Shared Sub SetHook() KeyboardHooking._hookID = KeyboardHooking.SetWindowsHookEx(2, KeyboardHooking._proc, IntPtr.Zero, Convert.ToUInt32(AppDomain.GetCurrentThreadId)) End Sub <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _ Private Shared Function SetWindowsHookEx(ByVal idHook As Integer, ByVal lpfn As LowLevelKeyboardProc, ByVal hMod As IntPtr, ByVal dwThreadId As UInt32) As IntPtr End Function <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _ Private Shared Function UnhookWindowsHookEx(ByVal hhk As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean End Function ' Fields Private Shared _hookID As IntPtr = IntPtr.Zero Private Shared _proc As LowLevelKeyboardProc = New LowLevelKeyboardProc(AddressOf KeyboardHooking.HookCallback) Private Const WH_KEYBOARD As Integer = 2 Private Const WH_KEYBOARD_LL As Integer = 13 Private Const WM_KEYDOWN As Integer = &H100 ' Nested Types Public Delegate Function LowLevelKeyboardProc(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer End Class Public Class BindingFunctions ' Methods <DllImport("user32.dll")> _ Private Shared Function GetKeyState(ByVal nVirtKey As Integer) As Short End Function Public Shared Function IsKeyDown(ByVal keys As Keys) As Boolean Return ((BindingFunctions.GetKeyState(CInt(keys)) And &H8000) = &H8000) End Function End Class 

C#版本 – 上面的vb.net代码是从原来的转换 – 但我不得不使用Reflector作为CodeConverter&devfusion没有做到这一点。

 class KeyboardHooking { [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr GetModuleHandle(string lpModuleName); public delegate int LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam); private static LowLevelKeyboardProc _proc = HookCallback; private static IntPtr _hookID = IntPtr.Zero; //declare the mouse hook constant. //For other hook types, you can obtain these values from Winuser.h in the Microsoft SDK. private const int WH_KEYBOARD = 2; // mouse private const int HC_ACTION = 0; private const int WH_KEYBOARD_LL = 13; // keyboard private const int WM_KEYDOWN = 0x0100; public static void SetHook() { // Ignore this compiler warning, as SetWindowsHookEx doesn't work with ManagedThreadId #pragma warning disable 618 _hookID = SetWindowsHookEx(WH_KEYBOARD, _proc, IntPtr.Zero, (uint)AppDomain.GetCurrentThreadId()); #pragma warning restore 618 } public static void ReleaseHook() { UnhookWindowsHookEx(_hookID); } //Note that the custom code goes in this method the rest of the class stays the same. //It will trap if BOTH keys are pressed down. private static int HookCallback(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode < 0) { return (int)CallNextHookEx(_hookID, nCode, wParam, lParam); } else { if (nCode == HC_ACTION) { Keys keyData = (Keys)wParam; // CTRL + SHIFT + 7 if ((BindingFunctions.IsKeyDown(Keys.ControlKey) == true) && (BindingFunctions.IsKeyDown(Keys.ShiftKey) == true) && (BindingFunctions.IsKeyDown(keyData) == true) && (keyData == Keys.D7)) { // DO SOMETHING HERE } // CTRL + 7 if ((BindingFunctions.IsKeyDown(Keys.ControlKey) == true) && (BindingFunctions.IsKeyDown(keyData) == true) && (keyData == Keys.D7)) { // DO SOMETHING HERE } } return (int)CallNextHookEx(_hookID, nCode, wParam, lParam); } } } public class BindingFunctions { [DllImport("user32.dll")] static extern short GetKeyState(int nVirtKey); public static bool IsKeyDown(Keys keys) { return (GetKeyState((int)keys) & 0x8000) == 0x8000; } } 

在上面的代码中,您需要将代码放在HookCallback()方法中,以在按下组合键时捕获事件,我已经给出了两个示例Ctrl + Shift + 7和Ctrl + 7来帮助您。

然后在你的Excel中添加连线:

 Private Sub ThisAddIn_Startup() Handles Me.Startup 'enable keyboard intercepts KeyboardHooking.SetHook() 

并且不要忘记当你完成时禁用它:

 Private Sub ThisAddIn_Shutdown() Handles Me.Shutdown 'disable keyboard intercepts KeyboardHooking.ReleaseHook() 

这很难做到,Application.OnKey()方法非常受限制。 它只能调用一个macros,不能传递任何参数。 这意味着你将不得不提供一组macros。 你不需要特定的工作簿,你需要在任何文档中工作的macros。 我们先来解决。

  • 启动Excel并使用文件+closures以closures默认工作簿
  • 点击录制macros。 将“存储macros”设置更改为个人macros工作簿
  • 单击确定closures对话框,然后单击停止录制
  • 单击Visual Basic。 请注意,您现在已经有了一个名为PERSONAL.XLSB且带有Module1的VBA项目

删除Macro1并复制/粘贴这个VBA代码:

 Sub MyAddinCommand1() Application.COMAddIns("ExcelAddin1").Object.Command 1 End Sub Sub MyAddinCommand2() Application.COMAddIns("ExcelAddin1").Object.Command 2 End Sub Sub MyAddinCommand3() Application.COMAddIns("ExcelAddin1").Object.Command 3 End Sub 

根据需要重复一次,您需要为每个要定义的快捷键添加一个。 点击保存。 您现在在包含macros的c:\ users \ yourname \ appdata \ roaming \ microsoft \ excel \ xlstart \ personal.xlsb中创build了一个文件。 任何将要使用你的扩展的人都需要有这个文件,一个部署细节。

接下来你需要做的是公开你的命令。 您在插件中编写的Sub A1()不会执行,这些方法需要作为COM可见类的方法公开。 添加一个新的类到您的项目,并使代码如下所示:

 Imports System.Runtime.InteropServices <InterfaceType(Runtime.InteropServices.ComInterfaceType.InterfaceIsIDispatch)> _ <ComVisible(True)> _ Public Interface IMyAddinCommand Sub Command(ByVal index As Integer) End Interface <ClassInterface(Runtime.InteropServices.ClassInterfaceType.None)> _ <ComVisible(True)> _ Public Class MyAddinCommand Implements IMyAddinCommand Public Sub Command(index As Integer) Implements IMyAddinCommand.Command MsgBox("Command #" + CStr(index)) End Sub End Class 

只是一个简单的暴露一个名为Command()的方法,它需要一个整数。 我只是使用MsgBox,你会想写一个Select语句来实现基于索引值的命令。 还要注意与全局macros中的代码匹配。

还有一件事你需要做,你必须明确地暴露这个类。 重载插件中的RequestComAddInAutomationService函数。 我用来testing这个的看起来像这样:

 Public Class ThisAddIn Private Sub ThisAddIn_Startup() Handles Me.Startup Application.OnKey("+^{U}", "Personal.xlsb!MyAddinCommand1") '' Ctrl + Shift + U End Sub Protected Overrides Function RequestComAddInAutomationService() As Object If commands Is Nothing Then commands = New MyAddinCommand Return commands End Function Private commands As MyAddinCommand End Class 

按F5编译并启动Excel。 当我按Ctrl + Shift + UI得到这个:

在这里输入图像描述

使用Excel-DNA (我开发的一个开源的.NET / Excel集成库),.NET代码中的方法和用户定义函数通过C API注册到Excel中。 因此,行为更接近于VBA的行为,并且您的代码与Application.OnKey“…”string也将工作。

Excel-DNA允许您的代码位于已编译的.NET .dll程序集中,或直接作为加载加载项时处理的“.dna”文件中的文本。 这是一个这样的文本文件的例子(代码看起来是相同的,如果它在一个编译的项目)。 正如在其他答案中提到的,我已经重命名macros,所以他们的名字不会与单元格名称A1等冲突

做一个加载项

  1. 将代码保存为名为OnKeyTest.dna的文本文件
  2. 将它与从CodePlex上的发行版的Excel-DNA主库库ExcelDna.xll相匹配,您只需将其复制并重命名为OnKeyTest.xll (匹配的名称是加载.xll时如何find.dna文件) 。

这两个文件将形成完整的加载项,只需要在机器上运行.NET。

 <DnaLibrary Language="VB" RuntimeVersion="v2.0" > <![CDATA[ Imports ExcelDna.Integration Public Class MyAddIn Implements IExcelAddIn Private Sub AutoOpen() Implements IExcelAddIn.AutoOpen EnableShortCut() End Sub Private Sub AutoClose() Implements IExcelAddIn.AutoClose End Sub Sub EnableShortCut() With ExcelDnaUtil.Application .OnKey("+^{U}", "MacroA1") 'action A1 should be performed when user clicks Ctrl + Shift + U .OnKey("+^{L}", "MacroA2") 'action A2 should be performed when user clicks Ctrl + Shift + L .OnKey("+^{P}", "MacroA3") 'action A3 should be performed when user clicks Ctrl + Shift + P End With End Sub End Class Public Module MyMacros Sub MacroA1() MsgBox("A1") End Sub Sub MacroA2() MsgBox("A2") End Sub Sub MacroA3() MsgBox("A3") End Sub End Module ]]> </DnaLibrary> 

首先把A1,A2,A3作为单元格地址。

  1. 创build.xlsm文件并添加这些VBA代码

     Sub AOne() MsgBox "Message from AOne" End Sub Sub ATwo() MsgBox "Message from ATwo" End Sub Sub AThree() MsgBox "Message from AThree" End Sub 
  2. 现在在Visual Studio中创build一个Excel工作簿项目并添加现有文件并select上面创build的.xlsm文件

     private void ThisWorkbook_Startup(object sender, System.EventArgs e) { EnableShortCut(); } private void ThisWorkbook_Shutdown(object sender, System.EventArgs e) { } public void EnableShortCut() { Excel.Application app = Globals.ThisWorkbook.Application; app.OnKey("+^{U}", "AOne"); //action A1 should be performed when user clicks Ctrl + Shift + U app.OnKey("+^{L}", "ATwo");//action A2 should be performed when user clicks Ctrl + Shift + L app.OnKey("+^{P}", "AThree"); //action A3 should be performed when user clicks Ctrl + Shift + P } 

运行您的项目应该工作,Excel中的Application.OnKey或Word中的Application.Keybindings将macros名称作为参数。

检查我在Word中使用快捷键的答案