処理エラー

処理エラー

通常、ポストされたエラーはトランザクションの終了時に Revit の標準エラー解決 UI で処理されます(特に、Transaction.Commit()メソッドや Transaction.Rollback()メソッドが呼び出された場合)。エラーを処理するための情報とオプションがユーザに提示されます。

ドキュメント上のある操作(または一連の操作)に、特定のエラーに対する Revit アドインからの何らかの特別な処理が必要な場合、このような解決を実行するようエラー処理をカスタマイズできます。次の場合にカスタムのエラー処理を設定できます。

最後に、API には IFailuresProcessor インタフェースを使用して標準エラー処理ユーザ インタフェースを完全に置き換える機能があります。多くの場合、エラー処理には最初の 2 つのメソッドで十分ですが、より効果的なエラー処理 UI を提供したり、アプリケーションを Revit の最上位のフロントエンドとして使用するような特別な場合に、最後のオプションを使用できます。

エラー処理の概要

Transaction.Commit()への呼び出しと実際のエラー処理の間には、多数の処理が行われることに注意してください。自動結合、重なりのチェック、グループのチェック、ワークセットの編集可能性のチェックなどはその一部です。これらのチェックや変更により、一部のエラーが消失することもありますが、さらに新しいエラーがポストされることも多くあります。したがって、Transaction.Commit()が呼び出されたときには、処理すべきエラーの状態が完全には把握できていません。エラーを正しく処理するには、実際のエラー処理メカニズムに接続する必要があります。

エラー処理が始まると、トランザクションで作成されるはずであったドキュメントに対するすべての変更が行われ、すべてのエラーがポストされます。したがって、エラー処理時には、ドキュメントに対するコントロールされていない変更は許可されません。FailuresAccessor が提供する限定されたインタフェースでは、エラー解決の能力に限界があります。この場合は、すべてのトランザクション後のチェックとエラー処理を繰り返す必要があります。そのため、トランザクションの終わりにはエラー解決サイクルが数回発生する場合があります。

各エラー処理サイクルには次の 3 つの手順が含まれます。

  1. エラーの前処理(FailuresPreprocessor)
  2. エラー処理イベントのブロードキャスト(FailuresProcessing イベント)
  3. 最終処理(FailuresProcessor)

これらの 3 つの手順は、異なる FailureProcessingResults を返すことによって、次に発生する処理をコントロールすることができます。次のオプションがあります。

  • Continue - 実行フローには影響を与えません。FailuresProcessor が未解決なエラーとともに「Continue」を返した場合、Revit は「ProceedWithRollBack」が返されたように動作します。
  • ProceedWithCommit - エラー処理を中断し、トランザクション後チェックの別のループをトリガして、その後別のエラー処理が行われます。エラー解決の試行の後に返されます。正常なエラー処理なしで返された場合、すぐに無限ループの原因となることがあります。トランザクションが既にロールバックされている場合は返されることができず、この場合は「ProceedWithRollBack」として処理されます。
  • ProceedWithRollback - エラー処理の実行を継続しますが、最初にコミットするようリクエストされている場合でも、トランザクションを強制的にロールバックします。ProceedWithRollBack が返される前に FailureHandlingOptions がロールバック後にエラーを消去するよう設定されている場合は、その後のエラー処理は発生せず、すべてのエラーが削除され、トランザクションはサイレントにロールバックされます。設定されていない場合は既定のエラー処理が続行され、エラーがユーザに配信される場合がありますが、トランザクションをロールバックすることが保証されます。
  • WaitForUserInput - エラー処理の完了のための外部イベント(通常はユーザ入力)を待っている場合に、FailuresProcessor のみが返すことができます。

トランザクションにポストされたエラーの重大度と、トランザクションがコミットされるかロールバックされるかに応じて、これらの 3 つの手順のそれぞれがエラーを解決するためのオプションを持つことができます。ドキュメントにポストされるエラーに関するすべての情報、エラーを解決するための特定の操作を実行するための機能に関する情報、そのような操作を実行する API については、FailuresAccessor クラスによって提供されます。ドキュメントを使用すると追加情報を入手できますが、FailuresAccessor 以外の方法で変更することはできません。

FailuresAccessor

FailuresAccessor オブジェクトはエラー処理の各ステップに引数として渡され、エラーに関する情報をドキュメントに取得するために使用可能な唯一のインタフェースです。エラー処理中のドキュメントからの読み取りは許可されていますが、エラー解決中にドキュメントを修正する唯一の方法はこのクラスによって提供されるメソッドのみです。エラー処理から戻ると、クラスのインスタンスが非アクティブになり、それ以降は使用できなくなります。

FailuresAccessor から使用できる情報

FailuresAccessor オブジェクトは次のような一般情報を提供します。

  • エラーが処理または前処理されるドキュメント
  • ドキュメントにポストされるエラーの最も高い重大度
  • 終了するトランザクションのトランザクション名とエラー処理オプション
  • トランザクションのコミットが要求されたか、それともロールバックが要求されたか。

FailuresAccessor オブジェクトも、GetFailuresMessages()メソッドを介して特定のエラーに関する情報を提供します。

