I have a VBA project for Microsoft Office Outlook, which I'd like to rewrite as an Outlook Add-in with the help of NetOffice.
Here's a piece of VBA code which I'd like to transfer:
Dim objNS As Outlook.NameSpace
Set objNS = Application.GetNamespace("MAPI")
Set m_colCalendarItems = objNS.GetDefaultFolder(olFolderCalendar).Items
Application represents the running Outlook application.
My respective code in NetOffice looks like this:
Outlook.Application objApp = Outlook.Application.GetActiveInstance();
Outlook._NameSpace objNS = (Outlook._NameSpace)objApp.GetNamespace("MAPI");
m_colCalendarItems = (Outlook.Items)objNS.GetDefaultFolder(OlDefaultFolders.olFolderCalendar).Items;
Quite a lot of casts, surely this can be handled better. But the main problem is that I don't get a reference to the running application in the first line (objApp is null). Although this code is in the Addin_OnStartupComplete routine.
Any tips on how to set this up better?
I found the solution. The code snippet I postet is running in a class method. It is called from the Addin_OnStartupComplete in the Addin class (Auto-generated by the NetOffice Developer Toolbox).
I can get a reference to the running application: It's the Application property of the Addin class. I can provide this to the called method:
public class Addin : Outlook.Tools.COMAddin // this was auto-generated by the NetOffice Developer Toolbox
{
FolderEvents m_folderevents = new FolderEvents(); // 'FolderEvents' is my class
// additional auto-generated code removed
private void Addin_OnStartupComplete(ref Array custom)
{
m_folderevents.InitFolders(this.Application);
}
}
Related
I've exhausted every resource possible and can not figure out what the issue is. Button images won't show & keep getting this message when I try to use the command.
Failed to initialize the [add-in name] because the assembly [path to an add-in DLL file] does not exist
when launching Revit. Here's my code that I'm using.
#region Namespaces
using Autodesk.Revit.ApplicationServices;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.CSharp;
using System.Media;
using System.Reflection;
using System.IO.Packaging;
using System.Windows.Media.Imaging;
using System.Drawing.Imaging;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Autodesk.Revit.UI.Selection;
using Autodesk.Revit.DB.Architecture;
#endregion
namespace TpMechanical
{
internal class App : IExternalApplication
{
public Result OnStartup(UIControlledApplication a)
{
String tabname = "TpMechanical";
String panelname = "Tools";
//Option 1
BitmapImage b1Image = (System.Windows.Media.Imaging.BitmapImage)TpMechanical.Properties.Resources.ResourceManager.GetObject("_design3_fhY_icon.ico");
BitmapImage b2Image = (System.Windows.Media.Imaging.BitmapImage)TpMechanical.Properties.Resources.ResourceManager.GetObject("_design3_fhY_icon.ico");
BitmapImage b3Image = (System.Windows.Media.Imaging.BitmapImage)TpMechanical.Properties.Resources.ResourceManager.GetObject("_design3_fhY_icon.ico");
//Option 2
//Bitmap b1Image = (System.Drawing.Bitmap)(TpMechanical.Properties.Resources.ResourceManager.GetObject("Icon1.ico"));
//Bitmap b2Image = (System.Drawing.Bitmap)(TpMechanical.Properties.Resources.ResourceManager.GetObject("Image1.jpg"));
//Bitmap b3Image = (System.Drawing.Bitmap)(TpMechanical.Properties.Resources.ResourceManager.GetObject("Image2.bmp"));
//Option 3
//BitmapImage b1Image = new BitmapImage(new Uri("pack:application:,,,/TpMechanical/Resources/Icon1.ico"));
//BitmapImage b2Image = new BitmapImage(new Uri("pack:application:,,,/TpMechanical/Resources/Image1.jpg"));
//BitmapImage b3Image = new BitmapImage(new Uri("pack:application:,,,/TpMechanical/Resources/Image2.bmp"));
a.CreateRibbonTab(tabname);
var Tools = a.CreateRibbonPanel(tabname, panelname);
var button1 = new PushButtonData("TpButton1", "Button1", Assembly.GetExecutingAssembly().Location, "TpMechanical.command");
button1.ToolTip = " This is a short description";
button1.LongDescription = "This is a long description \n " +
"this is the second line";
var btn1 = Tools.AddItem(button1);
button1.Image = b1Image;
var button2 = new PushButtonData("TpButton2", "Button2", Assembly.GetExecutingAssembly().Location, "TpMechanical.command2");
button2.ToolTip = " This is a short description";
button2.LongDescription = "This is a long description \n " +
"this is the second line";
button2.Image = b2Image;
var button3 = new PushButtonData("TpButton3", "Button3", Assembly.GetExecutingAssembly().Location, "TpMechanical.command3");
button3.ToolTip = " This is a short description";
button3.LongDescription = "This is a long description \n " +
"this is the second line";
button3.Image = b3Image;
Tools.AddStackedItems(button2, button3);
return Result.Succeeded;
}
public Result OnShutdown(UIControlledApplication a)
{
return Result.Succeeded;
}
}
}
I also have my manifest code below.
<?xml version="1.0" encoding="utf-8"?>
<RevitAddIns>
<AddIn Type="Command">
<Text>Command TpMechanical</Text>
<Description>Some description for TpMechanical</Description>
<VisibilityMode>AlwaysVisible</VisibilityMode>
<Assembly>C:\My Revit- Custom Files\01-Revit 2021\Revit 2021 Repos\TpMechanical\bin\Debug\TpMechanical.dll</Assembly>
<FullClassName>TpMechanical.Command</FullClassName>
<ClientId>9EDCBEA6-942A-4D9A-932D-612B5E02DC9C</ClientId>
<VendorId>com.typepad.thebuildingcoder</VendorId>
<VendorDescription>The Building Coder, http://thebuildingcoder.typepad.com</VendorDescription>
</AddIn>
<AddIn Type="Command">
<Text>Command TpMechanical</Text>
<Description>Some description for TpMechanical</Description>
<VisibilityMode>AlwaysVisible</VisibilityMode>
<Assembly>C:\My Revit- Custom Files\01-Revit 2021\Revit 2021 Repos\TpMechanical\bin\Debug\TpMechanical.dll</Assembly>
<FullClassName>TpMechanical.Command2</FullClassName>
<ClientId>1A164A1B-8B02-499A-8ADB-94A75557CD66</ClientId>
<VendorId>com.typepad.thebuildingcoder</VendorId>
<VendorDescription>The Building Coder, http://thebuildingcoder.typepad.com</VendorDescription>
</AddIn>
<AddIn Type="Command">
<Text>Command TpMechanical</Text>
<Description>Some description for TpMechanical</Description>
<VisibilityMode>AlwaysVisible</VisibilityMode>
<Assembly>C:\My Revit- Custom Files\01-Revit 2021\Revit 2021 Repos\TpMechanical\bin\Debug\TpMechanical.dll</Assembly>
<FullClassName>TpMechanical.Command3</FullClassName>
<ClientId>C5CEC594-E407-40A8-B1B0-163DAA179CDD</ClientId>
<VendorId>com.typepad.thebuildingcoder</VendorId>
<VendorDescription>The Building Coder, http://thebuildingcoder.typepad.com</VendorDescription>
</AddIn>
<AddIn Type="Application">
<Name>Application TpMechanical</Name>
<Assembly>C:\My Revit- Custom Files\01-Revit 2021\Revit 2021 Repos\TpMechanical\bin\Debug\TpMechanical.dll</Assembly>
<FullClassName>TpMechanical.App</FullClassName>
<ClientId>C12635D2-96E2-4DF4-B172-7BD9487F7AE9</ClientId>
<VendorId>com.typepad.thebuildingcoder</VendorId>
<VendorDescription>The Building Coder, http://thebuildingcoder.typepad.com</VendorDescription>
</AddIn>
</RevitAddIns>
enter image description here
Rereading your question a third time over, it sounds as if your add-in is trying to reference a .NET assembly DLL that cannot be found when Revit tries to load it. Looking at the list of namespaces that you reference in your source code using statements, I see nothing but standard Autodesk Revit, Microsoft and .NET assemblies listed. So, they should all be present and accessible. Are you using anything else elsewhere in your code that is not obvious from that list? You might be able to use tools like fuslogv to analyse your add-in dependencies during load time, as suggested in the note on Exploring Assembly Reference DLL Hell with Fuslogvw.
I suggest you try again with a minimal one-liner external command and a minimal one-liner add-in manifest.
Follow these steps: Revit developers guide add-in registration.
Ensure that Revit has read access to its AddIns folder.
Look at the Hello world walkthrough.
Do not say you exhausted all resources. That would take too long and probably exceed your life span. New resources are being added faster than you can consume them, so any attempt is doomed to fail.
The error message is telling you that the problem is not in the internal implementation code, but just in the basic registration.
Why do you add internal to the IExternalApplication implementation? Isn't that a contradiction? What does that mean?
Why do you use The Building Coder VendorId? That is incorrect. You are not The Building Coder.
Your Assembly path is complex and littered with spaces. In general, I try to avoid such complex paths and all spaces in folder names. I also prefer forward slashes to backward ones. You can omit the folder name entirely if you place the DLL in the same place as the add-in manifest in the AddIns folder.
I am being inundated with similar questions these days. Here is another similar one, a summary of a recent email thread:
[Q] I have dived into the Getting Started with Revit platform API, following the DevTV tutorial by Augusto Goncalves. None of my commands appear on the Revit UI > Add Ins > external commands.
[A] One thing you ought to read is the introductory section of the Revit API developers guide. It tells you exactly what to do to install and launch your add-in. It is shocking of that information is not clear and does not work in the tutorial, though. Thank you for bringing it up!
Installing a Revit add-in is really simple, but people run into difficulties like you describe anyway.
There are only two relevant components:
Add-in manifest file *.addin
.NET class library assembly DLL
These are the important steps:
The DLL must implement IExternalCommand; that means, it must implement the Execute method.
The add-in manifest must point to the DLL and must be placed in the Revit Add-Ins folder for Revit to find and load it.
If the DLL and add-in manifest both reside in the Revit AddIns folder, the full DLL path can be omitted; otherwise it must be specified.
That is really all.
There are thousands of places explaining it; they all say the same thing.
Good luck and lots of fun with the Revit API :-)
[R] I have not had any luck since yesterday about my add-in not appearing in the Revit external commands.
I have carefully structured my code correctly. The add-in manifest file is pointing to my project .dll file. My project class explicitly implements the IExternalCommand interface and fires up the Execute method just fine.
I don't understand what the issue could be, not sure it could be the revit version am using am trying to figure out all possibilities.
[R2] I managed to debug my code. Kindly, ignore previous message.
The location of my manifest add-in file was locked. I guess that was done when my account was set up. The location needed permission to be accessed. This path:
C:\ProgramData\Autodesk\Revit\Addins\2022\
I utilised the try and catch exception to see the issue.
Once I gave access permission, the add-in file is now visible; it worked!
I've struggle several hours on that and I can't find what I'm doing wrong.
I created a new C# dll project, here is the content of the only class it contain:
using System;
using System.Runtime.InteropServices;
namespace PolygonSl {
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class Config {
[ComVisible(true)]
public string GetCompany() {
return "POL";
}
}
}
I basically remove everything from it trying to make it work, the only reference is System.
I checked the Make assembly COM-Visible flag on the Assembly Information and my project is signed (seams required for codebase).
It compiling fine, after that, I called RegAsm.exe, giving it my dll, I added /codebase and /tlb, the command is successful.
When I go to my VBA project, I can add my new tlb file to the references, working fine. After, I can use it in my code, the autocomplete is working and I can compile with no errors.
Then, when I execute, I got this:
Run-time error '430':
Class does not support Automation or does not support expected interface
Here is my code sample in the VBA:
Private Sub Button1_Click()
'With CreateObject("PolygonSl.Config")
With New PolygonSl.Config
MessBox .GetCompany, MB_OK, "Test"
End With
End Sub
I tried late binding and my code is running fine with it but I'd like to be able to use the autocomplete.
Anyone have a suggestion on what I could try to make it work?
Edit (Adding some details on my environment)
I work on VS2008 for projects related to Dynamics SL (one of the Microsoft ERPs)
I'm on Windows Server 2008 R8 Standard, running from VMWare
Compiling on Framework 3.5, Release, x86, Dynamics SL client is 32 bits
I tried my dll on Dynamics but also on Excel to be sure that the problem was not Dynamics ;)
I think you need to define an interface to be able to see getcompany.
using System;
using System.Runtime.InteropServices;
namespace PolygonSl
{
[Guid("6DC1808F-81BA-4DE0-9F7C-42EA11621B7E")]
[System.Runtime.InteropServices.ComVisible(true)]
[System.Runtime.InteropServices.InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IConfig
{
string GetCompany();
}
[Guid("434C844C-9FA2-4EC6-AB75-45D3013D75BE")]
[System.Runtime.InteropServices.ComVisible(true)]
[System.Runtime.InteropServices.ClassInterface(ClassInterfaceType.None)]
public class Config : IConfig
{
public string GetCompany()
{
return "POL";
}
}
}
You can generate the interface automatically by placing the cursor in the class definition and using Edit.Refactor.ExtractInterface.
I'd have to admit that I'm at the absolute edge of my abilities here and the above is put together based on examples I've seen elsewhere.
Edit
The following test code works fine on my PC
Option Explicit
Sub polygontest()
Dim my_polygon As SOPolygon.Config
Set my_polygon = New SOPolygon.Config
Debug.Print my_polygon.GetCompany
End Sub
Where SOPolygon is the project name.
I am creating an application level add-in for Word 2010 using C# VSTO. The add-in has a Custom Task Pane with its visibility controlled by a toggle button on a ribbon. The ribbon has been created with XML (not the Visual Studio Designer).
When I call ribbon.Invalidate from outside the add-in's ribbon class I cannot refresh my ribbon because it is null. I get a System.NullReferenceException that issues the message:
Object reference not set to an instance of the object
I suspect that the ribbon is null because the underlying XML is not loaded when it is called. I have tried many things including
Invalidating Ribbon from Outside Ribbon
which suggests defining a class-level Office.IRibbonUI in the ThisAddIn class, and setting the value of this in the Ribbon_Load callback. I still received the exception with this and all other attempts I made. Here's my code:
In the myRibbon class I have:
[ComVisible(true)]
public class myRibbon : Office.IRibbonExtensibility
{
public Office.IRibbonUI ribbon;
private bool isTaskPaneVisible;
public bool IsTaskPaneVisible
{
get { return isTaskPaneVisible; }
set
{
isTaskPaneVisible = value;
// This is where the null exception is thrown
ribbon.Invalidate();
}
}
and
public void Ribbon_Load(Office.IRibbonUI ribbonUI)
{
this.ribbon = ribbonUI;
}
In the ThisAddin class I have:
public partial class ThisAddIn
{
internal myRibbon myRibbon;
and
protected override Microsoft.Office.Core.IRibbonExtensibility CreateRibbonExtensibilityObject()
{
myRibbon = new myRibbon();
return myRibbon;
}
Any assistance in getting ribbon.Invalidate to function correctly when called from outside the ribbon class would be greatly appreciated.
More than two and a half years after posting this question, the solution came to me while working on a different VSTO project. The issue was with the XML for the VSTO project.
ribbon.Invalidate was null because
public void Ribbon_Load(Office.IRibbonUI ribbonUI)
{
this.ribbon = ribbonUI;
}
assigned a null value to ribbon. And ribbonUI was null because the Ribbon XML file was missing a reference to Ribbon_Load.
So the original XML file read
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" xmlns:nsCustom="Custom Namespace">
But it should have had onLoad="Ribbon_Load" included
<customUI onLoad="Ribbon_Load" xmlns="http://schemas.microsoft.com/office/2009/07/customui" xmlns:nsCustom="Custom Namespace">
I revisited the code I wrote and confirmed that this resolved the issue.
Was the Ribbon_Load callback called before you try to use the IRibbonUi instance?
There is no need to call the Invalidate method if the Load callback is not yet called. The fact is that your callbacks will be invoked automatically for the first time right after the Load one.
Read more about the Fluent UI (aka Ribbon UI) in the following series of articles in MSDN:
Customizing the 2007 Office Fluent Ribbon for Developers (Part 1 of 3)
Customizing the 2007 Office Fluent Ribbon for Developers (Part 2 of 3)
Customizing the 2007 Office Fluent Ribbon for Developers (Part 3 of 3)
I have searched nearly everywhere, but cannot find a way of creating/inserting a new Page/Tab in C# within a Visio document. I recorded a VB Macro of creating a new page within a document, and it is really simple there. However, I am using C# and cannot find the right commnands. Thanks in advance!
Writing in C# you will use the same COM API which VBA uses. A simple way to automate Visio using C# is to download and install the Primary Interop Assembly (PIA). Then include the reference Microsoft.Office.Interop.Visio in your project. Here is a simple example of using the PIA to manipulate the pages in a Visio document.
namespace VisioExample
{
using System;
using Microsoft.Office.Interop.Visio;
class Program
{
public static void Main(string[] args)
{
// Start Visio
Application app = new Application();
// Create a new document.
Document doc = app.Documents.Add("");
// The new document will have one page,
// get the a reference to it.
Page page1 = doc.Pages[1];
// Add a second page.
Page page2 = doc.Pages.Add();
// Name the pages. This is what is shown in the page tabs.
page1.Name = "Abc";
page2.Name = "Def";
// Move the second page to the first position in the list of pages.
page2.Index = 1;
}
}
}
To learn about developing solutions you can look at the Developing Visio Solutions book online. Download the Visio SDK, it contains a library of sample code in C#. You could look at "Visio 2003 Developer's Survival Pack" by Graham Wideman. As you found, the macro recorder can show you the API methods you need to call to achieve a task. The COM API used by VBA are the same API you will use in C#, the syntax of the code will differ obviously.
In visual studio I have an Excel 2010 Add-in project. How can I have that project create the following module:
I know I can save that workbook with that module then use it with my add in. It will be nice if I can have my add-in create that module...
It is possible to create the module. However for this to work the setting to "Trust access to the VB Project model" must be selected in Excel. It throws an error that access is denied if the trust setting is not selected.
using Excel = Microsoft.Office.Interop.Excel;
using VB = Microsoft.Vbe.Interop;
Excel.Application eApp = new Excel.Application();
eApp.Visible = true;
Excel.Workbook eBook = eApp.Workbooks.Add();
VB.VBProject eVBProj = (VB.VBProject)eBook.VBProject;
VB._VBComponent vbModule = eVBProj.VBE.ActiveVBProject.VBComponents.Add(VB.vbext_ComponentType.vbext_ct_StdModule);
String functionText = "Function MyTest()\n";
functionText += "MsgBox \"Hello World\"\n";
functionText += "End Function";
vbModule.CodeModule.AddFromString(functionText);
I dont think that VSTO supports Excel UDF's, the general recommendation is to use Automation Add-in's (as Sid's link suggests).
Another option is to call a managed VSTO function from VBA. Once again this is not recommended but possible.
(Recap of tutorial from link)
Here is any easy way to call Managed functions from VBA.
Create a class with your functions in VSTO
<System.Runtime.InteropServices.ComVisible(True)> _
Public Class MyManagedFunctions
Public Function GetNumber() As Integer
Return 42
End Function
End Class
Wire up your class to VBA in VSTO
Private Sub ThisWorkbook_Open() Handles Me.Open
Me.Application.Run("RegisterCallback", New MyManagedFunctions)
End Sub
Create Hook for managed code and a wrapper for the functions in VBA
In a VBA module in your spreadsheet or document
Dim managedObject As Object
Public Sub RegisterCallback(callback As Object)
Set managedObject = callback
End Sub
Public Function GetNumberFromVSTO() As Integer
GetNumberFromVSTO = managedObject.GetNumber()
End Function
Now you can enter =GetNumberFromVSTO() in a cell, when excel starts the cell value should be 42.
http://blogs.msdn.com/b/pstubbs/archive/2004/12/31/344964.aspx
If what you really want to do is to write .NET UDFs, or a combined .NET application level command and UDF addin then using VSTO is not currently a good solution: I would recommend using either Addin Express (costs) or Excel DNA (free). Both of these allow you to create both .NET XLL UDF addins and Automation UDF addins (XLL UDF addins offer significant performance advantages but with slightly more restricted access to the Excel object model)
A VSTO addin can't create UDF's, so you need to create a separate addin for the functions. Although this addin can be in the same DLL as the VSTO addin, you cannot communicate between the VSTO and the UDF's without special trickery.
I have a blog post about this. It gives you a complete example project that includes VSTO and UDF's.
Here is the basic structure of the UDF itself.
[Guid("3B81B6B7-3AF9-454F-AADF-FAF06E5A98F2")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[ComVisible(true)]
public interface IFunctions
{
int MYINT();
}
[Guid("F58C591D-A22F-49AD-BC21-A086097DC26B")]
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class Functions : IFunctions
{
public int MYINT()
{
return 42;
}
}