I think I have found a final solution for this. By simply "NETLOAD"-ing this dll, all ribbon tooltips with a HelpTopic (which must be set programmatically) starting with "http://" will open your default internet browser with that address when you press F1. All other F1 keypresses work as normal.
It works by creating a hook procedure to catch all messages to AutoCAD. Also two event handlers help detect if a tooltip is showing at the moment. If that triggers a browser being opened for help, we filter away the message AutoCAD sends to itself to open AutoCAD default help.
Below is the code, in one cs file. Compile this to AutoCadRibbonHelp.dll.
I would appreciate feedback on this solution. Are there any dangers I am overlooking? Deadlocks because of the hook, memory corruption etc.?
// (C) Copyright 2014 by Microsoft
//
using System;
using System.Runtime.InteropServices;
using System.Windows;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.EditorInput;
// This line is not mandatory, but improves loading performances
[assembly: ExtensionApplication(typeof(AutoCadRibbonHelp.RibbonHelpPlugin))]
namespace AutoCadRibbonHelp
{
public class RibbonHelpPlugin : IExtensionApplication
{
private static bool dropNextHelpCall = false; // Flag to tell if the next message from AutoCAD to display it's own help should be ignored
private static string currentTooltip = null; // If not null, this is the HelpTopic of the currently open tooltip. If null, no tooltip is displaying.
// Import declarations for AutoCAD API functions
[DllImport("accore.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, EntryPoint = "?acedRegisterFilterWinMsg@@YAHQ6AHPEAUtagMSG@@@Z@Z")]
private static extern int acedRegisterFilterWinMsg(WindowHookProc callBackFunc);
[DllImport("accore.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, EntryPoint = "?acedRemoveFilterWinMsg@@YAHQ6AHPEAUtagMSG@@@Z@Z")]
private static extern int acedRemoveFilterWinMsg(WindowHookProc callBackFunc);
// Windows hook declarations
public enum WndMsg
{
WM_ACAD_HELP = 0x4D,
WM_KEYDOWN = 0x100,
}
public enum WndKey
{
VK_F1 = 0x70,
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int WindowHookProc(ref System.Windows.Forms.Message msg);
private static WindowHookProc callBackFunc = null;
private static int WindowsHook(ref System.Windows.Forms.Message msg)
{
if (msg.Msg == (int)WndMsg.WM_KEYDOWN)
{
if ((int)msg.WParam == (int)WndKey.VK_F1)
{ // F1 pressed
if (currentTooltip != null && currentTooltip.Length > 7 && currentTooltip.Substring(0, 7) == "http://")
{ // Another implementation could be to look up the help topic in an index file matching it to URLs.
dropNextHelpCall = true; // Even though we dont' forward this F1 keypress, AutoCAD sends a message to itself to open the AutoCAD help file
object nomutt = Autodesk.AutoCAD.ApplicationServices.Application.GetSystemVariable("NOMUTT");
string cmd = "._BROWSER " + currentTooltip + " _NOMUTT " + nomutt.ToString() + " ";
Autodesk.AutoCAD.ApplicationServices.Application.SetSystemVariable("NOMUTT", 1);
Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.SendStringToExecute(cmd, true, false, false);
return 1;
}
}
return 0;
}
else if (msg.Msg == (int)WndMsg.WM_ACAD_HELP && dropNextHelpCall)
{ // Seems this is the message AutoCAD generates itself to open the help file. Drop this if help was called from a ribbon tooltip.
dropNextHelpCall = false; // Reset state of help calls
return 1; // Stop this message from being passed on to AutoCAD
}
return 0;
}
// AutoCAD event handlers to detect if a tooltip is open or not
private static void ComponentManager_ToolTipOpened(object sender, EventArgs e)
{
Autodesk.Internal.Windows.ToolTip tt = sender as Autodesk.Internal.Windows.ToolTip;
if (tt == null)
return;
Autodesk.Windows.RibbonToolTip rtt = tt.Content as Autodesk.Windows.RibbonToolTip;
if (rtt == null)
currentTooltip = tt.HelpTopic;
else
currentTooltip = rtt.HelpTopic;
}
private static void ComponentManager_ToolTipClosed(object sender, EventArgs e)
{
currentTooltip = null;
}
void IExtensionApplication.Initialize()
{
callBackFunc = new WindowHookProc(WindowsHook);
if (acedRegisterFilterWinMsg(callBackFunc) != 0)
{
Autodesk.Windows.ComponentManager.ToolTipOpened += new EventHandler(ComponentManager_ToolTipOpened);
Autodesk.Windows.ComponentManager.ToolTipClosed += new EventHandler(ComponentManager_ToolTipClosed);
}
}
void IExtensionApplication.Terminate()
{
acedRemoveFilterWinMsg(callBackFunc);
Autodesk.Windows.ComponentManager.ToolTipOpened -= ComponentManager_ToolTipOpened;
Autodesk.Windows.ComponentManager.ToolTipClosed -= ComponentManager_ToolTipClosed;
}
}
}