I had the same issues so far. The goals I've planned to achieve:
- The application should be extensible via plugins.
- Application plugins should be able to add their own UI content into predefined UI regions: ribbon tabs, ribbon application menu, ribbon quick access toolbar and status bar.
- The application should be built within MVVM architecture as close, as it possible.
- Plugin developer should be able to describe UI content in XAML. There shouldn't be any view model types, which reproduce hierarchy of ribbon controls (such as
RibbonTabViewModel
,RibbonButtonViewModel
, etc), because this leads to parallel hierarchy of classes, and limits the abilities of XAML. - Plugins should use MEF to be loaded into host application.
Here's the approach I've used.
All my plugins are decorated with [ApplicationExtension(...)]
and implement IApplicationExtension
:
public interface IApplicationExtension
{
void Startup();
bool CanShutdown();
void Shutdown();
}
ApplicationExtension
is declared as:
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public sealed class ApplicationExtensionAttribute : ExportAttribute, IApplicationExtensionMetadata
{
public ApplicationExtensionAttribute(/* ... */)
: base(typeof(IApplicationExtension))
{
this.Name = name;
this.UICompositionOrder = uiCompositionOrder;
}
// ...
}
IApplicationExtensionMetadata
- some metadata, such as display name, etc.
Host application, in turn, implements this interface:
public interface IApplicationInfrastructureProvider : IInfrastructureProvider
{
ICollection<ViewModel> RibbonTabs { get; }
ICollection<ViewModel> ApplicationMenuItems { get; }
ICollection<ViewModel> QuickAccessToolbarItems { get; }
ICollection<ViewModel> StatusBarItems { get; }
}
where ViewModel
- some base view model class, and loads plugins via MEF:
[ImportMany]
private IEnumerable<Lazy<IApplicationExtension, IApplicationExtensionMetadata>> Extensions { get; set; }
When application initializes, it calls Startup
method for each plugin.
Here plugin implementation is able to add tabs, menu items, etc. via IApplicationInfrastructureProvider
as view models to extend application's UI.
Note, that these view models are not any RibbontTabViewModel
or RibbonButtonViewModel
. They are just some pieces of functionality.
How it works at the UI side.
When, for example, RibbonTabs
collection is changed, application calls custom service, which performs this job:
- First, it lookups a resource dictionary for the view model. For simplicity, I'm using naming conventions: if the type of tab view model is
MyCustomVm
, the service looks forMyCustom.xaml
in the same assembly. - Second, the service explores resource dictionary and looks for resource, whose key ends with
_RibbonTabKey
. View model becomes the data context of found resource. This assumes, that resource is at leastFrameworkElement
. - Third, the found resource is being added to
RibbonTabsUI
collection of host application.
The ribbon is bound to the RibbonTabsUI
in XAML for host application:
<r:Ribbon x:Name="ribbon" Grid.Row="0" ItemsSource="{Binding RibbonTabsUI}">
<!-- other content -->
</r:Ribbon>
The sample of ribbon tab looks like this:
<r:RibbonTab x:Key="MyCustom_RibbonTabKey" x:Shared="False">
<r:RibbonGroup Header="Some group">
<!-- other content -->
</r:RibbonGroup>
<!-- other content -->
</r:RibbonTab>
ApplicationMenuItems
and QuickAccessToolbarItems
are being processed the same way, the difference is the resource key suffix.