11

我正在使用 Visual Studio 2010 创建一个 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

安装的加载项在单击快捷方式时显示错误。它说找不到特定的宏。Sub EnableShortCut()当它在 excel vba 模块中时,下面的代码运行良好。将其添加到使用 Visual Studio 创建的 Excel 加载项时,同样不会起作用。有人请帮我解决这个问题。

4

4 回答 4

9

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

它很棘手,并且要在没有任何外部依赖的情况下使用键盘挂钩来使用 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 AddIn 中将其连接起来:

Private Sub ThisAddIn_Startup() Handles Me.Startup

'enable keyboard intercepts
KeyboardHooking.SetHook()

完成后不要忘记禁用它:

Private Sub ThisAddIn_Shutdown() Handles Me.Shutdown

'disable keyboard intercepts
KeyboardHooking.ReleaseHook()
于 2012-04-21T07:53:13.050 回答
3

使用Excel-DNA(我开发的一个开源 .NET/Excel 集成库),您的 .NET 代码中的方法和用户定义函数通过 C API 注册到 Excel。因此,该行为更接近 VBA 的行为,并且您的带有 Application.OnKey "..." 字符串的代码也可以工作。

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

制作插件

  1. 将代码保存为名为OnKeyTest.dna的文本文件,然后
  2. 将其与CodePlex 上发布的 Excel-DNA 宿主库 ExcelDna.xll 的副本相匹配,您只需将其复制并重命名为OnKeyTest.xll(匹配的名称是加载 .xll 时如何找到 .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>
于 2012-04-22T07:00:05.123 回答
2

这很难做到,Application.OnKey() 方法非常受限。它只能调用宏,不能传递任何参数。这意味着您必须提供一组宏。您不需要特定于工作簿的那些,您需要适用于任何文档的宏。让我们先解决这个问题。

  • 启动 Excel 并使用 File + Close 关闭默认工作簿
  • 单击录制宏。将“将宏存储在”设置更改为个人宏工作簿
  • 确定关闭对话框并单击停止录制
  • 单击 Visual Basic。请注意,您现在有一个名为 PERSONAL.XLSB 的 VBA 项目,其中包含 Module1

删除 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

根据需要经常重复,您需要为每个要定义的快捷键设置一个。单击保存。您现在在 c:\users\yourname\appdata\roaming\microsoft\excel\xlstart\personal.xlsb 中创建了一个包含宏的文件。任何打算使用您的扩展程序的人也需要拥有此文件,即部署详细信息。

接下来你需要做的是公开你的命令。您在插件中编写的 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 语句来实现基于index值的命令。还要注意与全局宏中的代码的匹配。

您还需要做一件事,您必须明确地公开这个类。覆盖插件中的 RequestComAddInAutomationService 函数。我用来测试的那个看起来像这样:

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 得到这个:

在此处输入图像描述

于 2012-04-21T00:49:36.787 回答
0

首先将 A1、A2、A3 视为单元地址。

  1. 创建 .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 中创建一个 Excel 工作簿项目并添加现有文件并选择上面创建的 .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 将宏名称作为参数。

在此处查看我在 Word 中使用快捷键的答案

于 2012-04-20T16:19:33.627 回答