You are on page 1of 20

Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

SD11192
Advanced Revit Code RefactoringC# Language
Features and Design Patterns Can Help
David Echols Hankins & Anderson, Inc. Senior Programmer

Learning Objectives
Learn how to add extension methods to existing Revit API objects to add functionality
Learn how to utilize Action class delegates combined with lambda expressions to encapsulate
functionality and simplify code
Learn how to create LINQ expressions to select and process operations on groups of objects
Learn how to use design patterns to separate code responsibilities and provide uniform code
interfaces

Description
This class will explain how code based on the Revit software API can be refactored to simplify usage,
isolate functionality, and improve maintainability. Class material will cover the use of C# language
features and industry best practices to refactor code in Revit software add-ins and supporting library
modules. A detailed look will be taken at extension methods, the Action and Func delegate classes,
Microsoft LINQ, and design patterns. Specific before and after samples of code running in a production
add-in will be shown. Extension methods will be used to show how to attach functionality to the
responsible object. Delegates will be used to encapsulate code, making it less complex and more
maintainable. LINQ samples will demonstrate selecting and processing groups of objects. Finally, design
patterns will be explored that separate command functionality from the user interface and simplify the
creation of Revit elements.

Your AU Experts
Dave Echols began using CADD in 1986 with Prime Medusa. He started with AutoCAD in 1988 using
version 2.6. He has written programs for AutoCAD in Autolisp, C/C++, Visual Basic and .NET. He has been
developing add-ins for Revit since 2009 using the Revit .NET API.

1
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

Introduction
Technology does not stand still and this is certainly true with the Microsoft .NET framework and the
Revit API. Over the years, Microsoft has added new APIs and language features. The Revit API has
grown and improved every year with new feature. As add-in code is migrated from one version of Revit
to the next, it is beneficial to review the code and refactor it using new functionality in the API and new
functionality and language features in the .NET Framework. This class will present a number of ideas for
optimizing and refactoring your add-ins.

Learn how to add extension methods to existing Revit API objects to add
functionality
The Revit API provides us with a static interface for accessing the Revit application, user interface,
models and elements. Developers often create methods that extend functionality of specific classes
where the source code is not available. These methods can be added to new classes implementing the
Decorator or Adapter design patterns or added as static methods to a utility class. With the creation of
Extension Methods in the Microsoft .NET Framework, these methods can now be associated with
existing read only classes within static APIs.

Extension Methods used in Hankins & Anderson Revit add-Ins


Hankins & Anderson has been developing Revit add-ins since 2009. Over the years each release of
Revit has meant updating and refining this code. In order to make certain code more reusable and
organized, a number of extension methods have been created. Below is a list of some of the Revit API
classes utilizing extension methods.
BasicFileInfo
Document
ViewSheet
ModelPath
Transaction
TransactionGroup

IsOpenedAsCentral extension method for BasicFileInfo


The BasicFileInfo class provides the ability for developers to extract specific basic information
about any Revit model; opened or unopened files. A portion of Hankins & Andersons workflow
does not permit most users to open a central model directly. BasicFileInfo provides an IsCentral
property that informs the developer if a model is a central or local model. The
IsOpenedAsCentral extension method was developed to determine if the document being
opened is opening as a central model versus local or detached. The method is used in the
DocumentOpening event handler. If the user is not a power user the opening of the document is
canceled and a message is displayed to the user. The block below shows the code for the
method followed by an example of its implementation.

2
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

public static bool IsOpenedAsCentral(this BasicFileInfo basicInfo,


string documentPathName)
{
string fileName = Path.GetFileName(documentPathName).ToUpper();
string centralFile = string.Empty;
if (basicInfo.CentralPath.Length > 0)
{
centralFile = Path.GetFileName(basicInfo.CentralPath).ToUpper();
}
return centralFile.ToUpper().Equals(fileName.ToUpper());
}

FIGURE 1: THE BASICFILEINFO.ISOPENEDASCENTRAL EXTENSION METHOD

