Revit API offers several types of buttons, each serving a purpose in enhancing your plugin’s UI. While the PushButton is the most common, PulldownButtons, SplitButtons, and RadioButtonGroups also have specific use cases that can improve user experience.
PushButton
The PushButton is the most basic and widely used button type. It executes a specific action when clicked. We’ve covered PushButtons in detail in a previous post, including how to configure your project to attach images to buttons. You can revisit that post for a deeper dive.
To recap, start by creating a PushButtonData to define the button’s name (ID, not visible in Revit), text, and the command to execute when the button is clicked. Then, add the PushButtonData to a panel to create a PushButton. You can further customize it with tooltips, contextual help, or images.
C#
var ribbonTabName = "Revit Mastery";
application.CreateRibbonTab(ribbonTabName);
var panel = application.CreateRibbonPanel(ribbonTabName, "Revit Mastery Panel");
var assemblyPath = Assembly.GetExecutingAssembly().Location; //localization of the currently executing assembly. Don't worry, copy ;)
var assemblyDirectory = Path.GetDirectoryName(assemblyPath);
// PushButton
var pushButtonData = new PushButtonData(
"pushButton",
"Click me",
assemblyPath,
typeof(SimpleCommand).FullName);
var pushButton = panel.AddItem(pushButtonData) as PushButton;
pushButton.ToolTip = "A simple test button.";
var pushButtonContextualHelp = new ContextualHelp(ContextualHelpType.Url, "https://revitmastery.com/revit-plugin-types/");
pushButton.SetContextualHelp(pushButtonContextualHelp);
pushButton.LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "ads_click_32.png")));
The command class associated with the button must implement the IExternalCommand interface. When the button is clicked, the Execute method is executed.
Here’s a simple command that notifies the user when the button is clicked.
C#
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SimpleCommand : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
TaskDialog.Show("Simple command", "The button has been clicked.");
return Result.Succeeded;
}
}
PulldownButton
A PulldownButton allows users to select and click one of the options from a drop-down list.
To create a PulldownButton:
C#
// [...] code before
// PulldownButton
//1. Defining options (in our case, the same for PulldownButton and SplitButton)
var option1Data = new PushButtonData("option1", "Walls", assemblyPath, typeof(SelectWallsCommand).FullName);
option1Data.ToolTip = "Select walls.";
option1Data.LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "counter_1_16.png")));
var option2Data = new PushButtonData("option2", "Floors", assemblyPath, typeof(SelectFloorsCommand).FullName)
{
//a different code approach to customize the button
ToolTip = "Select floors.",
LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "counter_2_16.png"))),
};
var option3Data = new PushButtonData("option3", "Stairs", assemblyPath, typeof(SelectStairsCommand).FullName)
{
ToolTip = "Select stairs.",
LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "counter_3_16.png")))
};
// 2. Defining the PulldownButton
var pulldownButtonData = new PulldownButtonData("pulldownButton", "Select elements");
var pulldownButton = panel.AddItem(pulldownButtonData) as PulldownButton;
pulldownButton.LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "widgets_32.png")));
// 3. Adding options to the PulldownButton (with optional separator)
pulldownButton.AddPushButton(option1Data);
pulldownButton.AddPushButton(option2Data);
pulldownButton.AddSeparator();
pulldownButton.AddPushButton(option3Data);
C#
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SelectWallsCommand : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
var uiApp = commandData.Application;
var doc = uiApp.ActiveUIDocument.Document;
var selectionManager = uiApp.ActiveUIDocument.Selection;
var elementsType = typeof(Wall);
var collector = new FilteredElementCollector(doc).WhereElementIsNotElementType().OfClass(elementsType);
var collectedElementsIds = collector.ToElementIds();
selectionManager.SetElementIds(collectedElementsIds);
return Result.Succeeded;
}
}
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SelectFloorsCommand : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
var uiApp = commandData.Application;
var doc = uiApp.ActiveUIDocument.Document;
var selectionManager = uiApp.ActiveUIDocument.Selection;
var elementsType = typeof(Floor);
var collector = new FilteredElementCollector(doc).WhereElementIsNotElementType().OfClass(elementsType);
var collectedElementsIds = collector.ToElementIds();
selectionManager.SetElementIds(collectedElementsIds);
return Result.Succeeded;
}
}
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SelectStairsCommand : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
var uiApp = commandData.Application;
var doc = uiApp.ActiveUIDocument.Document;
var selectionManager = uiApp.ActiveUIDocument.Selection;
var elementsType = typeof(Stairs);
var collector = new FilteredElementCollector(doc).WhereElementIsNotElementType().OfClass(elementsType);
var collectedElementsIds = collector.ToElementIds();
selectionManager.SetElementIds(collectedElementsIds);
return Result.Succeeded;
}
}
🛈
Avoiding Repetitions in Command Classes
As you can see in the code above, each option requires a separate command class. However, the classes are almost identical; each performs the same action, differing only in the Revit element type (Walls/Floors/Stairs). It leads to repeating code, which is a bad practice.
But curating the code style is important for good programmers – like ourselves. Therefore, let’s implement the DRY (Don’t Repeat Yourself) coding principle. Using C# inheritance and generics, you can create a parent abstract class and subclasses for specific Revit element types.
C#
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SelectWallsCommand : SelectElementsOfTypeCommand<Wall>;
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SelectFloorsCommand : SelectElementsOfTypeCommand<Floor>;
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SelectStairsCommand : SelectElementsOfTypeCommand<Stairs>;
public abstract class SelectElementsOfTypeCommand<TRevitElementType> : IExternalCommand where TRevitElementType : Element
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
var uiApp = commandData.Application;
var doc = uiApp.ActiveUIDocument.Document;
var selectionManager = uiApp.ActiveUIDocument.Selection;
var elementsType = typeof(TRevitElementType);
var collector = new FilteredElementCollector(doc).WhereElementIsNotElementType().OfClass(elementsType);
var collectedElementsIds = collector.ToElementIds();
selectionManager.SetElementIds(collectedElementsIds);
return Result.Succeeded;
}
}
This approach simplifies code maintenance. Now, when you need to modify the command logic you can change the code in a single place, not in each class. Effective and error-prone.
SplitButton
The SplitButton is an enhanced version of the PulldownButton. In addition to the drop-down list of options, it also displays the last clicked (or default) option as a separate button. This allows users to quickly execute the last used command without opening the drop-down list.
Creating a SplitButton is similar to PulldownButton. The first option will appear as the default button until another option is clicked.
To create a SplitButton :
C#
// SplitButton
// 1. Defining options (in our case, the same for PulldownButton and SplitButton)
// [... preevious code]
// 2. Defining the SplitButton
var splitButtonData = new SplitButtonData("splitButton", "Split button");
var splitButton = panel.AddItem(splitButtonData) as SplitButton;
// 3. Adding options to the SplitButton (with optional separator)
splitButton.AddPushButton(option1Data);
splitButton.AddPushButton(option2Data);
splitButton.AddSeparator();
splitButton.AddPushButton(option3Data);
RadioButtonGroup
The RadioButtonGroup is a group of toggle buttons where only one button can be selected at a time. Therefore, the RadioButtonGroup performs a different role than the previous buttons. It can be used to set user preferences, which can be persisted for further use.
In our example, the user will select whether a single or all Revit elements (Walls/Floors/Stairs) should be selected when the user clicks the previous buttons.
To create a RadioButtonGroup :
C#
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class RevitMasteryApplication : IExternalApplication
{
// 4.1 Needed to access the RadioButton value elsewhere
private static RadioButtonGroup radioButton;
public static bool ShouldSingleElementBeSelected => radioButton.Current.ItemText == "Single";
public Result OnStartup(UIControlledApplication application)
{
// [... previous code]
// RadioButton
//1. Defining options
var toggleButtonData1 = new ToggleButtonData("toggleButton1", "Single")
{
ToolTip = "Set to select a single element of type.",
LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "single_16.png"))),
};
var toggleButtonData2 = new ToggleButtonData("toggleButton2", "All")
{
ToolTip = "Set to select all elements of type.",
LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "multiple_16.png"))),
};
//2. Defining the RadioButton
var radioButtonData = new RadioButtonGroupData("radioButtonGroup");
var radioButton = panel.AddItem(radioButtonData) as RadioButtonGroup;
//3. Adding options to the RadioButton
radioButton.AddItem(toggleButtonData1);
radioButton.AddItem(toggleButtonData2);
//4. Assigning the RadioButton to a global variable (to use its value elsewhere)
RevitMasteryApplication.radioButton = radioButton;
return Result.Succeeded;
}
}
Now, we can modify commands to select single/all elements, depending on the option from the RadioButtonGroup. You need to modify SelectWallsCommand, SelectFloorsCommand, and SelectStairCommand or just SelectElementsOfTypeCommand if you implemented the Don’t Repeat Yourself approach. The complete code is in the Final Code section.
The Final Code
C#
using System.IO;
using System.Reflection;
using System.Windows.Media.Imaging;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using RevitMastery.Revit.Commands;
namespace RevitMastery.Revit;
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class RevitMasteryApplication : IExternalApplication
{
// 4.1 Needed to access the RadioButton value elsewhere
private static RadioButtonGroup radioButton;
public static bool ShouldSingleElementBeSelected => radioButton.Current.ItemText == "Single";
public Result OnStartup(UIControlledApplication application)
{
var ribbonTabName = "Revit Mastery";
application.CreateRibbonTab(ribbonTabName);
var panel = application.CreateRibbonPanel(ribbonTabName, "Revit Mastery Panel");
var assemblyPath = Assembly.GetExecutingAssembly().Location; //localization of the currently executing assembly. Don't worry, copy ;)
var assemblyDirectory = Path.GetDirectoryName(assemblyPath);
// PushButton
var pushButtonData = new PushButtonData(
"pushButton",
"Click me",
assemblyPath,
typeof(SimpleCommand).FullName);
var pushButton = panel.AddItem(pushButtonData) as PushButton;
pushButton.ToolTip = "A simple test button.";
var pushButtonContextualHelp = new ContextualHelp(ContextualHelpType.Url, "https://revitmastery.com/revit-plugin-types/");
pushButton.SetContextualHelp(pushButtonContextualHelp);
pushButton.LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "ads_click_32.png")));
// PulldownButton
// 1. Defining options (in our case, the same for PulldownButton and SplitButton)
var option1Data = new PushButtonData("option1", "Walls", assemblyPath, typeof(SelectWallsCommand).FullName);
option1Data.ToolTip = "Select walls.";
option1Data.LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "counter_1_16.png")));
var option2Data = new PushButtonData("option2", "Floors", assemblyPath, typeof(SelectFloorsCommand).FullName)
{
// a different code approach to customize the button
ToolTip = "Select floors.",
LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "counter_2_16.png"))),
};
var option3Data = new PushButtonData("option3", "Stairs", assemblyPath, typeof(SelectStairsCommand).FullName)
{
ToolTip = "Select stairs.",
LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "counter_3_16.png")))
};
// 2. Defining the PulldownButton
var pulldownButtonData = new PulldownButtonData("pulldownButton", "Select elements");
var pulldownButton = panel.AddItem(pulldownButtonData) as PulldownButton;
pulldownButton.LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "widgets_32.png")));
// 3. Adding options to the PulldownButton (with optional separator)
pulldownButton.AddPushButton(option1Data);
pulldownButton.AddPushButton(option2Data);
pulldownButton.AddSeparator();
pulldownButton.AddPushButton(option3Data);
// SplitButton
//2. Defining the SplitButton
var splitButtonData = new SplitButtonData("splitButton", "Split button");
var splitButton = panel.AddItem(splitButtonData) as SplitButton;
// 3. Adding options to the SplitButton (with optional separator)
splitButton.AddPushButton(option1Data);
splitButton.AddPushButton(option2Data);
splitButton.AddSeparator();
splitButton.AddPushButton(option3Data);
// RadioButton
//1. Defining options
var toggleButtonData1 = new ToggleButtonData("toggleButton1", "Single")
{
ToolTip = "Set to select a single element of type.",
LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "single_16.png"))),
};
var toggleButtonData2 = new ToggleButtonData("toggleButton2", "All")
{
ToolTip = "Set to select all elements of type.",
LargeImage = new BitmapImage(new Uri(Path.Combine(assemblyDirectory, "Resources", "Images", "multiple_16.png"))),
};
//2. Defining the RadioButton
var radioButtonData = new RadioButtonGroupData("radioButtonGroup");
var radioButton = panel.AddItem(radioButtonData) as RadioButtonGroup;
//3. Adding options to the RadioButton
radioButton.AddItem(toggleButtonData1);
radioButton.AddItem(toggleButtonData2);
//4. Assigning the RadioButton to a global variable (to use its value elsewhere)
RevitMasteryApplication.radioButton = radioButton;
return Result.Succeeded;
}
public Result OnShutdown(UIControlledApplication application)
{
return Result.Succeeded;
}
}
C#
using Autodesk.Revit.Attributes;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Architecture;
namespace RevitMastery.Revit.Commands;
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SelectWallsCommand : SelectElementsOfTypeCommand<Wall>;
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SelectFloorsCommand : SelectElementsOfTypeCommand<Floor>;
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SelectStairsCommand : SelectElementsOfTypeCommand<Stairs>;
public abstract class SelectElementsOfTypeCommand<TRevitElementType> : IExternalCommand where TRevitElementType : Element
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
var uiApp = commandData.Application;
var doc = uiApp.ActiveUIDocument.Document;
var selectionManager = uiApp.ActiveUIDocument.Selection;
var elementsType = typeof(TRevitElementType);
var collector = new FilteredElementCollector(doc).WhereElementIsNotElementType().OfClass(elementsType);
var collectedElementsIds = collector.ToElementIds();
if (RevitMasteryApplication.ShouldSingleElementBeSelected)
{
var firstElementId = collectedElementsIds.First();
selectionManager.SetElementIds(new List<ElementId> { firstElementId });
}
else
{
selectionManager.SetElementIds(collectedElementsIds);
}
return Result.Succeeded;
}
}
Summary
By understanding and utilizing the variety of button types available in the Revit API, you can significantly enhance the user interface of your Revit plugins.
Do you want to become the Revit Programmer of the Future?