This post explains one concrete command—line by line—so a beginner can follow without extra abstractions.
What this command does when you click your add-in button:
Tries to create a sheet (using an invalid title block on purpose → shows how error handling works).
Collects up to 10 existing sheets and formats their numbers/names.
Displays the result in a WPF dialog via a ViewModel.
✅ This article focuses only on the command. It assumes you’ve already wired a ribbon button (and .addin) that triggers StartupCommand.
The Command (Full Code)
using Autodesk.Revit.Attributes;
using Nice3point.Revit.Toolkit.External;
using UKON.Views;
using UKON.ViewModels;
namespace UKON.Commands
{
/// <summary>
/// External command entry point
/// </summary>
[UsedImplicitly]
[Transaction(TransactionMode.Manual)]
public class StartupCommand : ExternalCommand
{
public override void Execute()
{
var viewModel = Host.GetService<UKONViewModel>();
var titleBlockId = ElementId.InvalidElementId;
string resultMessage = "";
using (var transaction = new Transaction(Document, "Create Sheet"))
{
transaction.Start();
try
{
var sheet = ViewSheet.Create(Document, titleBlockId);
transaction.Commit();
resultMessage = "Sheet created successfully!";
}
catch (Autodesk.Revit.Exceptions.ArgumentException ex)
{
transaction.RollBack();
resultMessage = $"Sheet could not be made: {ex.Message}";
}
catch (Exception ex)
{
transaction.RollBack();
resultMessage = $"Error: {ex.Message}";
}
}
// Get sheet information to display
var allSheets = new FilteredElementCollector(Document).
OfClass(typeof(ViewSheet)).
ToElements().
ToList();
if (allSheets.Count > 0)
{
var sheetNames = new List<string>();
for (int i = 0; i < Math.Min(10, allSheets.Count); i++)
{
var sheet = allSheets[i] as ViewSheet;
sheetNames.Add($"{sheet.SheetNumber} - {sheet.Name}");
}
resultMessage += $"\n\nFound {allSheets.Count} sheets:
\n" + string.Join("\n", sheetNames);
}
else
{
resultMessage += "\n\nNo sheets found in document.";
}
viewModel.ResultMessage = resultMessage;
var view = new UKONView(viewModel);
view.ShowDialog();
}
}
}
What Each Part Does (Beginner-Friendly)
1) Imports
using Autodesk.Revit.Attributes; // [Transaction(...)] attribute
using Nice3point.Revit.Toolkit.External; // ExternalCommand base + Host DI
using UKON.Views; // WPF Window (UKONView)
using UKON.ViewModels; // ViewModel (UKONViewModel)
Revit attributes let you declare transaction behaviour.
Nice3point Toolkit provides a simplified ExternalCommand base (gives you Document) and DI access via
Host.GetService<T>().
Views/ViewModels are your WPF MVVM types.
2) Class & Attributes
[UsedImplicitly]
[Transaction(TransactionMode.Manual)]
public class StartupCommand : ExternalCommand
StartupCommand runs when the user clicks your button.
[Transaction(TransactionMode.Manual)]
= you start/commit/rollback changes.ExternalCommand (Nice3point) simplifies the Revit command pattern.
UsedImplicitly is an analyzer hint (e.g., JetBrains) so tooling doesn’t flag it as “unused.”
3) Getting the ViewModel
var viewModel = Host.GetService<UKONViewModel>();
- Pulls a ViewModel instance from DI so the dialog can display results via data binding.
4) Intentionally Invalid Title Block
var titleBlockId = ElementId.InvalidElementId; // invalid on purpose
string resultMessage = "";
We force a failure to demonstrate safe error handling.
resultMessage will collect feedback for the user.
5) Transaction + Error Handling Around
ViewSheet.Create
using (var transaction = new Transaction(Document, "Create Sheet"))
{
transaction.Start();
try
{
var sheet = ViewSheet.Create(Document, titleBlockId);
transaction.Commit();
resultMessage = "Sheet created successfully!";
}
catch (Autodesk.Revit.Exceptions.ArgumentException ex)
{
transaction.RollBack();
resultMessage = $"Sheet could not be made: {ex.Message}";
}
catch (Exception ex)
{
transaction.RollBack();
resultMessage = $"Error: {ex.Message}";
}
}
Revit is read-only unless you’re inside a Transaction.
Creating a sheet with an invalid title block throws ArgumentException → we rollback safely and show the message.
Any unexpected error also triggers a rollback.
This is the core pattern you’ll reuse for any change in Revit: Start → Do work → Commit or RollBack on error.
6) Collecting Existing Sheets
var allSheets = new FilteredElementCollector(Document)
.OfClass(typeof(ViewSheet))
.ToElements()
.ToList();
Use FilteredElementCollector to find elements in the document.
Here we collect all sheets.
Then we format a short list for display:
if (allSheets.Count > 0)
{
var sheetNames = new List<string>();
for (int i = 0; i < Math.Min(10, allSheets.Count); i++)
{
var sheet = allSheets[i] as ViewSheet;
sheetNames.Add($"{sheet.SheetNumber} - {sheet.Name}");
}
resultMessage += $"\n\nFound {allSheets.Count} sheets:\n" + string.Join("\n", sheetNames);
}
else
{
resultMessage += "\n\nNo sheets found in document.";
}
We show up to 10 sheets as “A101 - Floor Plan”.
If there are none, we display that clearly.
7) Show the Result in WPF
viewModel.ResultMessage = resultMessage;
var view = new UKONView(viewModel);
view.ShowDialog();
- Set the ViewModel property (MVVM).
- Open your WPF dialog to show the message.
Why the Sheet Creation “Fails” (and How to Make It Succeed)
It fails because we passed ElementId.InvalidElementId
. To actually create a sheet, find a real title block type first:
var titleBlockId = new FilteredElementCollector(Document)
.OfCategory(BuiltInCategory.OST_TitleBlocks)
.WhereElementIsElementType()
.FirstElementId(); // throws if none exist
Replace the invalid id with this titleBlockId before ViewSheet.Create(…) and the command will create a sheet (assuming at least one title block type is loaded).
Optional Cleanups (Beginner-Safe)
- Safer casting with LINQ:
var sheets = new FilteredElementCollector(Document)
.OfClass(typeof(ViewSheet))
.Cast<ViewSheet>()
.ToList();
StringBuilder for long messages (not required, just tidy for larger outputs).
Key Takeaways
Always use a Transaction to modify the model.
Handle exceptions and rollback on failure—never leave a transaction open.
Collectors are how you find elements efficiently.
MVVM + WPF: set data on the ViewModel; the dialog updates automatically.
For creating sheets, you need a valid title block type (
OST_TitleBlocks
).
These tutorials were inspired by the work of Aussie BIM Guru. If you’re looking for a deeper dive into the topics, check out his channel for detailed explanations.