public static CustomTaskPane CreateCustomTaskPane(Type userControlType, string title, object parent) { //if (!typeof(System.Windows.Forms.UserControl).IsAssignableFrom(userControlType)) //{ // throw new ArgumentException("userControlType for Custom Task Pane must be derive from type System.Windows.Forms.UserControl"); //} // I could use the ProgId and ClsId of the UserControl type here. // But then the reigstration has to be persistent or coordinated, which I dislike. // It's already a problem for the RTD servers. // Users that want persistent and consistent names, can sort out registration themselves, or use the ExcelComClass support. // Then the CTP is created through the CreateCustomTaskPane("My.ProgId",...) overloads. // So when passed the type, I always synthesize a progid. // We pick a new Guid as ClassId for this add-in... Guid clsId = Guid.NewGuid(); // ...and make the ProgId from this Guid - max 39 chars. string progId = "CtpSrv." + clsId.ToString("N"); // Register UserControl // (could probably get away with RegistrationServices.RegisterTypeForComClients instead of our own ClassFactoryRegistration class) using (ProgIdRegistration progIdReg = new ProgIdRegistration(progId, clsId)) using (ClsIdRegistration clsIdReg = new ClsIdRegistration(clsId, progId)) using (ClassFactoryRegistration cf = new ClassFactoryRegistration(userControlType, clsId)) { return CreateCustomTaskPane(progId, title, parent); } }
// Forwarded from XlCall public static object RTD(string progId, string server, params string[] topics) { // Check if this is any of our business. if (!string.IsNullOrEmpty(server) || !registeredRtdServerTypes.ContainsKey(progId)) { // Just pass on to Excel. return CallRTD(progId, null, topics); } // Check if already loaded. if (loadedRtdServers.ContainsKey(progId)) { // Call Excel using the synthetic RtdSrv.xxx (or actual from attribute) ProgId return CallRTD(loadedRtdServers[progId], null, topics); } // Not loaded already - need to get the Rtd server loaded // TODO: Need to reconsider registration here..... // Sometimes need stable ProgIds. Type rtdServerType = registeredRtdServerTypes[progId]; object rtdServer = Activator.CreateInstance(rtdServerType); RtdServerWrapper rtdServerWrapper = new RtdServerWrapper(rtdServer, progId); // We pick a new Guid as ClassId for this add-in... CLSID clsId = Guid.NewGuid(); // ... but take a stable ProgId from the XllPath and type's FullName - max 39 chars. // ... (bad idea - this will cause Excel to try to load this RTD server while it is not registered.) // Guid typeGuid = GetGuidFromString(DnaLibrary.XllPath + ":" + rtdServerType.FullName); // string progIdRegistered = "RtdSrv." + typeGuid.ToString("N"); // by making a fresh progId, we are sure Excel will try to load when we are ready. string progIdRegistered = "RtdSrv." + clsId.ToString("N"); Debug.Print("RTD - Using ProgId: {0} for type: {1}", progIdRegistered, rtdServerType.FullName); // Mark as loaded - ServerTerminate in the wrapper will remove. // TODO: Consider multithread race condition... loadedRtdServers[progId] = progIdRegistered; using (SingletonClassFactoryRegistration regClassFactory = new SingletonClassFactoryRegistration(rtdServerWrapper, clsId)) using (ProgIdRegistration regProgId = new ProgIdRegistration(progIdRegistered, clsId)) using (ClsIdRegistration regClsId = new ClsIdRegistration(clsId, progIdRegistered)) { return CallRTD(progIdRegistered, null, topics); } }
public static void LoadComAddIn(ExcelComAddIn addIn) { // We pick a new Guid as ClassId for this add-in. CLSID clsId = Guid.NewGuid(); // and make the ProgId from this Guid - max 39 chars.... string progId = "AddIn." + clsId.ToString("N"); addIn.SetProgId(progId); // Put together some nicer descriptions for the Add-ins dialog. string friendlyName; if (addIn is ExcelRibbon) friendlyName = addIn.DnaLibrary.Name + " (Ribbon Helper)"; else if (addIn is ExcelCustomTaskPaneAddIn) friendlyName = addIn.DnaLibrary.Name + " (Custom Task Pane Helper)"; else friendlyName = addIn.DnaLibrary.Name + " (COM Add-in Helper)"; string description = string.Format("Dynamically created COM Add-in to load custom UI for the Excel Add-in {0}, located at {1}.", addIn.DnaLibrary.Name, DnaLibrary.XllPath); try { Debug.Print("Getting Application object"); object app = ExcelDnaUtil.Application; Type appType = app.GetType(); Debug.Print("Got Application object: " + app.GetType().ToString()); CultureInfo ci = new CultureInfo(1033); object excelComAddIns; object comAddIn; using (SingletonClassFactoryRegistration regClassFactory = new SingletonClassFactoryRegistration(addIn, clsId)) using (ProgIdRegistration regProgId = new ProgIdRegistration(progId, clsId)) using (ComAddInRegistration regComAddIn = new ComAddInRegistration(progId, friendlyName, description)) { excelComAddIns = appType.InvokeMember("COMAddIns", BindingFlags.GetProperty, null, app, null, ci); // Debug.Print("Got COMAddins object: " + excelComAddIns.GetType().ToString()); appType.InvokeMember("Update", BindingFlags.InvokeMethod, null, excelComAddIns, null, ci); // Debug.Print("Updated COMAddins object with AddIn registered"); comAddIn = excelComAddIns.GetType().InvokeMember("Item", BindingFlags.InvokeMethod, null, excelComAddIns, new object[] { progId }, ci); // Debug.Print("Got the COMAddin object: " + comAddIn.GetType().ToString()); // At this point Excel knows how to load our add-in by CLSID, so we could clean up the // registry aggressively, before the actual (dangerous?) loading starts. // But this seems to lead to some distress - Excel has some assertion checked when // it updates the LoadBehavior after a successful load.... comAddIn.GetType().InvokeMember("Connect", BindingFlags.SetProperty, null, comAddIn, new object[] { true }, ci); // Debug.Print("COMAddin is loaded."); loadedComAddIns.Add(comAddIn); } } catch (Exception e) { Debug.Print("LoadComAddIn exception: " + e.ToString()); // CONSIDER: How to deal with errors here...? For now I just re-raise the exception. throw; } }