This repository contains a project template for use with Revit Dependency Injection with Unity.
The project templates are available on Nuget
- Install the latest .Net SDK
- Run
dotnet new -i Revit.DependencyInjection.Templates
to install the project templates
Just run dotnet new in a required folder.
dotnet new RevitDiUnity --name ProjectName
For dependency Injection refer to Revit Dependency Injection with Unity repo or Unity Documentation.
The template contains sample IExternalApplication
Class wrapped in RevitApp
The following method is used to register Tabs, Panels & Buttons
// This attribute is crucial and should have a unique GUID (created by template by default)
[ContainerProvider("{{GUID}}")]
public class App : RevitApp
{
public override void OnCreateRibbon(IRibbonManager ribbonManager)
{
var br = ribbonManager.GetLineBreak();
var sampleTab = ribbonManager.CreateTab("TabName");
var samplePanel = ribbonManager.CreatePanel(sampleTab.GetTabName(), "PanelName");
samplePanel.AddPushButton<ButtonClass, ButtonAvailability>($"Button{br}Name", "Image");
}
[...]
}
samplePanel.AddPushButton<ButtonClass, ButtonAvailability>($"Button{br}Name", "image");
ButtonClass
is a class implementing RevitAppCommand<App>
where <App>
is a name of the application class
ButtonAvailability
is class implementing IExternalCommandAvailability
Image
is a name of image file used for button icon. You should create two files for each button. One 32x32px & one 16x16px. Place them somewhere in Resources folder. Remember to follow the convention for naming the files "Image32" & "Image16".
public class App : RevitApp
{
[...]
public override Result OnStartup(IUnityContainer container, UIControlledApplication application)
{
// Register Your services here
container.RegisterType<ISampleService, SampleService>();
// Or use Extension Methods
container.RegisterSampleServies();
// This registers Revit Async Handler (ref. below)
container.AddRevitAsync(GetAsyncSettings);
}
[...]
}
Registering RevitAsync container.AddRevitAsync(GetAsyncSettings);
you will register an instance of IRevitEventHandler
that will allow you to execute methods within Revit Context.
As the rest of the functions here it's been created by the guys at the excellent Onbox Framework
You can inject it into any registered class by
public class SampleClass
{
private readonly IRevitEventHandler _eventHandler;
public SampleClass(IRevitEventHandler eventHandler)
{
_eventHandler = eventHandler;
}
}
Then You can run it by
// With return value
public async Task<T> GetSomethingFromRevit
{
var returnValue = await _eventHandler.RunAsync(uiApp =>
{
// Access Revit Application Classes as such
var doc = uiApp.ActiveUIDocument.Document;
return something;
});
}
// As void
public async Task DoSomethingInRevit
{
await _eventHandler.RunAsync(uiApp =>
{
// Access Revit Application Classes as such
var doc = uiApp.ActiveUIDocument.Document;
// Your Logic Here
}
}
If you prefer different implementation you could use the very popular Revit.Async,
Register an instance of RevitTask where required,
Create Your own implementation of IExternalEventHandler
or any other.
Commands should inherit from RevitAppCommand<App>
where <App> is the name of the application class.
//Remember to decorate the class with the Transaction Attribute
[Transaction(TransactionMode.Manual)]
public class SampleWindowCommand : RevitAppCommand<App>
{
public override Result Execute(IUnityContainer container, ExternalCommandData commandData, ref string message, ElementSet elements)
{
// Your Logic Here
}
}
The template uses Prism.Core library to handle MVVM implementation. You can follow Prism or any other MVVM convention.
Create a standard XAML Window & a ViewModel
in Window class inject ViewModel class
public partial class SampleWindow : Window
{
public SampleWindowViewModel ViewModel { get; }
public SampleWindow(SampleWindowViewModel viewModel)
{
ViewModel = viewModel;
InitializeComponent();
}
}
In XAML you can set data context as such
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
Then register both View & ViewModel as transient
container.RegisterType<SampleWindow>();
container.RegisterType<SampleWindowViewModel>();
Opening a windows is as simple as
var window = container.Resolve<SampleWindow>();
window.Show();
By Default Prism View Model should inherit from BindableBase
Inject any Dependencies You may need
public class SampleWindowViewModel : BindableBase
{
private readonly ISampleService _sampleService;
public SampleWindowViewModel(ISampleService sampleService)
{
_sampleService = sampleService;
}
Properties have SetProperty
from BindableBase
implementing INotifyPropertyChanged
// Example Collection property to be bound
private ObservableCollection<Element> _elements;
public ObservableCollection<Element> Elements
{
get => _elements;
set => SetProperty(ref _elements, value);
}
// Remember to Instantiate the collection!
public SampleWindowViewModel(ISampleService sampleService)
{
[...]
Elements = new ObservableCollection<Element>();
}
// Example Element property to be bound
private Element _selectedElement;
public Element SelectedElement
{
get => _selectedElement;
set => SetProperty(ref _selectedElement, value);
}
You can bind them as such
<ListView ItemsSource="{Binding Elements}"
SelectedItem="{Binding SelectedElement}">
<ListView.ItemTemplate>
<DataTemplate>
// You can access properties of a bound class in following way
<TextBlock Text="{Binding Name, Mode=OneWay}"/>
// When Binding to Revit Api Classes remember set One Way Binding as they cannot be set outside of Revit Context
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Delegate Commands are Prism Standard for ICommand
implementation
// Create Delegate Command and define Function
public DelegateCommand DoSomethingCommand { get; set; }
private async void DoSomething()
{
// Your Logic
}
public SampleWindowViewModel(ISampleService sampleService)
{
[...]
// Register Command
DoSomethingCommand = new DelegateCommand(DoSomething);
}
Binding Commands
<Button Command="{Binding DoSomethingCommand}" />
For more advanced documentation refer to https://prismlibrary.com/docs/ or Brian Lagunas Pluralsight course that goes way beyond the scope here.