To use the failure posting mechanism to report problems, the following steps are required:
Each possible failure in Revit must be defined and registered during Revit application startup by creating a FailureDefinition object that contains some persistent information about the failure such as identity, severity, basic description, types of resolution and default resolution.
The following example creates two new failures, a warning and an error, that can be used for walls that are too tall. In this example, they are used in conjunction with an Updater that will do the failure posting (in a subsequent code sample in this chapter). The FailureDefinitionIds are saved in the Updater class since they will be required when posting failures. The sections following explain the FailureDefinition.CreateFailureDefinition() method parameters in more detail.
Code Region 26-1: Defining and registering a failure |
WallWarnUpdater wallUpdater = new WallWarnUpdater(); UpdaterRegistry.RegisterUpdater(wallUpdater); ElementClassFilter filter = new ElementClassFilter(typeof(Wall)); UpdaterRegistry.AddTrigger(wallUpdater.GetUpdaterId(), filter, Element.GetChangeTypeGeometry()); // define a new failure id for a warning about walls FailureDefinitionId warnId = new FailureDefinitionId(new Guid("FB4F5AF3-42BB-4371-B559-FB1648D5B4D1")); // register the new warning using FailureDefinition FailureDefinition failDef = FailureDefinition.CreateFailureDefinition(warnId, FailureSeverity.Warning, "Wall is too big (>100'). Performance problems may result."); FailureDefinitionId failId = new FailureDefinitionId(new Guid("691E5825-93DC-4f5c-9290-8072A4B631BC")); FailureDefinition failDefError = FailureDefinition.CreateFailureDefinition(failId, FailureSeverity.Error, "Wall is WAY too big (>200'). Performance problems may result."); // save ids for later reference wallUpdater.WarnId = warnId; wallUpdater.FailureId = failId; |
A unique FailureDefinitionId must be used as a key to register the FailureDefinition. Each unique FailureDefinitionId should be created using a GUID generation tool. Later, the FailureDefinitionId can be used to look up a FailureDefinition in FailureDefinitionRegistry, and to create and post FailureMessages.
When registering a new failure, a severity is specified, along with the FailureDefinitionId and a text description of the failure that can be displayed to the user. The severity determines what actions are allowed in a document and whether the transaction can be committed at all. The severity options are:
A fourth severity, None, cannot be specified when defining a new FailureDefinition.
When a failure can be resolved, all possible resolutions should be predefined in the FailureDefinition class. This informs Revit what failure resolutions can possibly be used with a given failure. The FailureDefinition contains a full list of resolution types applicable to the failure, including a user-visible caption of the resolution.
The number of resolutions is not limited, however as of the 2011 Revit API, the only exposed failure resolution is DeleteElements. When more than one resolution is specified, unless explicitly changed using the SetDefaultResolutionType() method, the first resolution added becomes the default resolution. The default resolution is used by the Revit failure processing mechanism to resolve failures automatically when applicable. The Revit UI only uses the default resolution, but Revit add-ins, via the Revit API, can use any applicable resolution, and can provide an alternative UI for doing that (as described in the Handling Failures section later in this chapter).
In the case of a failure with a severity of DocumentCorruption, by the time failure resolution could occur, the transaction is already aborted, so there is nothing to resolve. Therefore, FailureResolutions should not be added to API-defined Failures of severity DocumentCorruption.
The Document.PostFailure() method is used to notify the document of a problem. Failures will be validated and possibly resolved at the end of the transaction. Warnings posted via this method will not be stored in the document after they are resolved. Failure posting is used to address a state of the document which may change before the end of the transaction or when it makes sense to defer resolution until the end of the transaction. Not all failures encountered by an external command should post a failure. If the failure is unrelated to the document, a task dialog should be used. For example, if the Revit UI is in an invalid state to perform the external command.
To post a failure, create a new FailureMessage using the FailureDefinitionId from when the custom failure was defined, or use a BuiltInFailure provided by the Revit API. Set any additional information in the FailureMessage object, such as failing elements, and then call Document.PostFailure() passing in the new FailureMessage. Note that the document must be modifiable in order to post a failure.
A unique FailureMessageKey returned by PostFailure() can be stored for the lifetime of transaction and used to remove a failure message if it is no longer relevant. If the same FailureMessage is posted two or more times, the same FailureMessageKey is returned. If a posted failure has a severity of DocumentCorruption, an invalid FailureMessageKey is returned. This is because a DocumentCorruption failure cannot be unposted.
The following example shows an IUpdate class (referenced in the "Defining and registering a failure" code region above) that posts a new failure based on information received in the Execute() method.
Code Region 26-2: Posting a failure |
public class WallWarnUpdater : IUpdater { static AddInId m_appId; UpdaterId m_updaterId; FailureDefinitionId m_failureId = null; FailureDefinitionId m_warnId = null; // constructor takes the AddInId for the add-in associated with this updater public WallWarnUpdater(AddInId id) { m_appId = id; m_updaterId = new UpdaterId(m_appId, new Guid("69797663-7BCB-44f9-B756-E4189FE0DED8")); } public void Execute(UpdaterData data) { Document doc = data.GetDocument(); Autodesk.Revit.ApplicationServices.Application app = doc.Application; foreach (ElementId id in data.GetModifiedElementIds()) { Wall wall = doc.GetElement(id) as Wall; Autodesk.Revit.DB.Parameter p = wall.LookupParameter("Unconnected Height"); if (p != null) { if (p.AsDouble() > 200) { FailureMessage failMessage = new FailureMessage(FailureId); failMessage.SetFailingElement(id); doc.PostFailure(failMessage); } else if (p.AsDouble() > 100) { FailureMessage failMessage = new FailureMessage(WarnId); failMessage.SetFailingElement(id); doc.PostFailure(failMessage); } } } } public FailureDefinitionId FailureId { get { return m_failureId; } set { m_failureId = value; } } public FailureDefinitionId WarnId { get { return m_warnId; } set { m_warnId = value; } } public string GetAdditionalInformation() { return "Give warning and error if wall is too tall"; } public ChangePriority GetChangePriority() { return ChangePriority.FloorsRoofsStructuralWalls; } public UpdaterId GetUpdaterId() { return m_updaterId; } public string GetUpdaterName() { return "Wall Height Check"; } } |
Because there may be multiple changes to a document and multiple regenerations in the same transaction, it is possible that some failures are no longer relevant and they may need to be removed to prevent "false alarms". Specific messages can be un-posted by calling the Document.UnpostFailure() method and passing in the FailureMessageKey obtained when PostFailure() was called. UnpostFailure() will throw an exception if the severity of the failure is DocumentCorruption.
It is also possible to automatically remove all posted failures when a transaction is about to be rolled back (so that the user is not bothered to hit Cancel) by using the Transaction.SetFailureHandlingOptions() method.