Run Macro as External Command

Joshua Lumley 5 Secrets of Revit API C# Coding Ribbon Button Macro External Events Modeless dockable addin

  • Modeless forms requires this nested structure: ExternalEvents -> Try-Catch statements for error handling messages -> Transaction -> Interaction with the database

  • Create a module and a macro with some code
    TaskDialog mainDialog = new TaskDialog("Fun Secrets of Revit Coding!");
    mainDialog.MainInstruction = "Secret Code in Revit API !";
    mainDialog.MainContent = "Do you want to be an awesome, all powerful, all knowing Revit API coder?";
    mainDialog.AddCommandLink(TaskDialogCommandLinkId.CommandLink1,"Yes, I do - show me the way!");
    mainDialog.AddCommandLink(TaskDialogCommandLinkId.CommandLink2, "No, I'm in a comfortable vegetative state.");
    
  • Set ThisApplication to inherit from IExternalCommand
     public partial class ThisApplication : IExternalCommand
    
  • Modify the macro to be able to accept an UIDocument
     public void myMacro(UIDocument uidoc)
    
  • Add an Execute subroutine that will run the macro
    public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements){
     myMacro(commandData.Application.ActiveUIDocument);
     return Result.Succeeded;
     }
    
  • Create a Ribbon Button: create a new module called myRibbon that inherits from IExternalApplication
     public partial class ThisApplication : IExternalApplication
    
  • To create the button we need to add the following directives:
    using System.Reflection;
    using System.IO;
    using System.Windows.Media.Imaging;
    

and the following references:

 WindowsBase
 PresentationCore
 SystemXaml
  • An IExternalApplication requires a OnStartup and OnShutdown routine ```csharp public Result OnShutdown(UIControlledApplication a) { return Result.Succeeded; }

public Result OnStartup(UIControlledApplication a) { string ChecklistsNumber = “myRibbonButton”; string path = Assembly.GetExecutingAssembly().Location;

String exeConfigPath = Path.GetDirectoryName(path) + "\\myRibbon.dll";
a.CreateRibbonTab(ChecklistsNumber);
RibbonPanel PRLChecklistsPanel = a.CreateRibbonPanel(ChecklistsNumber, ChecklistsNumber);
PushButtonData myPushButtonData = new PushButtonData(ChecklistsNumber, ChecklistsNumber, exeConfigPath, "myRibbon.Invoke");

myPushButtonData.LargeImage = new BitmapImage(new Uri(Path.Combine(Path.GetDirectoryName(path) + "\\011 myButtonImage paste into Addin.png"), UriKind.Absolute));

RibbonItem myRibbonItem = PRLChecklistsPanel.AddItem(myPushButtonData);

return Result.Succeeded; }				  ```
  • Add an ExternalEvent that calls the macro. The InvokeMember allows to run the command from memory without shutting down Revit
     	
         [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]	
    
        public class Invoke : IExternalCommand
     {
         //public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
         public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
         {
             string path = Assembly.GetExecutingAssembly().Location;
             //String exeConfigPath = Path.GetDirectoryName(path) + "..\\..\\..\\" + "myMacros\\2018\\Revit\\AppHookup\\_969_PRLChecklists\\AddIn\\_969_PRLChecklists.dll";
             String exeConfigPath = Path.GetDirectoryName(path) + "..\\..\\..\\" + "myModule\\AddIn\\myModule.dll";
             String exeConfigPath2 = Path.GetDirectoryName(path) + "..\\..\\..\\" + "myModule\\AddIn";
    
             string strCommandName = "ThisApplication";
    
             byte[] assemblyBytes = File.ReadAllBytes(exeConfigPath);
    
             Assembly objAssembly = Assembly.Load(assemblyBytes);
             IEnumerable<Type> myIEnumerableType = GetTypesSafely(objAssembly);
             foreach (Type objType in myIEnumerableType)
             {
                 if (objType.IsClass)
                 {
                     if (objType.Name.ToLower() == strCommandName.ToLower())
                     {
                         object ibaseObject = Activator.CreateInstance(objType);
                         object[] arguments = new object[] { commandData, exeConfigPath2, elements };
                         object result = null;
    
                         result = objType.InvokeMember("Execute", BindingFlags.Default | BindingFlags.InvokeMethod, null, ibaseObject, arguments);
    
                         break; 
                     }
                 }
             }
             return Result.Succeeded;     
    
         }
         private static IEnumerable<Type> GetTypesSafely(Assembly assembly)
         {
             try
             {
                 return assembly.GetTypes();
             }
             catch (ReflectionTypeLoadException ex)
             {
                 return ex.Types.Where(x => x != null);
             }
         }
        }
    
  • Add an image to the button. The image should be saved in the C:\ProgramData\Autodesk\Revit\Macros\2018\Revit\AppHookup\myRibbon\AddIn folder

  • Add an addin file to the Revit folder C:\ProgramData\Autodesk\Revit\Addins\2018

  • Add a modeless form ```csharp namespace myModule { /// <summary> /// Description of Form1. /// </summary> public partial class Form1 : System.Windows.Forms.Form {

    public Document doc { get; set; }

         linePatternsWeightsFalse mylinePatternsWeightsFalse;
         ExternalEvent myMakeLinePatterns;
            
        linePatternsWeightsTrue mylinePatternsWeightsTrue;
         ExternalEvent myMakeLineWeights;  public Form1()
     {
         //
         // The InitializeComponent() call is required for Windows Forms designer support.
         //
         InitializeComponent();
         mylinePatternsWeightsFalse = new linePatternsWeightsFalse();
         myMakeLinePatterns = ExternalEvent.Create(mylinePatternsWeightsFalse);
    		
         mylinePatternsWeightsTrue = new linePatternsWeightsTrue();
         myMakeLineWeights = ExternalEvent.Create(mylinePatternsWeightsTrue);
         //
     }
    

    }

public class linePatternsWeightsTrue : IExternalEventHandler {

    public void Execute(UIApplication a)
    {
    	
    	UIDocument uidoc =  a.ActiveUIDocument;
    	Document doc = uidoc.Document;
    	
Transaction transaction = new Transaction( doc);
transaction.Start( "Draw Line Patterns or Weights" );	
    	
    	DrawLines myThis = new DrawLines();
    	
    	myThis._99_DrawLinePatterns(true, false, uidoc);
		transaction.Commit();
    	return;
    	
    }
    
   public string GetName()
    {
        return "External Event Example";
    }
    
    
}
    
[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
[Autodesk.Revit.DB.Macros.AddInId("23CF5F71-5468-438D-97C7-554F4F782936")]	

    public class linePatternsWeightsFalse : IExternalEventHandler
{
	
    public void Execute(UIApplication a)
    {
    	
    	UIDocument uidoc =  a.ActiveUIDocument;
    	Document doc = uidoc.Document;
    	
        Transaction transaction = new Transaction( doc);
        transaction.Start( "Draw Line Patterns or Weights" );	
    	
    	DrawLines myThis = new DrawLines();
  	
    	myThis._99_DrawLinePatterns(false, false, uidoc);

		transaction.Commit();
    	return;
    }

public string GetName() { return “External Event Example”; }

} }  ```
  • InvalidOperationException errors can be neutralized using a try and catch statement ```csharp try { throw new InvalidOperationException();

           	using (Transaction t = new Transaction(doc, "Set a parameters"))
                   {
                    	t.Start();
                     doc.ProjectInformation.GetParameters("Project Name")[0].Set("Space Elevator");  //this needs to change in two places
                     t.Commit();
                    }
    	      	
    	      	
             //myEE7ActionParameter.Raise();  				
         }
    
         #region catch and finally
         catch (Exception ex)
         {
             TaskDialog.Show("Catch", "Failed due to:" + Environment.NewLine + ex.Message);
         }
         finally
         {
    
 
 - "Starting a transaction from an external application running outside of API context is not allowed." requires to launch methods from ExternalEvents. It has to be declared first and then activated.
 ```csharp