エラー解決のためのオプション

FailuresAccessor オブジェクトには次のようなエラーを解決するための方法がいくつか備わっています。

  • 警告の重大度を伴うエラー メッセージは、DeleteWarning()メソッドまたは DeleteAllWarnings()メソッドで削除できます。
  • ResolveFailure()メソッドや ResolveFailures()メソッドを使用すると、各エラーに対して設定された最新のエラー解決タイプを使用して 1 つまたは複数のエラーを解決できます。
  • DeleteElements()は、エラーに関連する要素を削除することでエラーを解決できます。
  • または、すべてのエラー メッセージを削除し、ReplaceFailures()メソッドを使用して 1 つの「一般的な」エラーと置き換えます。

IFailuresPreprocessor

IFailuresPreprocessor インタフェースを使用すると、特定のトランザクションのみに対して、カスタムのエラー処理を提供できます。IFailuresPreprocessor は前処理手順を実行するために使用できるインタフェースであり、予想されるトランザクション エラーをフィルタしたり、新しいエラーをポストできます。エラーは次のようにしてフィルタにより除外されます。

  • トランザクションにポストされていることが分かっており、特定のトランザクションのコンテキストにおいてはユーザに関係ないと考えられる警告をサイレントに削除
  • トランザクションにポストされていることが分かっており、常に特定のトランザクションのコンテキストにおいて解決する必要のある特定のエラーをサイレントに削除
  • 「不完全な」トランザクションをコミットするべきではない場合や、特定のワークフローのユーザとのやりとりにおいてトランザクションの中止が望ましい場合に、トランザクションをサイレントに中止。

エラー解決処理中は、IFailuresPreprocessor インタフェースが最初にコントロールを取得します。ポストこれは、すべてのエラーのポストが保証されるか、関係のないすべてのエラーが削除された後、IFailuresPreprocessor が適切なタイミングでコントロールを取得する点を除き、トランザクションが終了する前にエラーのチェックと解決を行うのとほぼ同等です。

IFailuresPreprocessor はトランザクションごとに 1 つのみとなり、既定のエラー プリプロセッサはありません。トランザクションにアタッチされていない場合(エラー処理オプションを介して)、このエラー処理の最初の手順は単純に省略されます。

コード領域 26-3: 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;
        }
}

FailuresProcessing イベント

FailuresProcessing イベントは、セッション全体や多数の関係のないトランザクションに対してユーザ インタフェースなしにカスタムのエラー処理を提供するアプリケーションに最適です。次は、このイベントを介したエラー処理の使用例です。

  • オフィス標準(または他の条件)に基づいて特定の警告を自動的に削除、および/または特定のエラーを自動的に解決
  • カスタムのエラー ログ記録

FailuresProcessing イベントは IFailuresPreprocessor(ある場合)が終了した後に発生します。ハンドラをいくつでも持つことができ、すべてが呼び出されます。イベント ハンドラには値を返す方法がないため、イベントの引数の SetProcessingResult()を使用してステータスを伝える必要があります。設定できるのは、Continue、ProceedWithRollback、ProceedWithCommit のみです。

次の例は、FailuresProcessing イベントのイベント ハンドラを表しています。

コード領域 26-4: FailuresProcessing イベントを処理

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);
                }
        }
}

FailuresProcessor

FailuresProcessing イベントが処理された後に、IFailuresProcessor インタフェースは最後にコントロールを取得します。Revit セッションでアクティブな IFailuresProcessor は 1 つだけです。エラー プロセッサを登録するには、IFailuresProcessor からクラスを派生させて、Application.RegisterFailuresProcessor()メソッドを使用して登録します。登録済みのエラー プロセッサがある場合は破棄されます。Revit アドインが Revit 用のエラー プロセッサを登録する場合は、そのプロセッサがセッションのすべての Revit エラーの既定のエラー ハンドラとなり、標準の Revit エラー ダイアログは表示されません。エラー プロセッサが設定されていない場合は、Revit UI にある既定のエラー プロセッサがすべての通常の Revit エラー ダイアログを呼び出します。FailuresProcessor は、既存の Revit エラー UI をカスタムのエラー解決ハンドラ(インタラクティブまたはユーザ インタフェースを持たないもの)に置き換える場合にのみオーバーライドされます。

RegisterFailuresProcessor()メソッドに null を渡した場合、エラーのあるトランザクションはすべてサイレントに中止されます(エラー処理の最初の 2 つの手順でエラーが解決されない場合)。

IFailuresProcessor.ProcessFailures()メソッドは、トランザクションを保留にしておく WaitForUserInput を返すことができます。この場合、FailuresProcessor は最終的に保留中のトランザクションをコミットまたはロールバックする UI を画面上に残すと想定されます。UI が残されない場合、保留中の状態が無制限に続き、実質的にドキュメントがフリーズすることになります。

IFailuresProcessor 実装の次の例では、エラーを確認し、エラーが発生している要素を削除し、ユーザ用の適切なメッセージを設定します。

コード領域 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;
                }
        }
}