if (basicInfo.IsOpenedAsCentral(e.PathName))
{
string message = "This model is a Central File. ";
message = string.Format("{0}You do not have permission to open a ", message);
message = string.Format("{0}Central File. If you want to open the ", message);
message = string.Format("{0}file, make sure the 'Create New Local'", message);
message = string.Format("{0} checkbox is checked.", message);
TaskDialogUtility.ShowTaskDialog("Central File Error", message);
e.Cancel();
return;
}

FIGURE 2: IMPLEMENTING THE BASICFILEINFO.ISOPENEDASCENTRAL EXTENSION METHOD

GetVersionNumber extension method for BasicFileInfo


Hankins & Andersons workflow also does not permit most users to upgrade models or families.
BasicFileInfo provides a SavedInVersion property and an IsSavedInCurrentVersion property that
informs the developer if a model is the same version as the Revit software. The
GetVersionNumber method parses the return value of the SavedInVersion property and extracts
the basic version of the file. The method is used in the DocumentOpening event handler. If the
user is not a power user and the software version does not match the file version, the opening
of the document is canceled and a message is displayed to the user. The block below shows the
code for the method followed by an implementation example.

3
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

public static string GetVersionNumber(this BasicFileInfo basicInfo)


{
if (basicInfo == null) return string.Empty;

if (basicInfo.SavedInVersion.IndexOf("2013") > -1) return "2013";


// 2014 & 2015
if (basicInfo.SavedInVersion.IndexOf("2016") > -1) return "2016";

Debug.Assert(false, "Error");

return string.Empty;
}

FIGURE 5: THE BASICFILEINFO.GETVERSIONNUMBER EXTENSION METHOD

if (!basicInfo.IsSavedInCurrentVersion)
{
string message = string.Format("This model is a \"{0}\" model. ",
basicInfo.GetVersionNumber());
message = string.Format("{0}The Revit software version is {1}. ",
message, GlobalSettings.RevitVersion);
message = string.Format("{0}This model will not be opened ", message);
message = string.Format("{0}by this version of Revit. ", message);
message = string.Format("{0}Please use the Revit version", message);
message = string.Format("{0} that matches the model.", message);
TaskDialogUtility.ShowTaskDialog("Version Mismatch", message);
e.Cancel();
return;
}

FIGURE 6: IMPLEMENTING THE BASICFILEINFO.GETVERSIONNUMBER EXTENSION METHOD

IsValidForPrintingOrExport extension method for ViewSheet


Hankins & Anderson batch processes PDF exports on all the sheets in its models for Q/A check
prints and submittals. The workflow does not permit blank or placeholder sheets to be printed.
The View.CanBePrinted property does not provide enough functionality to match this workflow.
The IsValidForPrintingOrExport extension method was developed to make sure sheets with text,
annotation symbols, schedules and other printable objects are included in the logic. The method
is used in the classes that handle the DWF, DWG, Data and PDF exports. The block below shows
the code for the method with an implementation sample.

4
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

public static bool IsValidForPrintingOrExport(this ViewSheet


viewSheet)
{
if (viewSheet.IsPlaceholder) return false;
if (viewSheet.GetAllPlacedViews().Count > 0) return true;
if (viewSheet.HasSchedules()) return true;
if (viewSheet.HasTextNotes()) return true;
if (viewSheet.HasAnnotationSymbols()) return true;
if (!viewSheet.CanBePrinted) return false;
return false;
}

FIGURE 3: THE VIEWSHEET.ISVALIDFORPRINTINGOREPORT EXTENSION METHOD

foreach (ViewSheet view in viewSheetCollector)


{
if (!view.IsValidForPrintingOrExport()) continue;

PlotPdfFile(singlePdfOutputFilename, view, item)


}

FIGURE 4: IMPLEMENTING THE VIEWSHEET.ISVALIDFORPRINTINGOREPORT EXTENSION METHOD

EndTransaction extension method for TransactionViewSheet


The EndTransaction extension method provides a way to handle a RollBack or Commit operation
on an active transaction. It can also handle any type of FailureHandlingOptions if a value is
passed into the method. The methods helps minimize code repetition in transactions. The same
type of extension method can be used for the TransactionGroup and SubTransaction classes.

public static TransactionStatus