// Declaration 
	public partial class Form1 : System.Windows.Forms.Form
	{
		
		ButtonEE7Parameter myEE7Parameter;
        ExternalEvent myEE7ActionParameter;
// Initialization
public Form1()
    {
        //
        // The InitializeComponent() call is required for Windows Forms designer support.
        //
        InitializeComponent();
        
        myEE7Parameter = new ButtonEE7Parameter();
        myEE7ActionParameter = ExternalEvent.Create(myEE7Parameter);
  • Use a raise command to trigger the ExternalEvent
    void Button3Click(object sender, EventArgs e)
    {
      try
      {
          myEE7ActionParameter.Raise();  				
      }
      catch (Exception ex)
      {
          TaskDialog.Show("Catch", "Failed due to:" + Environment.NewLine + ex.Message);
      }
      finally
      {
      }
    }
    

1. ThisApplication

- public Result Execute() -> launches myMacro
- public void myMacro() -> creates and shows the modeless form: myForm1.Show()

2. Form1

- an IExternalEventHandler parameter  ```ButtonShowTaskDialog myTaskButtonParameter;``` outside Form1()
- an ExternalEvent parameter ```ExternalEvent myTaskButtonEvent;``` outside Form1()
- the IExternalEventHandler parameter activation ```myTaskButtonParameter = new ButtonShowTaskDialog();``` inside Form1()
- the ExternalEvent parameter  activation ```myTaskButtonEvent = ExternalEvent.Create(myTaskButtonParameter);``` inside Form1()
- a ```myTaskButtonEvent.Raise()`` inside a ButtonClick(sender, args) 
Written on November 28, 2018