Normally posted failures are processed by Revit's standard failure resolution UI at the end of a transaction (specifically when Transaction.Commit() or Transaction.Rollback() are invoked). The user is presented information and options to deal with the failures.
If an operation (or set of operations) on the document requires some special treatment from a Revit add-in for certain errors, failure handling can be customized to carry out this resolution. Custom failure handling can be supplied:
Finally, the API offers the ability to completely replace the standard failure processing user interface using the interface IFailuresProcessor. Although the first two methods for handling failures should be sufficient in most cases, this last option can be used in special cases, such as to provide a better failure processing UI or when an application is used as a front-end on top of Revit.
It is important to remember there are many things happening between the call to Transaction.Commit() and the actual processing of failures. Auto-join, overlap checks, group checks and workset editability checks are just to name a few. These checks and changes may make some failures disappear or, more likely, can post new failures. Therefore, conclusions cannot be drawn about the state of failures to be processed when Transaction.Commit() is called. To process failures correctly, it is necessary to hook up to the actual failures processing mechanism.
When failures processing begins, all changes to a document that are supposed to be made in the transaction are made, and all failures are posted. Therefore, no uncontrolled changes to a document are allowed during failures processing. There is a limited ability to resolve failures via the restricted interface provided by FailuresAccessor. If this has happened, all end of transaction checks and failures processing have to be repeated. So there may be a few failure resolution cycles at the end of one transaction.
Each cycle of failures processing includes 3 steps:
Each of these 3 steps can control what happens next by returning different FailureProcessingResults. The options are:
Depending on the severity of failures posted in the transaction and whether the transaction is being committed or rolled back, each of these 3 steps may have certain options to resolve errors. All information about failures posted in a document, information about ability to perform certain operations to resolve failures and API to perform such operations are provided via the FailuresAccessor class. The Document can be used to obtain additional information, but it cannot be changed other than via FailuresAccessor.
A FailuresAccessor object is passed to each of failure processing steps as an argument and is the only available interface to fetch information about failures in a document. While reading from a document during failure processing is allowed, the only way to modify a document during failure resolution is via methods provided by this class. After returning from failure processing, the instance of the class is deactivated and cannot be used any longer.
The FailuresAccessor object offers some generic information such as:
The FailuresAccessor object also offers information about specific failures via the GetFailuresMessages() method.
The FailuresAccessor object provides a few ways to resolve failures:
The IFailuresPreprocessor interface can be used to provide custom failure handling for a specific transaction only. IFailuresPreprocessor is an interface that may be used to perform a preprocessing step to either filter out anticipated transaction failures or to post new failures. Failures can be "filtered out" by:
The IFailuresPreprocessor interface gets control first during the failure resolution process. It is nearly equivalent to checking and resolving failures before finishing a transaction, except that IFailuresPreprocessor gets control at the right time, after all failures guaranteed to be posted and/or after all irrelevant ones are deleted.
There may be only one IFailuresPreprocessor per transaction and there is no default failure preprocessor. If one is not attached to the transaction (via the failure handling options), this first step of failure resolution is simply omitted.
Code Region 26-3: Handling failures from IFailuresPreprocessor |
public class SwallowTransactionWarning : IExternalCommand { public Autodesk.Revit.UI.Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) { Autodesk.Revit.ApplicationServices.Application app = commandData.Application.Application; Document doc = commandData.Application.ActiveUIDocument.Document; UIDocument uidoc = commandData.Application.ActiveUIDocument; FilteredElementCollector collector = new FilteredElementCollector(doc); ICollection<Element> elementCollection = collector.OfClass(typeof(Level)).ToElements(); Level level = elementCollection.Cast<Level>().ElementAt<Level>(0); Transaction t = new Transaction(doc); t.Start("room"); FailureHandlingOptions failOpt = t.GetFailureHandlingOptions(); failOpt.SetFailuresPreprocessor(new RoomWarningSwallower()); t.SetFailureHandlingOptions(failOpt); doc.Create.NewRoom(level, new UV(0, 0)); t.Commit(); return Autodesk.Revit.UI.Result.Succeeded; } } public class RoomWarningSwallower : IFailuresPreprocessor { public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor) { IList<FailureMessageAccessor> failList = new List<FailureMessageAccessor>(); // Inside event handler, get all warnings failList = failuresAccessor.GetFailureMessages(); foreach (FailureMessageAccessor failure in failList) { // check FailureDefinitionIds against ones that you want to dismiss, FailureDefinitionId failID = failure.GetFailureDefinitionId(); // prevent Revit from showing Unenclosed room warnings if (failID == BuiltInFailures.RoomFailures.RoomNotEnclosed) { failuresAccessor.DeleteWarning(failure); } } return FailureProcessingResult.Continue; } } |
The FailuresProcessing event is most suitable for applications that want to provide custom failure handling without a user interface, either for the entire session or for many unrelated transactions. Some use cases for handling failures via this event are:
The FailuresProcessing event is raised after IFailuresPreprocessor (if any) has finished. It can have any number of handlers, and all of them will be invoked. Since event handlers have no way to return a value, the SetProcessingResult() on the event argument should be used to communicate status. Only Continue, ProceedWithRollback or ProceedWithCommit can be set.
The following example shows an event handler for the FailuresProcessing event.
Code Region 26-4: Handling the FailuresProcessing Event |
private void CheckWarnings(object sender, FailuresProcessingEventArgs e) { FailuresAccessor fa = e.GetFailuresAccessor(); IList<FailureMessageAccessor> failList = new List<FailureMessageAccessor>(); failList = fa.GetFailureMessages(); // Inside event handler, get all warnings foreach (FailureMessageAccessor failure in failList) { // check FailureDefinitionIds against ones that you want to dismiss, FailureDefinitionId failID = failure.GetFailureDefinitionId(); // prevent Revit from showing Unenclosed room warnings if (failID == BuiltInFailures.RoomFailures.RoomNotEnclosed) { fa.DeleteWarning(failure); } } } |
The IFailuresProcessor interface gets control last, after the FailuresProcessing event is processed. There is only one active IFailuresProcessor in a Revit session. To register a failures processor, derive a class from IFailuresProcessor and register it using the Application.RegisterFailuresProcessor() method. If there is previously registered failures processor, it is discarded. If a Revit add-in opts to register a failures processor for Revit that processor will become the default error handler for all Revit errors for the session and the standard Revit error dialog will not appear. If no failures processors are set, there is a default one in the Revit UI that invokes all regular Revit error dialogs. FailuresProcessor should only be overridden to replace the existing Revit failure UI with a custom failure resolution handler, which can be interactive or have no user interface.
If the RegisterFailuresProcessor() method is passed NULL, any transaction that has any failures is silently aborted (unless failures are resolved by first two steps of failures processing).
The IFailuresProcessor.ProcessFailures() method is allowed to return WaitForUserInput, which leaves the transaction pending. It is expected that in this case, FailuresProcessor leaves some UI on the screen that will eventually commit or rollback a pending transaction - otherwise the pending state will last indefinitely, essentially freezing the document.
The following example of implementing the IFailuresProcessor checks for a failure, deletes the failing elements and sets an appropriate message for the user.
Code Region 26-5: IFailuresProcessor |
[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Automatic)] public class MyFailuresUI : IExternalApplication { static AddInId m_appId = new AddInId(new Guid("9F179363-B349-4541-823F-A2DDB2B86AF3")); public Autodesk.Revit.UI.Result OnStartup(UIControlledApplication uiControlledApplication) { IFailuresProcessor myFailUI = new FailUI(); Autodesk.Revit.ApplicationServices.Application.RegisterFailuresProcessor(myFailUI); return Result.Succeeded; } public Autodesk.Revit.UI.Result OnShutdown(UIControlledApplication application) { return Result.Succeeded; } public class FailUI : IFailuresProcessor { public void Dismiss(Document document) { // This method is being called in case of exception or document destruction to // dismiss any possible pending failure UI that may have left on the screen } public FailureProcessingResult ProcessFailures(FailuresAccessor failuresAccessor) { IList<FailureResolutionType> resolutionTypeList = new List<FailureResolutionType>(); IList<FailureMessageAccessor> failList = new List<FailureMessageAccessor>(); // Inside event handler, get all warnings failList = failuresAccessor.GetFailureMessages(); string errorString = ""; bool hasFailures = false; foreach (FailureMessageAccessor failure in failList) { // check how many resolutions types were attempted to try to prevent // entering infinite loop resolutionTypeList = failuresAccessor.GetAttemptedResolutionTypes(failure); if (resolutionTypeList.Count >= 3) { TaskDialog.Show("Error", "Cannot resolve failures - transaction will be rolled back."); return FailureProcessingResult.ProceedWithRollBack; } errorString += "IDs "; foreach (ElementId id in failure.GetFailingElementIds()) { errorString += id + ", "; hasFailures = true; } errorString += "\nWill be deleted because: " + failure.GetDescriptionText() + "\n"; failuresAccessor.DeleteElements( failure.GetFailingElementIds() as IList<ElementId>); } if (hasFailures) { TaskDialog.Show("Error", errorString); return FailureProcessingResult.ProceedWithCommit; } return FailureProcessingResult.Continue; } } } |