EndTransaction(this Transaction transaction,
TransactionType transactionType,
FailureHandlingOptions options = null){
TransactionStatus status = transaction.GetStatus();
if (status != TransactionStatus.Started) return status;
switch (transactionType) {
case TransactionType.Commit:
return options != null ? transaction.Commit(options)
: transaction.Commit();
case TransactionType.RollBack:
return options != null ? transaction.RollBack(options)
: transaction.RollBack();}
return TransactionStatus.Error;}

FIGURE 3: THE TRANSACTION.ENDTRANSACTION EXTENSION METHOD

5
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

Learn how to utilize Action class delegates combined with lambda expressions to
encapsulate functionality and simplify code
Delegates have been in the .NET Framework from the beginning. With the release of .Net Framework
3.5, Action and Func delegates were added along with Lambda expressions. These features can be
difficult to understand and use but can provide developers with ways to encapsulate functionality and
simplify their code. The following section will cover the transition of a standard, well used code block in
Revit to an extension method that uses an Action delegate and lambda expression

Transforming Revit Transactions


Transactions are required in Revit add-in code when the developer wants to modify elements in the
model. It may be one of the most used classes in the API. It makes sense to look at the Transaction
workflow and refactor it for simplicity and code safety.

A normal Transaction code block


The code below shows a normal Transaction code block within a try/catch block. The code is
clear about what it accomplishes. It catches any errors and rolls back the transaction if errors
occur. This code block needs to be used everywhere the enclosed code will modify the Revit
model. This is a repeated pattern for any developer who has written Revit add-ins.

Transaction transaction = null;


try
{
transaction = new Transaction(revitDoc, "SingleImport");
if (transaction.Start("SingleImport") != TransactionStatus.Started) return;
DoWorkHere();
DoMoreWorkHere();
DoEvenMoreWorkHere();
transaction.Commit();
}
catch (Exception ex) { transaction.RollBack(); }
finally { if(transaction != null) transaction.Dispose(); }

FIGURE 1: FIRST PASS AT WRITING A TRANSACTION

A Transaction enclosed in a using block


The Transaction class implements the IDisposable interface and must be instantiated with the
new keyword. This makes it a perfect candidate for placement within a using block. The block
will create the object and allow it to be used within the scope of the block. It will close the
transaction once execution has exited the block regardless of success or failure of code within
the using block. This usage promotes code safety and ensures any unmanaged resources used
by the transaction are freed. This also prepares the object for garbage collection. This code
block must still be repeated everywhere a transaction is required.

6
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

using (Transaction transaction = new Transaction(revitDoc, "SingleImport"))


{
try
{
if (transaction.Start("SingleImport") != TransactionStatus.Started) return;
DoWorkHere();
DoMoreWorkHere();
DoEvenMoreWorkHere();
transaction.Commit();
}
catch (Exception ex) { transaction.RollBack(); }
}

FIGURE 1: UTILIZING A USING BLOCK WITH A TRANSACTION

A Transaction code block using a method to reduce complexity


In the example above, there are still multiple code statements within the code block. These
statements can and should be refactored into a separate method. The code below shows the
refactored block with a single method call. This simplifies the code and makes it much easier to
understand.

using (Transaction transaction = new Transaction(revitDoc, "SingleImport"))


{
try
{
if (transaction.Start("SingleImport") != TransactionStatus.Started) return;
DoAllTheWorkHere();
transaction.Commit();
}
catch (Exception ex) { transaction.RollBack(); }
}

FIGURE 1: REFACTOR CODE WITHIN THE TRANSACTION BLOCK INTO A SINGLE METHOD

The Transaction code block converted to an extension method


The Transaction constructor always takes a Document object as the first parameter. In order to
further refactor the code, it has been converted into an extension method of the Document
class. As is, this code is very inflexible because it can perform one function; the DoAllWorkHere()
method.

7
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

public static void UsingTransaction(this Document revitDoc)


{
using (var transaction = new Transaction(revitDoc))
{
try
{
if (transaction.Start() != TransactionStatus.Started) return;
DoAllTheWorkHere();
transaction.Commit();
}
catch (Exception ex)
{
transaction.RollBack();
throw;
}
}
}

FIGURE 1: REFACTORING A TRANSACTION TO AN EXTENSION METHOD

The transaction extension method with an added Action delegate parameter


