Switch View or Document by Showing Elements

A recent discussion on using the ShowElements method to toggle between documents and views brought up a few interesting points:

Open and Active an Unsaved Document

Normally, you can open and switch between documents using the OpenAndActivateDocument method.

However, it requires a file path, which may not have been defined yet, in the case of a newly created document.

In that case, you can open and switch between different documents and their views by calling the UIDocument ShowElements method instead.

This was discussed again in the Revit API discussion forum thread on changing the active document, and previously in how to open and active a new document that is not saved:

Question: When you have 2 Revit projects open and want to switch between them, it can be done with:

  application.OpenAndActivateDocument(file);

I used Document.PathName to get the filename required.

Now I run into problems with files on Revit Server and BIM 360 docs, because their Document.Pathname stays empty.

So, my question is, how do I switch between those documents (when Document.Pathname is empty)?

Is there a way to switch between documents without having to specify the file name?

Answer: The only way I have found is indirectly via UIDocument.ShowElements.

You pick an element from the DB document you want to change to, create a UIDocument object from a DB document and use UIDocument.ShowElements. You have to handle the occasional “No good view found” dialogue, but it seems to always switch the active document regardless of what is found. Helps if it is a View specific element.

Not sure if there is a better way...

When you call UIDocument.ShowElements, the active Document will change to the document you are showing elements in, therefore:

If you filter for any element in one of the views of the newly created document, you can call it using that element to activate that document.

Sometimes you get the dialogue 'No good view found', which is odd, considering you know there is at least one view with the element in considering you are filtering for it by view. You can handle the appearance of this dialogue and the ActiveDocument still gets changed. I believe the new document generally has a view with Elevation markers, hence there is always something to find and show.

I don't know the implications of changing the ActiveDocument this way or why the API has no ability to directly change the ActiveDocument directly, but I suspect if it were easy in terms of how the API works, it would have been done by now.

This workaround was also mentioned in The Building Coder discussion on mirroring in a new family and changing active view:

The first issue that arises is that the mirror command requires a current active view, which is not automatically present in the family document. Joe discovers a workaround for that issue using the ShowElements method. It generates an unwanted warning message, so a second step is required to deal with eliminating that as well.

As you can see on reading the final solution carefully, you can use the ShowElements method to change the active view and even switch it between the family and project documents. The official Revit 2011 API does not provide any method to switch the active view, but using ShowElements can be used to create a workaround for that."

I implemented a new external command CmdSwitchDoc in The Building Coder samples to try this out, in release 2018.0.138.1.

It demonstrates two uses of the ShowElements method:

Zoom to Selected Elements

/// <summary>
/// Zoom to the given elements, switching view if needed.
/// </summary>
/// <param name="ids"></param>
/// <param name="message">Error message on failure</param>
/// <param name="elements">Elements causing failure</param>
/// <returns></returns>
Result ZoomToElements( 
  UIDocument uidoc,
  ICollection<ElementId> ids,
  ref string message,
  ElementSet elements )
{
  int n = ids.Count;

  if( 0 == n )
  {
    message = "Please select at least one element to zoom to.";
    return Result.Cancelled;
  }
  try
  {
    uidoc.ShowElements( ids );
  }
  catch
  {
    Document doc = uidoc.Document;

    foreachElementId id in ids )
    {
      Element e = doc.GetElement( id );
      elements.Insert( e );
    }

    message = string.Format( 
      "Cannot zoom to element{0}.",
      1 == n ? "" : "s" );

    return Result.Failed;
  }
  return Result.Succeeded;
}

This functionality is similar to that provided by the Zoom to Awesome! add-in by Phil Read.

Here is a 40-second demo by Luke Johnson of using Zoom to Awesome, also showing how to add a keyboard shortcut:

Toggle Between Documents and Views

/// <summary>
/// Toggle back and forth between two different documents
/// </summary>
void ToggleViews( 
  View view1, 
  string filepath2 )
{
  Document doc = view1.Document;
  UIDocument uidoc = new UIDocument( doc );
  Application app = doc.Application;
  UIApplication uiapp = new UIApplication( app );

  // Select some elements in the first document

  ICollection<ElementId> idsView1
    = new FilteredElementCollector( doc, view1.Id )
      .WhereElementIsNotElementType()
      .ToElementIds();

  // Open the second file

  UIDocument uidoc2 = uiapp
    .OpenAndActivateDocument( filepath2 );

  Document doc2 = uidoc2.Document;

  // Do something in second file

  usingTransaction tx = new Transaction( doc2 ) )
  {
    tx.Start( "Change Scale" );
    doc2.ActiveView.get_Parameter(
      BuiltInParameter.VIEW_SCALE_PULLDOWN_METRIC )
        .Set( 20 );
    tx.Commit();
  }

  // Save modified second file

  SaveAsOptions opt = new SaveAsOptions
  {
    OverwriteExistingFile = true
  };

  doc2.SaveAs( filepath2, opt );

  // Switch back to original file;
  // in a new file, doc.PathName is empty

  if( !string.IsNullOrEmpty( doc.PathName ) )
  {
    uiapp.OpenAndActivateDocument(
      doc.PathName );

    doc2.Close( false ); // no problem here, says Remy
  }
  else
  {
    // Avoid using OpenAndActivateDocument

    uidoc.ShowElements( idsView1 );
    uidoc.RefreshActiveView();

    //doc2.Close( false ); // Remy says: Revit throws the exception and doesn't close the file
  }
}

Toggle