By Aaron Lu
In previous Revit version, we can register/unregister an event in a modeless dialog, but now Revit 2016 changed the behavior of Events, it is no longer allowed to do so, otherwise exception will be thrown, if that exception is not caught by your application, Revit may crash.
Revit 2016 API changes document says:
API events - behavioral change
Although the Revit API has never officially supported such a work-flow it is now enforced that registering to and unregistering from events must happen while executing on the main thread. An exception will be thrown if an external application attempts to register to (or unregister from) events from outside of valid API context.
The solutions can be:
- Use model dialog, or make sure you register/unregister events in the Execute function of IExternalCommand or OnStartup/OnShutdown function of IExternalApplication.
- If you must use modeless dialog, you can use ExternalEvent.Raise() method, force the context switch to Revit main thread, and in the corresponding Execute method of IExternalEventHandler, register/unregister your events.
Code examples
public class EventRegisterHandler : IExternalEventHandler { public void Execute(UIApplication app) { app.Application.DocumentChanged += Application_DocumentChanged; } void Application_DocumentChanged(object sender, Autodesk.Revit.DB.Events.DocumentChangedEventArgs e) { // do your stuff } public string GetName() { return "EventRegisterHandler"; } }When you want to make it happen, just create an instance of ExternalEvent, and call its Raise() method. e.g. in a modeless dialog, clicking a button to cause the registration of the event DocumentChanged. Code:
private void button1_Click(object sender, EventArgs e) { ExternalEvent _exEvent = null; EventRegisterHandler _exEventHandler = null; _exEventHandler = new EventRegisterHandler(); _exEvent = ExternalEvent.Create(_exEventHandler); _exEvent.Raise(); }
Edited on 2016/3/8
Someone found the above code "ExternalEvent.Create" method throws Autodesk.Revit.Exceptions.InvalidOperationException: Attempting to create an ExternalEvent outside of a standard API execution, that is a true bug in my code, sorry for that :-(. And the solution is easy: do not call "ExternalEvent.Create" in modeless dialog but in "IExternalCommand.Execute" or "IExternalApplication.OnStartup". Following is a full code example showing that: Run ExternalCommand "EventRegistrationInModelessDialogViaExternalEvent" and then click the button in the modeless dialog to register or unregister DocumentChanged event.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Autodesk.Revit.ApplicationServices; using Autodesk.Revit.Attributes; using Autodesk.Revit.DB; using Autodesk.Revit.UI; using System.Windows.Forms; namespace TestScript { [TransactionAttribute(TransactionMode.Manual)] public class EventRegistrationInModelessDialogViaExternalEvent : IExternalCommand { public Document doc; public Autodesk.Revit.ApplicationServices.Application RevitApp; ExternalEvent _exEvent; public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) { EventRegisterHandler _exeventHander = new EventRegisterHandler(); _exEvent = ExternalEvent.Create(_exeventHander); MyForm form = new MyForm(); form.ExEvent = _exEvent; form.Show(); return Result.Succeeded; } } public class MyForm : System.Windows.Forms.Form { public MyForm() : base() { Button btn = new Button(); btn.Text = "Toggle DocumentChanged Event Registration"; btn.Click += btn_Click; btn.Width = 250; this.Controls.Add(btn); } public ExternalEvent ExEvent { get; set; } void btn_Click(object sender, EventArgs e) { if (ExEvent != null) ExEvent.Raise(); else MessageBox.Show("external event handler is null"); } } public class EventRegisterHandler : IExternalEventHandler { public bool EventRegistered { get; set; } public void Execute(UIApplication app) { if (EventRegistered) { EventRegistered = false; app.Application.DocumentChanged -= Application_DocumentChanged; } else { EventRegistered = true; app.Application.DocumentChanged += Application_DocumentChanged; } } void Application_DocumentChanged(object sender, Autodesk.Revit.DB.Events.DocumentChangedEventArgs e) { var sb = new StringBuilder(); var added = "added:" + e.GetAddedElementIds() .Aggregate("", (ss, el) => ss + "," + el).TrimStart(','); var modified = "modified:" + e.GetModifiedElementIds() .Aggregate("", (ss, el) => ss + "," + el).TrimStart(','); var deleted = "deleted:" + e.GetDeletedElementIds() .Aggregate("", (ss, el) => ss + "," + el).TrimStart(','); sb.AppendLine(added); sb.AppendLine(modified); sb.AppendLine(deleted); TaskDialog.Show("Changes", sb.ToString()); } public string GetName() { return "EventRegisterHandler"; } } }