Radical - UI Composition: inject content at runtime
We have seen some of the concepts behind the UI Composition features offered by the Radical framework, remember that all these concepts aims to be cross platform, currently WPF and Silverlight are supported, next will be Windows 8 Metro and Windows Phone 7.
Injection
The concept is trivial: a view defines one or more “areas” where another view can be injected.
Currently it is a really easy operation, as we have seen the MainView simply defines some regions and the MainViewModel has nothing special:
class MainViewModel : AbstractViewModel { }
Really nothing, on the other side the view that we want to inject (that is the driver of the injection) is defined as follows:
public partial class SampleContentView : UserControl { public SampleContentView() { InitializeComponent(); } }
Even here nothing special, but…let us give a wider look:
namespace Radical.Presentation.Samples.Presentation.Partial.SampleContentRegion { public partial class SampleContentView : UserControl { public SampleContentView() { InitializeComponent(); } } }
Take a look at the namespace:
- everything that is in the “*.Presentation.Partial.*” (it’s a modifiable convention) namespace will be considered a region content;
- the last token of the namespace, in this sample “SampleContentRegion” will be considered as the name of the region;
If this convention cannot satisfy the user needs (and actually there are a lot of cases where the convention cannot be used) the view to inject can be defined as follows:
[InjectViewInRegion( Named = "InnerContentRegion" )] public partial class InnerContentView : UserControl { public InnerContentView() { InitializeComponent(); } }
Using the InjectViewInRegion attribute.
Control is everything
What happens if we want to have manual control over the injection process? Simply intercept it :-)
Inside the framework the process is governed by a message (ViewModelLoaded) broadcasted by the message broker, so just handle the message and do whatever you want with your own logic:
class MainViewModelLoadedHandler : MessageHandler<ViewModelLoaded>, INeedSafeSubscription { readonly IConventionsHandler conventions; readonly IRegionService regionService; public MainViewModelLoadedHandler( IConventionsHandler conventions, IRegionService regionService ) { this.conventions = conventions; this.regionService = regionService; } protected override bool OnShouldHandle( ViewModelLoaded message ) { return message.ViewModel is MainViewModel; } public override void Handle( ViewModelLoaded message ) { var view = this.conventions.GetViewOfViewModel( message.ViewModel ); if( this.regionService.HoldsRegionManager( view ) ) { var manager = this.regionService.GetRegionManager( view ); manager.GetRegion<IElementsRegion>( "MainMenuRegion" ) .Add( new MenuItem() { Header = "Injected in main menu", Command = DelegateCommand.Create() .OnExecute( o => { MessageBox.Show( "Clicked!" ); } ) } ); manager.GetRegion<IElementsRegion>( "FileMenuRegion" ) .Add( new MenuItem() { Header = "Injected in File menu", Command = DelegateCommand.Create() .OnExecute( o => { MessageBox.Show( "Clicked!" ); } ) } ); } } }
Note: Simply dropping a class like that in the “*.Messaging.Handlers” namespace defines it as a message handler that will be automatically subscribed to the specified message.
What happens?
- Inheriting from MessageHandler<TMessage> defines that the class is a message handler of the given type TMessage;
- The INeedSafeSubscription interface is a empty marker interface that declares to the message broker that we want to be invoked in the main thread using the dispatcher;
- OnShouldHandle is a easy way to tell to the message broker that we are not interested in handling a specific message, the broker calls OnShouldHandle on every handler of the given message type before calling “Handle”;
- Handle is where the real work happens:
- given the ViewModel use the conventions the retrieve a reference to the view;
- verify if the view has an associated region manager;
- retrieve a reference to the region manager of the view;
- use the region manager to access the region by name;
- add the desired content to the region;
Disclaimer:
Currently all what seen in this post is publicly available on NuGet using a couple of pre-release package.
.m