The example in the previous figure must be made more flexible to be of use. Action delegates
come to the rescue. An Action delegate is added as a parameter to the extension method. This
allows the developer to pass in any method. The Action delegate replaces the DoAllWorkHere()
method and provides unlimited flexibility.

public static void UsingTransaction(this Document revitDoc,


Action<Transaction> action)
{
using (var transaction = new Transaction(revitDoc))
{
try
{
if (transaction.Start() != TransactionStatus.Started) return;
action(transaction);
transaction.Commit();
}
catch (Exception ex)
{
transaction.RollBack();
throw;
}
}
}

FIGURE 1: THE EXTENSION METHOD USING AN ACTION DELEGATE

8
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

UsingTransaction extension method production code


The code examples above have been simplified to explain the transformation process. Below is
the production version of the Document.UsingTransaction extension method. It returns a
TransactionStatus value instead of a void return.

public static TransactionStatus UsingTransaction(this Document revitDoc,


Action<Transaction, TransactionType, FailureHandlingOptions> action,
TransactionType transactionType = TransactionType.Commit,
FailureHandlingOptions failureHandlingOptions = null,
string transactionName = null)
{
transactionName = SetTransactionName(transactionName);
using (var transaction = new Transaction(revitDoc, transactionName))
{
try
{
TransactionStatus transactionStatus = transaction.Start();
if (transactionStatus != TransactionStatus.Started) return transactionStatus;
action(transaction, transactionType, failureHandlingOptions);
return transaction.EndTransaction(transactionType, failureHandlingOptions);
}
catch (Exception ex)
{
transaction.EndTransaction(TransactionType.RollBack, failureHandlingOptions);
throw;
} } }

FIGURE 1: PRODUCTION CODE OF THE USINGTRANSACTION EXTENSION METHOD

Implementing the UsingTransaction extension method


The code snippet below shows how the UsingTransaction extension method is implemented in a
PrintPdf external command. The necessary parameters are passed in along with the Action
delegate implemented with a Lambda expression.

9
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

private void GeneratePdfFiles(string folder,


List<ElementId> ids)
{
try
{
string fileName = Path.GetFileNameWithoutExtension(RevitDoc.PathName);
Manager = new PlotManager(RevitDoc);
Setup = new PlotSetup(Manager);
RevitDoc.UsingTransaction(TransactionType.RollBack, "Print PDF Command",
(transaction, transactionType) => GeneratePdfFiles(folder, ids, fileName));
}
catch(Exception ex)
{
// Log errors and inform user
}
}

FIGURE 1: IMPLEMENTING THE EXTENSION METHOD IN ONE LINE OF CODE

Learn how to create LINQ expressions to select and process operations on groups of
objects
LINQ stands for Language Integrated Query. It made its debut the release of .Net Framework 3.5 and is
closely tied to Lambda expressions. The LINQ API allows the use of two different syntax forms; LINQ
Query syntax and LINQ Method syntax. Both forms will be covered in this class. Query syntax looks very
similar to standard SQL statements and is transformed by the compiler into the Method syntax. The
Method syntax provides a number of method calls that operate on collections of objects. These
collections are derived from the implementation of the IEnumerable<T> interface of the Enumerable
class in the System.Collections.Generic namespace. Interestingly the LINQ methods are themselves
extension methods.

LINQ Expression Usage with the REVIT API


Hankins & Anderson code uses LINQ code in several places. The Query syntax is used most of the time,
but the choice of Query versus Method syntax is mostly developer preference. Examples include both
styles so an easy comparison can be made between the two. The following sections will cover LINQ
expressions in the Revit user interface and with Revit filters.

Working with the Revit Ribbon


Hankins & Anderson utilizes a workflow that does not permit most Revit users to add a Revit
model link or AutoCAD DWG link. These functions are managed by the CADD Support
department when a project is first setup. In order to implement this workflow, the Manage Links
command is redefined to display a custom dialog box that only allows a user to reload Revit
model links. A side effect of this is the requirement to disable various ribbon items in the Ribbon
bar. The following figure show the hierarchy of ribbon objects that must be accessed to enforce
this workflow. These classes are not in the RevitAPI or RevitAPIUI assemblies; they are in the
AdWindows assembly. Each of these classes implement the IEnumerable<T> interface.

