MVVM Support
This topic discusses the overall MVVM-friendly product architecture of Bars controls, along with the optional open source companion Bars MVVM library.
The MVVM library contains a full set of view model classes, template selector classes, and related view templates that support easy MVVM-based configuration and management of Bars controls. It is demonstrated in the main Bars "Document Editor" demo in the sample project, where a very large and complex ribbon hierarchy is constructed using MVVM techniques.
Bars Product Architecture
All of the Bars controls have been purposefully designed to be compatible with MVVM techniques.
Before getting into the contents of the MVVM library itself, let's examine the overall Bars product architecture and how its controls work with view models, template selectors, and view templates.
Items Controls
Many Bars controls, especially those in the ribbon hierarchy, inherit the native ItemsControl
class. This is key to the principle of supporting MVVM because it allows you to bind a collection of items (generally view models) to a control's ItemsSource
. The control's item container generator will then examine each item and if it is not an allowed "container" element for the ItemsControl
, it will ask for a "container" element to be generated. This is where classes that implement IItemContainerTemplateSelector come into play.
Item Container Template Selectors
Bars controls that derive from ItemsControl
make heavy use of classes that implement IItemContainerTemplateSelector. This interface is designed so that an item, usually a view model, is passed into a SelectTemplate method along with a reference to the owner ItemsControl
, and the method will return an IDataTemplate
to be used for that item.
The point of an IItemContainerTemplateSelector is to generate a "container" element for an ItemsControl
's item. The SelectTemplate method returns an IDataTemplate
that contains the element hierarchy to generate. The root element generated from that IDataTemplate
will be used as the "container" for the item, and the item will be set as that "container" element's DataContext
.
Ribbon Example
As an example to the above process, let's consider the Ribbon control. It inherits SelectingItemsControl
, which in turn inherits native ItemsControl
. It's set up to call into the Ribbon.ItemContainerTemplateSelector that is currently assigned (if any) to try and generate a RibbonTabItem "container" element for any item in its ItemsSource
that doesn't derive from RibbonTabItem.
Now say that a custom ribbon tab view model class (call it RibbonTabViewModel
) is the type of item bound to the ribbon's ItemsSource
, and there is one instance of the class for each tab in the ribbon.
It's the responsibility of the SelectTemplate method in the IItemContainerTemplateSelector assigned to Ribbon.ItemContainerTemplateSelector to look at the RibbonTabViewModel
and return an IDataTemplate
that contains a RibbonTabItem control at its root. The RibbonTabItem XAML in the IDataTemplate
should have bindings configured from the view model to the control.
Note
Bars controls are set up to pass an IItemContainerTemplateSelector that is assigned down to any child controls. For instance, setting the Ribbon.ItemContainerTemplateSelector property will pass that IItemContainerTemplateSelector down through tabs, groups, and even into popup menus. In this way, the same template selector instance can be reused throughout the control hierarchy.
With all of that in place, when a Ribbon sees a RibbonTabViewModel
item in its ItemsSource
, it will generate a RibbonTabItem "container" that will have its DataContext
set as the RibbonTabViewModel
instance, and the tab will appear in the ribbon UI.
Properties on the RibbonTabViewModel
may be modified, and as long as INotifyPropertyChanged
is implemented on the view model class, the property changes will flow into the generated RibbonTabItem via the bindings that were set up in the IDataTemplate
.
Tip
View model classes can inherit the ObservableObjectBase class from the Core Library to easily implement INotifyPropertyChanged
.
Gallery Item Content
RibbonGallery and BarMenuGallery are graphically-rich list controls that support selection of anything from colors to text styles. Even when a ribbon hierarchy is constructed in pure XAML, gallery controls must still use MVVM for their items.
While galleries can use logic like above with an IItemContainerTemplateSelector to generate BarGalleryItem "container" elements for their items, the actual items should always be some sort of view model. The visual content within a displayed gallery item is selected by a custom IDataTemplateSelector instance set to the gallery's ItemTemplateSelector
property. The IDataTemplateSelector.SelectTemplate method is passed the view model item and returns an IDataTemplate
to use as the UI for the gallery item.
The IDataTemplate
may contain the complete UI for the gallery item, or it may include a custom element that knows how to measure and render itself in code-behind. The DataContext
for the elements in the IDataTemplate
will be the gallery item view model, allowing for the view model's properties to be bound into any elements within the IDataTemplate
.
See the Gallery topic for more information on galleries.
Backstage Tab Content
A RibbonBackstage control is effectively a tab control where each item generates a RibbonBackstageTabItem "container" element if one isn't used directly as the item. Note that buttons and separators can also be used as backstage items.
If the RibbonBackstage.ItemsSource
is set to a collection of view models (InfoRibbonBackstageTabViewModel
, NewRibbonBackstageTabViewModel
, etc.), the RibbonBackstage.ContentTemplate
property can define the user interface for each tab's content.
Important
When using the MVVM library, the RibbonBackstage.ContentTemplateProperty
is typically assigned by setting the respective RibbonBackstageViewModel.ContentTemplate property on the view model.
One option is to assign a specialized IDataTemplate
implementation to RibbonBackstage.ContentTemplate
that can examine a backstage tab view model and provide an appropriate DataTemplate
instance to be used. For example, the TypedDataTemplateSelector class can define implicit DataTemplate
instances for each backstage tab with their x:DataType
attributes set to the related view model type. The following example demonstrates defining a TypedDataTemplateSelector as a static resource in XAML and then assigning that resource to RibbonBackstageViewModel.ContentTemplate in the code-behind:
xmlns:actipro="http://schemas.actiprosoftware.com/avaloniaui"
...
<actipro:RibbonWindow>
<actipro:RibbonWindow.Resources>
<actipro:TypedDataTemplateSelector x:Key="BackstageContentTemplates">
<DataTemplate DataType="{x:Type local:InfoRibbonBackstageTabViewModel}">
<!-- 'Info' tab content here -->
</DataTemplate>
<DataTemplate DataType="{x:Type local:NewRibbonBackstageTabViewModel}">
<!-- 'New' tab content here -->
</DataTemplate>
</actipro:TypedDataTemplateSelector>
</actipro:RibbonWindow.Resources>
<actipro:RibbonContainerPanel>
<actipro:Ribbon x:Name="ribbon" ... />
</actipro:RibbonContainerPanel>
...
</actipro:RibbonWindow>
using ActiproSoftware.UI.Avalonia.Controls.Bars;
using ActiproSoftware.UI.Avalonia.Controls.Bars.Mvvm;
...
public partial class RootWindow : RibbonWindow {
public RootWindow() {
InitializeComponent();
// Lookup the IDataTemplate for backstage content and assign
// to the RibbonBackstageViewModel.ContentTemplate property
if (ribbon.DataContext is RibbonViewModel ribbonViewModel
&& ribbonViewModel.Backstage is not null
&& this.TryFindResource("BackstageContentTemplates", out var contentTemplate)) {
ribbonViewModel.Backstage.ContentTemplate = contentTemplate as IDataTemplate;
}
}
...
}
When using the MVVM library, another option is to set either RibbonBackstageTabViewModel.Content or RibbonBackstageTabViewModel.ContentTemplate of each individual tab to the desired content or IDataTemplate
for that tab.
See the Backstage topic for more information on backstage.
Ribbon Footer Content
The RibbonFooterControl is a ContentControl
and supports standard IDataTemplate
-based content selection.
See the Footer topic for more information on ribbon footers.
Container Element Summary
This table summarizes most of the ItemsControl
-based controls in Bars and which "container" elements they expect to be generated from an IItemContainerTemplateSelector:
Open Source MVVM Library
The full source of the helpful companion MVVM library that contains Bars-related view model classes, template selector classes, and related view templates is available in GitHub at:
https://github.com/Actipro/Avalonia-Controls/tree/main/Source/Bars.Mvvm
If you wish for your Bars controls to be managed with MVVM, but don't wish to use our pre-built MVVM library, its source code is still an excellent reference for how you can integrate custom view models, template selectors, and templates with Bars controls.
MVVM Library Contents
The MVVM library contains many types and templates to assist in managing Bars control hierarchies using MVVM techniques.
Template Selectors
As described earlier in this topic, the Bars product has been designed to make use of IItemContainerTemplateSelector and IDataTemplateSelector instances to associate view models with related view templates. The MVVM library includes numerous view model types and offers several template selectors classes that map the view models to predefined view templates.
For Bar Controls
The BarControlTemplateSelector class implements IItemContainerTemplateSelector and helps generate "container" elements for all of the bar control view models described later in this topic.
When using the MVVM library with Bars controls, an instance of BarControlTemplateSelector should be set to the root bar control's ItemContainerTemplateSelector
property, such as Ribbon.ItemContainerTemplateSelector or StandaloneToolBar.ItemContainerTemplateSelector. The template selector will propagate down the hierarchy of bar controls automatically.
For Ribbon Footer Content
The RibbonFooterContentTemplateSelector class implements IDataTemplateSelector and provides the view IDataTemplate
to use for ribbon footer content view models described later in this topic that are passed into the RibbonFooterViewModel.Content property. An instance of the template selector class is automatically assigned to the RibbonFooterViewModel.ContentTemplateSelector property.
For Gallery Items
The BarGalleryItemTemplateSelector class implements IDataTemplateSelector and provides the view IDataTemplate
to use for gallery item view models described later in this topic. An instance of the template selector needs to be assigned to each gallery control, which can be done through the BarGalleryViewModel.ItemTemplateSelector property.
View Models
The MVVM library contains many view models that can drive the various Bars controls via MVVM.
Bar Control View Models
The following table shows various bar control view model types defined in the MVVM library, and maps them to the control concepts they are pre-configured to generate via a BarControlTemplateSelector instance.
Note
Some view models may generate a different control based on the usage context. For instance, a BarButtonViewModel will generate a BarButton control when in a ribbon/toolbar context, and a BarMenuItem control when in a menu context.
Ribbon Footer Content View Models
The following table shows the ribbon footer content view model types defined in the MVVM library, which are supported by the RibbonFooterContentTemplateSelector class out of the box. Instances of these view model types can be assigned to the RibbonFooterViewModel.Content property when a footer message should be displayed.
Name | Description |
---|---|
RibbonFooterInfoBarContentViewModel | Configures an InfoBar to render in the ribbon footer |
RibbonFooterSimpleContentViewModel | Specifies an IImage and text message to render in the ribbon footer. |
Gallery Item View Models
The following table shows the gallery item view model types defined in the MVVM library, which are supported by the BarGalleryItemTemplateSelector class out of the box. Instances of these view model types can be bound into the ItemsSource
of any gallery control.
Name | Description |
---|---|
IBarGalleryItemViewModel | An interface that provides the common requirements of a gallery item view model. |
BarGalleryItemViewModel<T> | Has a generic type parameter for a related value. Can be used directly or inherited for specialized gallery item view model types. |
ColorBarGalleryItemViewModel | Represents a Color value, used for any kind of color picker gallery. |
EnumBarGalleryItemViewModel<T> | Has a generic type parameter for a related enumeration value. Can be used directly or inherited for specialized gallery item view model types. See "Using EnumBarGaleryItemViewModel" section below for more details. |
FontFamilyBarGalleryItemViewModel | Represents a font family name, used for font comboboxes. |
FontSizeBarGalleryItemViewModel | Represents a font size, used for font size comboboxes. |
SymbolBarGalleryItemViewModel | Represents a symbol (e.g., copyright sign) string, used to insert a symbol into a document. Rendered by a SymbolPresenter element. |
TextBarGalleryItemViewModel | Represents a simple text string. |
TextStyleBarGalleryItemViewModel | Represents a text style for a document, which stores information like font, color, bold, etc. Rendered by a TextStylePresenter element. |
Using EnumBarGalleryItemViewModel
Instances of the EnumBarGalleryItemViewModel<T> class (where T
is an enumeration type) are typically generated by the EnumBarGalleryItemViewModel<T>.CreateCollection method. This static method uses reflection to automatically generate a view model for every value that is defined for the enumeration.
By default, the Label and Value are populated from the declaration, but attributes can also be used to define additional information, customize the label, or indicate a value should be skipped.
Attribute Class | Property Name | Description |
---|---|---|
System.ComponentModel.EditorBrowsableAttribute |
State |
When set to EditorBrowsableState.Never , the value will be ignored when generating the collection. |
System.ComponentModel.DescriptionAttribute |
Description |
Defines a custom Label replacing the declared name. |
System.ComponentModel.DataAnnotations.DisplayAttribute |
Name |
Defines a custom Label replacing the declared name. If DescriptionAttribute is also defined, this attribute property is ignored. |
System.ComponentModel.DataAnnotations.DisplayAttribute |
ShortName |
Defines a custom Label replacing the declared name. If either DescriptionAttribute or DisplayAttribute.Name is also defined, this attribute property is ignored. |
System.ComponentModel.DataAnnotations.DisplayAttribute |
GroupName |
Populates the Category property that is used for grouping. |
System.ComponentModel.DataAnnotations.DisplayAttribute |
Description |
Populates the Description property that is used for tooltips. |
System.ComponentModel.DataAnnotations.DisplayAttribute |
Order |
(Default = 10000 ) Used to determine the order in which items appear in the collection. Values lower than the default will appear before the items with default order, and those with values higher than the default will appear after the items with default order. |
Important
Since the CreateCollection method uses DisplayAttribute
to initialize some view model properties and that attribute supports localization, any view models created by this method will reflect the locale at the time they were created. If the locale changes, call EnumBarGalleryItemViewModel<T>.RefreshFromAttributes to update properties from current attribute data.
Tip
Use the EnumBarGalleryItemViewModel<T>.CreateCollection method to easily populate a Combobox with all the available values of an enumeration.
Root Bar Control Themes
The BarsMvvmResourceKeys class provides access to keys of several pre-configured ControlTheme
instances in Resources
that bind root bar controls to their related view model. It is assumed that the appropriate view model is set to the bar control's DataContext
property in your code.
Property Name | Description |
---|---|
RibbonControlTheme | Sets up bindings on a Ribbon control to a RibbonViewModel instance. |
StandaloneToolBarControlTheme | Sets up bindings on a StandaloneToolBar control to a StandaloneToolBarViewModel instance. |
Tip
It is recommended that these control themes are used on the root bar controls when using the MVVM library.
This example shows how to use a root bar control ControlTheme
on a Ribbon:
xmlns:actipro="http://schemas.actiprosoftware.com/avaloniaui"
...
<actipro:Ribbon x:Name="ribbon"
DataContext="{Binding Ribbon}"
Theme="{StaticResource {x:Static actipro:BarsMvvmResourceKeys.RibbonControlTheme}}"
/>
Bar Image Provider
The IBarImageProvider interface defines the base requirements for a class that can provide images for a given set of options.
The BarImageOptions class designates a desired image size via its Size property, whose values can be Small
(16x16), Medium
(24x24), or Large
(32x32). It also designates an optional ContextualColor that some implementations may use to show a swatch color on top of an image. This feature can be used for images that show the current text color.
The BarImageProvider class implements IBarImageProvider and allows you to Register an image factory function for a string key. The key should generally match the key for the related bar control.
The factory function is called when the GetImage method is invoked and passes a BarImageOptions instance, while expecting an IImage
in return.
This example demonstrates a registration, and assumes your application has an ImageLoader.GetIcon
method that loads the image with the specified filename:
imageProvider.Register("AlignCenter", options => ImageLoader.GetIcon("AlignTextCenter16.png"));
Call the Unregister method to later unregister a factory function for a particular key if needed.
Customizing View Models and Template Selectors
Any of the template selectors described above can be augmented in case the built-in functionality is not enough, or new view model classes are used.
Adding a View Model Property
For instance, say the BarButtonViewModel class is missing a Tag
property that you wish to set to carry other custom data. Create a class that inherits BarButtonViewModel and adds a Tag
property. Update the BarControlTemplateSelector.BarButtonDefaultTemplate property to point to a DataTemplate
that is a clone of the default DataTemplate
, but also adds a binding of the BarButton.Tag
property to the view model's Tag
property.
Third-Party Control Support
In another scenario, perhaps you wish to introduce a brand-new bar control view model class for a third-party control. Create a view model class with the properties you wish to bind to the control. Next, create a class that inherits BarControlTemplateSelector and override its SelectTemplate
method to handle the new view model as an item. It should return a new IDataTemplate
that contains the third-party control and appropriate bindings to the view model. Be sure to call the base SelectTemplate
method for other bar control view model types so they are still handled properly. Set an instance of the custom BarControlTemplateSelector-based class to all root bar controls like Ribbon or StandaloneToolBar.
New Gallery Item Kind Support
The most common customization is likely for gallery items since these tend to be more application specific. When creating a new kind of gallery item, create a view model class that inherits BarGalleryItemViewModel<T> and has whatever other properties are necessary for the gallery item to be identifiable and capable of rendering in UI. Next, create a class that inherits BarGalleryItemTemplateSelector and override its SelectTemplate
method to handle the new view model as an item. It should return a new IDataTemplate
that contains the UI elements used to render the gallery item, along with appropriate bindings to the view model. Be sure to call the base SelectTemplate
method for other gallery item view model types so they are still handled properly. Set an instance of the custom BarGalleryItemTemplateSelector-based class to any galleries that host the new kind of gallery item.