BlazorBindings.Maui v1.0
Oleksandr Liakhevych
Posted on March 7, 2023
This release is one of the biggest ones so far, and I have decided that BlazorBindings.Maui is stable enough for a 1.0 release! Take a look at Get Started page to check it out!
- About this project
- New controls supported
- Behaviors
- BlazorBindingsApplication
- Root Wrapper
- Error Boundaries
- Entry is generic
- Other changes
- Breaking changes
- What's next?
About this project
This project is a fork of MobileBlazorBindings - experimental project by Microsoft to allow to use Blazor syntax for native controls instead of XAML with MAUI. Here is a sample Counter component that renders native UI, which may look familiar to Blazor developers, that increments a value on each button press:
<StackLayout>
<Label FontSize="30">You pressed @count times </Label>
<Button Text="+1" OnClick="@HandleClick" />
</StackLayout>
@code {
int count;
void HandleClick()
{
count++;
}
}
New controls supported
This update brings the support to five more controls. That alse means that all Maui built-in controls are supported now (however, not all the functionality is supported for some controls yet).
You can take a look at ControlGallery sample to find the examples of usage for those (and other) controls.
SearchBar
SearchBar is a user input control used to initiating a search.
<SearchBar @bind-Text="_searchQuery" @bind-Text:after="UpdateSearchResults" />
<CollectionView ItemsSource="_displayedItems" />
@code {
string _searchQuery;
string[] _displayedItems;
async Task UpdateSearchResults()
{
_displayedItems = await /* retrieve items */;
}
}
IndicatorView
IndicatorView is a control that displays indicators that represent the number of items, and current position, in a CarouselView.
<CarouselView IndicatorView="() => indicatorView">
<ItemTemplate>
...
</ItemTemplate>
</CarouselView>
<IndicatorView @ref="indicatorView"
HorizontalOptions="LayoutOptions.Center"
IndicatorColor="Colors.DarkGray"
SelectedIndicatorColor="Colors.LightGray" />
@code {
IndicatorView indicatorView;
}
SwipeView
SwipeView is a container control that wraps around an item of content, and provides context menu items that are revealed by a swipe gesture.
@foreach (var item in _items)
{
<SwipeView>
<RightItems>
<SwipeItems>
<SwipeItem BackgroundColor="Colors.Yellow"
Text="Favorite"
OnInvoked="() => MakeFavorite(item)" />
<SwipeItem BackgroundColor="Colors.Red"
Text="Remove"
IsDestructive="true"
OnInvoked="() => Remove(item)" />
</SwipeItems>
</RightItems>
<ChildContent>
<ContentView BackgroundColor="Colors.Green" Padding="8">
<Label>@item</Label>
</ContentView>
</ChildContent>
</SwipeView>
}
TableView
TableView displays a table of scrollable items that can be grouped into sections. A TableView is typically used for displaying items where each row has a different appearance, such as presenting a table of settings.
<TableView>
<TableRoot>
<TableSection Title="Cells examples">
<TextCell Text="Click me" Detail="Tapping makes something happen." OnTapped="OnCellTapped" />
<EntryCell Label="Enter your name:" @bind-Text="entryText" />
<SwitchCell Text="Click to toggle:" @bind-On="switchOn" />
</TableSection>
</TableRoot>
</TableView>
ListView
ListView displays a scrollable vertical list of selectable data items. While ListView manages the appearance of the list, the appearance of each item in the list is defined by a DataTemplate that uses a Cell to display items.
<ListView ItemsSource="items" SelectionMode="ListViewSelectionMode.None">
<ItemTemplate>
<ImageCell Text="@context.Name" Detail="@context.Type" ImageSource="@context.ImageUrl">
<ContextActions>
<MenuItem Text="Remove" IsDestructive="true" OnClick="() => RemoveItem(context)" />
</ContextActions>
</ImageCell>
</ItemTemplate>
</ListView>
Behaviors
While Behaviors concept is a bit foreign with Blazor, it is an important part of the Maui ecosystem, therefore this update brings the support for Behaviors. While there are no Behavior components provided out of the box, you can create bindings for the behaviors that you need using component generator.
Here's the example for StatusBarBehavior from the MAUI Community Toolkit (you can take a look at the full sample here).
<ContentPage Title="CommunityToolkit.Behaviors">
<Behaviors>
<StatusBarBehavior StatusBarColor="Colors.Green" />
</Behaviors>
...
</ContentPage>
BlazorBindingsApplication
Previously, you had to use MauiBlazorBindingsRenderer
instance to add BlazorBindings component to your application. However, MauiBlazorBindingsRenderer
is not recommended to be used from the application code, as it is a low level implementation detail.
This release brings an easy to use BlazorBindingsApplication
type, which accept your root component type as a generic argument.
You can inherit your App type from BlazorBindingsApplication, if you need to customize the Application in any way:
public class App : BlazorBindingsApplication<AppShell>
{
public App(IServiceProvider services) : base(services)
{
// Do whatever you need (e.g. add resources).
}
}
Or you can use this type directly in simple cases:
builder.UseMauiApp<BlazorBindingsApplication<AppShell>>();
Root Wrapper
Generally, BlazorBindings' renderer creates separate render trees for each page - that includes MainPage and navigations (both regular and modals). However, in some cases it might be useful to have a component, which wraps any rendered subtree - e.g. to define global cascading parameters, or to update all the pages on some theme changed.
Now it is possible to do that via BlazorBindingsApplication
's WrapperComponentType
property.
For instance, here's an example of a wrapping component Root.razor
, which adds a global cascading value, and updates the UI based on some event:
@inject AppState AppState
<CascadingValue Value="123" Name="test">
@ChildContent
</CascadingValue>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
protected override void OnInitialized()
{
AppState.ThemeChanged += InvokeAsync(() => StateHasChanged());
}
}
Now, it is set as a wrapper component by overriding WrapperComponentType
property:
public class App : BlazorBindingsApplication<AppShell>
{
public App(IServiceProvider services) : base(services) { }
public override Type WrapperComponentType => typeof(Root);
}
Error Boundaries
One of use cases for wrapper components is to allow to set a global error boundary. It allows to handle any exception, display a nice error page, and return back to the application (instead of simply crashing the application).
[!NOTE]
Error boundaries do not allow you to ignore exceptions based on some conditions, and leave the user on a current page. Error boundary content is always replaced with an error content if any exception occurs.
Here's an example of such wrapper component:
<ErrorBoundary @ref="_errorBoundary">
<ChildContent>
@ChildContent
</ChildContent>
<ErrorContent>
<ContentPage Title="Error">
<VerticalStackLayout Margin="8" Spacing="8">
<Label Text="Oops!" HorizontalOptions="LayoutOptions.Center" FontSize="20" />
<Label Text="Unexpected error has occured:" />
<Label>@context.Message</Label>
<Button Text="Return" OnClick="Recover" />
</VerticalStackLayout>
</ContentPage>
</ErrorContent>
</ErrorBoundary>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
ErrorBoundary _errorBoundary;
void Recover() {
_errorBoundary.Recover();
}
}
[!NOTE]
You'll need to create your ErrorBoundary type, implementing ErrorBoundaryBase. You can take a look here for an example.
Entry is generic
Entry component is generic now, which allows binding the value to non-string properties, like int
or decimal
.
<Label>Int entry: @_int</Label>
<Entry @bind-Text="_int" Keyboard="Keyboard.Numeric" />
@code{
int _int;
}
[!NOTE]
While this change should not cause any troubles in most cases (since Blazor is able to infer generic argument from the@bind
directive),
it still might cause compilation errors in some cases. You can addT="string"
attribute value to resolve that.
Other changes
- Component Generator adds XML documentation now.
Breaking changes
- Due to some internal changes, components created via Component Generator have to be regenerated.
-
NativeControlComponentBase.ElementHandler
is protected now. - Control components do not inherit
ComponentBase
anymore. - Default error page is removed. Application will crash in case of unhandled exceptions now.
-
MauiBlazorBindingsRenderer
does not support rendering the component to non-Application parent. - Some methods, which are not stable yet, have [RequiresPreviewFeatures] attribute now.
- Entry component is generic now (see above).
What's next?
- Shell URL navigation improvements.
- Supporting DataTemplateSelectors.
- Supporting Maps controls, which were added in MAUI for .NET 7.
- Improving the documentation.
Posted on March 7, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.