10
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

FIGURE 1: THE RIBBON CONTROL OBJECT HIERARCHY

Traversing the Ribbon Hierarchy


The code below shows how LINQ statements can be easily used to traverse the Revit ribbon
control object hierarchy. The ComponentManager.Ribbon property represents the top level
ribbon control. The GetRibbonTab method uses a tabID parameter to locate a Tab exposed in
the ribbon control. The GetTabPanel method locates the specified panel within the supplied tab.
The GetPanelItem method locates the ribbon item in the supplied panel. All three methods use
the Query syntax. The from keyword specifies the collection to be queried and the variable that
will hold the results. The where keyword specifies the search criteria to find a specific object in
the collection. The select keyword executes the query and stores the return value in the result
variable.

public RibbonTab GetRibbonTab(string tabId){


var selectedTab = from tab in ComponentManager.Ribbon.Tabs
where tab.Id.ToUpper().Equals(tabId.ToUpper())
select tab; return selectedTab.First();}

public RibbonPanel GetTabPanel(RibbonTab tab, string automationName){


var selectedPanel = from panel in tab.Panels
where panel.Source.AutomationName.ToUpper().
Equals(automationName.ToUpper())
select panel; return selectedPanel.First();}

public RibbonItem GetPanelItem(RibbonPanel panel, string id){


var selectedItem = from item in panel.Source.Items
where item.Id.ToUpper().Equals(id.ToUpper())
select item; return selectedItem.First();}

FIGURE 1: USING LINQ TO TRAVERSE THE REVIT RIBBON HIERARCHY

11
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

Using the Method Syntax


The Method syntax is used in the code below to contrast the Query method used above. The
code looks very different but returns the same result. The First method is used with a Lambda
expression that executes a Boolean predicate matching the where keyword contents shown in
the Query syntax sample.

public RibbonTab GetRibbonTabX(string tabId){


return ComponentManager.Ribbon.Tabs.First(tab =>
tab.Id.ToUpper().Equals(tabId.ToUpper()));}

public RibbonPanel GetTabPanelX(RibbonTab tab, string name){


return tab.Panels.First(panel =>
panel.Source.AutomationName.ToUpper().
Equals(name.ToUpper()));}

public RibbonItem GetPanelItemX(RibbonPanel panel, string id){


return panel.Source.Items.First(item =>
item.Id.ToUpper().Equals(id.ToUpper()));}

FIGURE 1: LINQ EXPRESSION USING THE METHOD SYNTAX

Implementing code to disable ribbon items


The code below shows how the preceding methods are used to locate specific objects in the
Revit ribbon bar and disable them during application initialization. Additional code not shown
redefines the Manage Links command and replaces it with a custom workflow.

public static class ApplicationInitializedHandler


{
public static void Handler(object sender, ApplicationInitializedEventArgs e)
{
RibbonTab tab = RevitUi.Instance.GetRibbonTab("Insert");
RibbonPanel linkPanel = RevitUi.Instance.GetTabPanel(tab, "Link");

RibbonItem revitLinkitem = RevitUi.Instance.GetPanelItem(linkPanel,


"ID_RVTDOC_LINK");
revitLinkitem.IsEnabled = false;

RibbonItem ifcLinkitem = RevitUi.Instance.GetPanelItem(linkPanel,


"ID_IFC_LINK");
ifcLinkitem.IsEnabled = false;
}
}

FIGURE 1: CODE DISABLING RIBBON ITEMS

12
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

Working with Revit filters - Views


Filters in the Revit API support the use of LINQ expressions because the *Collector* classes
return IEnumerable<T> object collections. Using the FilterElementCollector, it is very easy to
obtain a collection of views with the OfClass method. The figure below implements the method
to find all ViewSheets within the model. A LINQ expression using the Query syntax further
narrows the query results by finding ViewSheets with a SheetNumber that starts with E2.

