Revit API offers several types of buttons to enhance your plugin’s UI: PushButton (most common), PulldownButton, SplitButton, and RadioButtonGroup.
📌 TLDR – Summary Upfront:
➡️
Use PushButtons for single commands.
➡️
Use PulldownButtons and SplitButtons to group similar commands.
➡️
Use RadioButtonGroups to let users set preferences.
Avoid repetitions in command classes: use C# inheritance and generics to create a parent abstract class and subclasses for specific Revit element types.
PushButton
The PushButton is simple and widely used. It executes a specific action when clicked.
We’ve covered PushButtons in detail in a previous post, including how to configure projects to attach images to buttons.
To recap:
→
Create a PushButtonData to define the button’s name (ID, not visible in Revit), text, and the command to execute when the button is clicked.
→
Add the PushButtonData to a panel to create a PushButton.
→
Customize 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.
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
The PulldownButton provides a dropdown list of multiple commands.
→
Define options in the drop-down list:
For each option, create a PushButtonData specifying the button’s name (ID, not visible in Revit), text, and the command to execute when clicked. Each option can be customized with a tooltip, contextual help, or an image.
For each option, create a PushButtonData specifying the button’s name (ID, not visible in Revit), text, and the command to execute when clicked. Each option can be customized with a tooltip, contextual help, or an image.
→
Create the PulldownButton:
Create PulldownButtonData to define the button’s name (ID, not visible in Revit) and text. Add the PulldownButtonData to a panel to create the PulldownButton, which can be customized with a tooltip, contextual help, or image.
Create PulldownButtonData to define the button’s name (ID, not visible in Revit) and text. Add the PulldownButtonData to a panel to create the PulldownButton, which can be customized with a tooltip, contextual help, or image.
→
Add options to the PulldownButton.
You can also add separators to divide options in the drop-down list.
You can also add separators to divide options in the drop-down list.
C#
// [...] code before
// PulldownButton
//1. Defining options
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
Each option requires a separate command class. But the classes are almost identical; each performs the same action and differs only in the element type (Walls/Floors/Stairs). It leads to repeating code, which is a bad practice.
To overcome repetitions, use C# inheritance and generics to 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;
}
}
It simplifies code maintenance. Now, to modify logic, you 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, it displays the last clicked (or default) option as a separate button allowing quicker access.
→
Define options in the drop-down list:
For each option, create a PushButtonData specifying the button’s name (ID, not visible in Revit), text, and the command to execute when clicked. Each option can be customized with a tooltip, contextual help, or an image.
For each option, create a PushButtonData specifying the button’s name (ID, not visible in Revit), text, and the command to execute when clicked. Each option can be customized with a tooltip, contextual help, or an image.
→
Create the SplitButton :
Create SplitButtonData to define the button’s name (ID, not visible in Revit) and text. Add the SplitButtonData to a panel to create the SplitButton, which can be customized with a tooltip, contextual help, or image.
Create SplitButtonData to define the button’s name (ID, not visible in Revit) and text. Add the SplitButtonData to a panel to create the SplitButton, which can be customized with a tooltip, contextual help, or image.
→
Add options to the SplitButton.
You can also add separators to divide options in the drop-down list.
You can also add separators to divide options in the drop-down list.
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 of which only one can be selected. The RadioButtonGroup sets user preferences.
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.
→
Define ToggleButtons as options in the group:
For each option, create a ToggleButtonData specifying the toggle button’s name (ID, not visible in Revit), and text. Each option can be customized with a tooltip, contextual help, or an image.
For each option, create a ToggleButtonData specifying the toggle button’s name (ID, not visible in Revit), and text. Each option can be customized with a tooltip, contextual help, or an image.
→
Create the RadioButtonGroup:
Create RadioButtonGroupData to define the radio button’s name (ID, not visible in Revit). Add the RadioButtonGroupData to a panel to create the RadioButtonGroup.
Create RadioButtonGroupData to define the radio button’s name (ID, not visible in Revit). Add the RadioButtonGroupData to a panel to create the RadioButtonGroup.
→
Add options to the RadioButtonGroup.
→
Assign the RadioButtonGroup to a global variable:
Store the RadioButtonGroup in a private static variable, and create a public static property to retrieve the selected option.
Store the RadioButtonGroup in a private static variable, and create a public static property to retrieve the selected option.
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;
}
}
The Final Code
To select single/all elements, depending on the option from the RadioButtonGroup, we have to modify SelectWallsCommand, SelectFloorsCommand, and SelectStairCommand (or just SelectElementsOfTypeCommand in the “non-repetitions” approach).
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;
}
}