UI Composition
Radical offers a fully flagged UI Composition engine based on the concept of regions.
A
region
is a named injectable portion of the UI where other components can inject their on content. A region is attached to a DependencyObject
on the UI, depending on the type of the object the region is attached to the region behavior changes. Radical has 3 different main region types:IContentRegion<T>
: a content region is thought for aContentPresenter
or aContentControl
UIElement, it can host one single content at a time and each time a new content is set the previous one will be removed;IElementsRegion<T>
: an elements region can host multiple contents at a time, it is thought for aPanel
UIElement, so each WPF control that inherits from panel, such as theStackPanel
, can be used with anIElementsRegion
; Content from anIElementsRegion
can be added or removed and will be available depending on the logic implemented by the underlying UIElement;ISwitchingElementsRegion<T>
: a switching elements region is an element region that, other than being able to host multiple elements at a time, has also the concept of an active element that can change over time; a typical sample is aTabControl
where eachTabItem
can be seen;
Note: each time a content is removed from a region its lifecycle is managed as every View/ViewModel:
- View and ViewModel will be released;
- If View or ViewModel implements
IDisposable
they will be disposed; - If View or ViewModel implements
IExpectViewClosedCallback
they will receive a callback notification;
Each region is characterized by 2 main attributes:
- is owned by a Region Manager, an
IRegionManager
implementation; - has a unique name in the set of regions owned by the same Region Manager;
A
RegionManager
is automatically created by the UI Composition engine as soon as a region is added to a View
, a RegionManager
is bound to a WPF Window
instance.Regions can be nested as preferred, a Window can contain a region that at runtime will contain another region and so on without limitations. For example the following is a valid
logical tree
:Window
-> Grid
-> ContentPresenter
-> IContentRegion<ContentPresenter>
-> UserControl
-> Grid
-> StackPanel
-> IElementsRegion<StackPanel>
In the above sample one single RegionManager will be created at runtime.
First define a region in the XAML where is needed and attach it to the
UIElement
that requires injection:<Window x:Class="Samples.Presentation.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:rg="http://schemas.radicalframework.com/windows/presentation/regions"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<ContentPresenter rg:RegionService.Region="{rg:ContentPresenterRegion Name=MyRegion}" />
</Grid>
</Window>
Remarks
- The
rg
namespace declaration pointing to the Radical region namespacehttp://schemas.radicalframework.com/windows/presentation/regions
; - A region is attached to a
UIElement
via theRegion
attached property of theRegionService
element; - A region is declared as a markup extension whose primary role is to define the region type and the region name;
As soon as we define a region the UI Composition engine, at runtime, will create a
RegionManager
to host the region, RegionManager whose lifecycle is bound to the lifecycle of the hosting Window
. If the region is defined in a UserControl
the RegionManager lifecycle will be bound to the lifecycle of the Window
hosting the UserControl.Once a region is defined in the XAML we need to inject some content, we can inject content in a region in 3 different ways: manually, using partial views or using a declarative approach.
Manual injection
Once a View contains a region each time the View is loaded a
ViewLoaded
message is broadcasted to notify that the View has been loaded:class MyViewLoadedHandler : MessageHandler<ViewLoaded>, INeedSafeSubscription
{
public IViewResolver ViewResolver{ get; set; }
public IConventionsHandler Conventions{ get; set; }
public IRegionService RegionService{ get; set; }
protected override bool OnShouldHandle( ViewLoaded message )
{
return message.View is Samples.Presentation.MyView;
}
public override void Handle( ViewLoaded message )
{
if ( this.RegionService.HoldsRegionManager( message.View ) )
{
var view = this.viewResolver.GetView<MyRegionView>();
var region = this.RegionService.GetRegionManager( message.View )
.GetRegion<IContentRegion>( "MyRegion" );
region.Content = view;
}
}
}
In the above sample we are defining a message handler to handle the
ViewLoaded
message, overriding the OnShouldHandle
method to define a rule to handle only the ViewLoaded event related to the View we are interested in.In the
Handle
method we utilize:- the
RegionService
to determine is the View has aRegionManager
; - if the View has a region manager
- we resolve the content to inject;
- retrieve a reference to the region manager and to the region;
- inject the content;
Resources:
Automatic (aka Partial regions)
Radical UI Composition engine has a concept called
partial view
, a partial view is a View
, and if defined its ViewModel
, that can be automatically picked up and injected based on a set of conventions:- Given a region, as in the previous XAML sample named
MyRegion
; - Given a View, and an optional ViewModel, that lives in a namespace that matches
*.Presentation.Partials.*
; - Where the last segment of the View/ViewModel namespace is the region name, in our sample MyRegion;
The View will be resolved, as usual, and injected into the expected region. Given the following namespace structure:
MySampleApp
.Presentation
.Partials
.MyRegion
.MySampleView.xaml
.MySampleViewModel.cs
The MySampleView and it ViewModel, MySampleViewModel, will be automatically injected into the MyRegion region.
Declarative
The last option, to inject a
View
in a specific region
, is to decorate the View
class with the InjectViewInRegionAttribute
:[InjectViewInRegion( Named = "MyRegion" )]
class MyUserControlView : UserControl
{
}
In the above sample, at runtime, the UI Composition engine will inject an instance of the
MyUserControlView
into the region named "MyRegion".As previously said Radical has 3 different region types:
IContentRegion<T>
, IElementsRegion<T>
and ISwitchingElementsRegion<T>
. Each region type has a default implementation.A
ContentPresenterRegion
is a IContentRegion<ContentPresenter>
that can be applied to a ContentPresenter UIElement
.A
PanelRegion
is a IElementsRegion<Panel>
, given that a Panel
is an abstract class, this region can be used with any UIElement
that inherits from Panel
, such as a StackPanel
.A
TabControlRegion
is an implementation of the ISwitchingElementsRegion<TabControl>
and can be used with a TabControl
.Last modified 1yr ago