I normally advise against mixing VBA and C# in the same project. The reason is that debugging can become difficult and complicated. However, there are a few cases where the object model design built on top of legacy VBA constructs leaves you no other option but to mix a little VBA into your project.
Key Binding is one such example. Short of building your own Key Binder using Windows API calls, which has it’s own drawbacks upon performance, the next best thing is to build a callback model where the keystroke is captured via a VBA macro and the macro then makes a call back into your C# project.
In this example, we will be building upon my previous post on exposing methods using the RequestComAddInAutomationService callback. First, you need to create a Word Template (dotm) file, add a VBA project to it (press ALT+F11 to open the VB Editor), and then insert a new Module (from the Insert menu, click Module.) Here is the code you will need to add to the template:
Function GetAddin() As Object
On Error Resume NextDim addIn As COMAddIn
Dim automationObject As Object
Set addIn = Application.COMAddIns(“dttWordKeyBindingPOC”)
Set automationObject = addIn.Object
Set GetAddin = automationObject
End FunctionPublic Sub KeyCode1()
On Error Resume Next
GetAddin.CallKey 1
End SubPublic Sub KeyCode2()
On Error Resume Next
GetAddin.CallKey 2
End SubPublic Sub KeyCode3()
On Error Resume Next
GetAddin.CallKey 3
End SubPublic Sub KeyCode4()
On Error Resume Next
GetAddin.CallKey 4
End SubPublic Sub KeyCode5()
On Error Resume Next
GetAddin.CallKey 5
End Sub
In the above code, we have a function called GetAddin that returns a reference to the C# DLL add-in we will build below. We then have 5 methods that simply return a number to the add-in when they are executed. Nothing else is needed at this point, simply save this to the install folder, or the DEBUG folder of your add-in as “KeyCodes.dotm.”
NOTE: We are not registering the key codes in the Word Template. Instead, we will be registering them in the add-in.
Now we need to create the Word add-in. Here we will generate a standard add-in, but as discussed above, expose it so that the VBA code above can find and connect to it.
First, we create an interface:
/// <summary> /// INTERFACE - for the Key Code callback /// </summary> [ComVisible(true)] public interface IKeys { void CallKey(int i); }
Next, we expose our class, attach the interface, and then register the keys (comments inline):
/// <summary> /// ADDIN - use IKeys Intrface and make /// COM Visible /// </summary> [ComVisible(true)] [ClassInterface(ClassInterfaceType.None)] public partial class ThisAddIn : IKeys { Word.AddIn addinKeys; /// <summary> /// Method is required to allow the VBA code /// to hook to our class here and get the exposed /// interface method CallKey() /// </summary> /// <returns></returns> protected override object RequestComAddInAutomationService() { return this; } /// <summary> /// EXPOSED METHOD - VBA will call this when a /// macro from an assigned key is called /// </summary> /// <param name="i"></param> public void CallKey(int i) { switch (i) { case 1: MessageBox.Show("You pressed CTRL+ALT+D"); break; case 2: MessageBox.Show("You pressed CTRL+ALT+Y"); break; } } /// <summary> /// STARTUP - get the path to our VBA add-in, verify that /// it is valid and then hook up the keys /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ThisAddIn_Startup(object sender, System.EventArgs e) { // load the KeysAddin //get the full location of the assembly string fullPath = AppDomain.CurrentDomain.BaseDirectory + "addins\\KeyCodes.dotm"; // NOTE: There is no need to load the add-in // having the fuull path and verifying it is // all you need... if (!new FileInfo(fullPath).Exists) return; // we failed - do not hook keys // laod the template addinKeys = Application.AddIns.Add(fullPath); addinKeys.Installed = true; // wire up our keys object cContext = Application.CustomizationContext; Application.CustomizationContext = Application.Templates[fullPath]; Application.KeyBindings.Add(Word.WdKeyCategory.wdKeyCategoryCommand, "KeyCode1", Application.BuildKeyCode(Word.WdKey.wdKeyControl, Word.WdKey.wdKeyAlt, Word.WdKey.wdKeyD)); Application.KeyBindings.Add(Word.WdKeyCategory.wdKeyCategoryCommand, "KeyCode2", Application.BuildKeyCode(Word.WdKey.wdKeyControl, Word.WdKey.wdKeyAlt, Word.WdKey.wdKeyY)); Application.CustomizationContext = cContext; // what happens here is the ADDIN is loaded first to // expose the KeyCode functions in the addin // there are (5) in the adding KeyCode# // we can add more if needed, but this give an example // of the fact that those macros in the add-in are generic // and in no way are tied to the respective key code that // is assigned below. // NOTE: on unload of this we MUST unload the add-in // make sure the customization - keys - does not dirty the file // otherwise on close the user will get prompted to save this // template - this line avoid that... Application.Documents[addinKeys.Name].Saved = true; } /// <summary> /// SHUTDOWN - clean up /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ThisAddIn_Shutdown(object sender, System.EventArgs e) { // remove the add-in addinKeys.Installed = false; addinKeys.Delete(); }
And that is all there is to it. In the above example, CTRL+ALT+D and CTRL+ALT+Y are now bound. When you launch Word, the C# add-in will load the Word template, from the same folder a the DLL and then register each KeyCode. In the template we created above you can register up to 5 codes, but you can easily modify that to add more.