using (FilteredElementCollector collector =


new FilteredElementCollector(revitDoc))
{
using (ElementClassFilter viewSheetFilter =
new ElementClassFilter(typeof(ViewSheet)))
{
var selectedViewSheets = from vs in collector
.WherePasses(viewSheetFilter)
.Cast<ViewSheet>()
where vs.SheetNumber.StartsWith("E2")
select vs;

foreach (ViewSheet sheet in selectedViewSheets)


{Debug.Print(sheet.SheetNumber);}
}
}

FIGURE 1: LOCATING VIEWSHEETS WITH SPECIFIC SHEET NUMBER CRITERIA

Working with Revit filters - TextNotes


Expanding on the example above, the code below uses a FilteredElementCollector to return the
ElementIds of all DraftingViews in the model. A second FilteredElementCollector collection uses
the OfClass method to collect all TextNote objects. These TextNote objects are further filtered
to a specific drafting view using the OwnedByView method. The result is a collection of all
TextNote objects in a view. The where keyword of the LINQ expression filters the list of
TextNote to all objects with a TextNoteType that is one eighth inch (1/8) in height. This type of
workflow supports locating specific text and changing the TextNoteType to a different type that
matches company standards.

13
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

using (FilteredElementCollector viewCollector =


new FilteredElementCollector(revitDoc)){
foreach (ElementId viewId in viewCollector.OfClass(typeof(ViewDrafting))
.ToElementIds())
{
using (FilteredElementCollector collector =
new FilteredElementCollector(revitDoc))
{
var selectedTextNotes = from tn in collector.OfClass(typeof(TextNote))
.OwnedByView(viewId)
.Cast<TextNote>()
where tn.TextNoteType.Name.IndexOf("1/8")>-1
select tn;
foreach (TextNote textNote in selectedTextNotes)
{ // Change to 3/32" }}}}

FIGURE 1: FILTERS IN TEXTNOTES BY A SPECIFIC TEXTNOTETYPE

Learn how to use design patterns to separate code responsibilities and provide
uniform code interfaces
The book Design Patterns: Elements of Reusable Object-Oriented Software by Gamma, Helm, Johnson
& Vlissides was first published in 1995. Design patterns in software architecture have been around for
over 2 decades. From the book: A design pattern names, abstracts and identifies the key aspects of a
common design structure that makes it useful for creating a reusable object-oriented design.. 1
It is safe to say that the developers of the Revit API have used design patterns in the implementation of
the API. Using a tool like Red Gates .Net Reflector, it is easy to spot these design patterns. Proxies are
used to handle unmanaged resources and Adapters are used to transform the interface presented by
the proxy objects into the public interface presented in the API. The
Autodesk.Revit.DB.ExtensibleStorage namespace uses the Builder pattern to build fields and schemas.
Developers using the Revit API can also use design patterns in their code.

Design Patterns in Hankins & Anderson code


Hankins & Anderson uses design patterns in its code to organize and isolate functionality and make the
code easier to maintain. The list below shows some of the patterns in use.
Singleton
Command
Factory
Adapter
Builder

Command and Factory patterns used together


The image below give an overview of how a CommandFactory object executes a
PrintPdfCommand object from within an IExternalCommand initiated by a pushbutton in the
Revit ribbon bar. Notice in the diagram the decoupling of the dialog box and the command that

14
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

interacts with Revit to produce the PDF files. This is a key benefit of using the factory pattern
with the command pattern. Internally, Revit probably uses a similar mechanism to manage the
loading and execution of external commands.

FIGURE 1: OVERVIEW OF THE COMMANDFACTORY/COMMAND IMPLEMENTATION

The IExternalCommand code


In the code below, the IExternalCommand calls the dialog box to collect information from the
user. The user selects the sheets to be exported and clicks the OK button. The CommandFactory
is then called to initiate the PrintPdfCommand. The first step is to register the PrintPdfCommand
which instantiates the object and stores it in an internal list of registered commands. The next
step is to execute the command with the data collected from the dialog box stored in an object
array. When the command finishes, the CommandFactory unregisters the command.

15
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

public Result Execute(ExternalCommandData data,


ref string msg,
ElementSet elements)
{try
{
Document revitDoc = data.Application.ActiveUIDocument.Document;
AssemblyName name = Assembly.GetExecutingAssembly().GetName();
using (PrintPdfForm frm = new PrintPdfForm(name,
revitDoc))
{
CommandFactory.GetInstance().Register(typeof (PrintPdfCommand),
"Print PDF Files");
CommandFactory.GetInstance().Execute("Print PDF Files",
new object[] {commandData,
elements,
frm.OutputFolder,
frm.SelectedSheets});
CommandFactory.GetInstance().Unregister(typeof (PrintPdfCommand),
"Print PDF Files");
}
return Result.Succeeded;
}
catch (Exception ex)
{ message = ex.Message; }
return Result.Failed;
}

FIGURE 1: IMPLEMENTATION OF THE IEXTERNALCOMMAND EXECUTE METHOD

The CommandFactory class is also a Singleton


The code below shows the CommandFactory class defined using the Singleton design pattern.
This pattern only allows one instance of an object. Within the loaded Revit add-in, one
CommandFactory object will manage the loading and execution of all Command objects. No
thread locking is needed because only one command at a time can run in the Revit user
interface.

16
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

public sealed class CommandFactory


{
private static readonly CommandFactory _instance = new CommandFactory();

private CommandFactory()
{
Commands = new List<ICommand>();
RegisteredCommands = new List<ICommand>();
}

public static CommandFactory GetInstance()


{
return _instance;
}
// ...
}

FIGURE 1: THE COMMANDFACTORY CLASS IMPLEMENTED AS A SINGLETON

Adapter pattern used with DetailLines


Hankins & Anderson has an external command that creates a drafting view from an AutoCAD
DWG file. This required developing drawing routines for the DetailCurve elements supported in
a drafting view. To create a DetailLine using the Revit API, a Line object must be instantiated
and used with the Document.Create property to create the new element. The following figure
shows an overview of the classes involved.

FIGURE 1: CLASS DIAGRAMS OF HADETAILLINE VERSUS REVIT API CLASSES

Creating a HaDetailLine object


The HaDetailLine adapter contains a single static Create method that instantiates a new
DetailLine element and adds it to a view. Using an Adapter class, a single line of code can be
used to create the element. This much simpler that using the native Revit API. Other elements,

17
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

such as the TextNote element are more complex. The following figure shows how the Create
method works by providing a clean wrapper for creating a DetailLine.

public static HaDetailLine Create(XYZ startPoint,


XYZ endPoint,
View currentView){
HaDetailLine newHaDetailLine = new HaDetailLine();
using (Line newLine = Line.CreateBound(startPoint, endPoint)){
using (DetailLine detailLine =
currentView.Document.Create
.NewDetailCurve(currentView, newLine) as DetailLine){
if (detailLine != null){
newHaDetailLine._detailLine = detailLine;
newHaDetailLine.IsInitialized = true;}}}
return newHaDetailLine;}

FIGURE 1: THE STATIC CREATE METHOD OF THE HADETAILLINE CLASS

Creating a DetailLine from an AutoCAD AcDbLine entity


The following DrawLine method shows how easy it is to create a DetailLine element using data
from An AutoCAD Line entity. The HaDetailLine adapter object is created with the static Create
method using AutoCAD line data. A line style is assigned by performing a lookup into a
ColorIndex that maps AutoCAD line thicknesses to Revit line thicknesses.

18
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

private bool DrawLine(AcadEntity entity, AcadEntity parentEntity,


XYZ startPoint, XYZ endPoint){
try
{
HaDetailLine newLine = HaDetailLine.Create(startPoint,
endpoint,
ActiveView);
newLine.AssignLineStyle(GlobalSettings.ImportLineWeights
[GetColorIndex(entity, parentEntity)],
HaLineStyles.GetLinePatternType(entity, parentEntity));
return true;
}
catch (ElementCreationException ex) { // Log Errors }
catch (Exception ex) { // Log Errors }
return false;}

FIGURE 1: CREATING A DETAILLINE FROM AUTOCAD LINE ENTITY DATA

19
Advanced Revit Code RefactoringC# Language Features and Design Patterns Can Help

References
The following references were used in the preparation of this handout.
1. Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1995). Introduction: Section 1.1. In Design
patterns: Elements of reusable object-oriented software (p. 3). Reading, MA: Addison-Wesley.

20

You might also like