🏗️
MauiReactor
  • What is MauiReactor?
  • What's new in Version 2
  • What's new in Version 3
  • Getting Started
  • Getting Started Version 2
  • Components
    • State-less Components
    • Stateful Components
      • Inline Components
    • Component life-cycle
    • Component Properties
    • Component with children
    • Component Parameters
    • Theming
    • Navigation
      • NavigationPage
      • Shell
      • Back button
    • Controls
      • Button
      • RadioButton
      • FlyoutPage
      • CollectionView
        • Interactions
        • Layout
        • Selection
        • Empty view
        • Scrolling
        • Grouping
      • IndicatorView
      • Picker
      • Shell
      • Label
    • Wrap 3rd party controls
      • Lottie animations
      • Provide DataTemplate to native controls
    • Accessing native controls
    • Animation
      • Property-Based
      • AnimationController
      • Timer
    • Graphics
      • CanvasView control
    • Window
    • Testing
    • XAML Integration
  • Deep dives
    • Native tree and Visual tree
    • Dependency injection
    • Working with the GraphicsView
    • Migrating from MVVM Model
    • Using XAML Resources
    • Behaviors
  • resources
    • Source and Sample Applications
  • Q&A
    • How to deal with state shared across Components?
    • Does this support ObservableCollection for CollectionView?
    • Do we need to add states to create simple animations such as ScaleTo, FadeTo, etc on tap?
    • How to deal with custom dialogs/popups?
  • How to create a Menu/ContextMenu?
Powered by GitBook
On this page
  • Shell with ShellContents
  • Shell with Tab and FlyoutItem (AsMultipleItems)
  • Custom FlyoutItem appearance
  • Custom FlyoutContent
  • Shell menu items
  • Flyout Header and Footer
  • Tabs
  • Navigation

Was this helpful?

Edit on GitHub
  1. Components
  2. Controls

Shell

This page describes how create a Shell in MauiReactor

PreviousPickerNextLabel

Last updated 11 months ago

Was this helpful?

.NET Multi-platform App UI (.NET MAUI) Shell reduces the complexity of app development by providing the fundamental features that most apps require, including:

  • A single place to describe the visual hierarchy of an app.

  • A common navigation user experience.

  • A URI-based navigation scheme that permits navigation to any page in the app.

  • An integrated search handler.

Official documentation: MauiReactor sample app:

Shell with ShellContents

The sample code below shows how to create a Shell with some pages using the ShellContent class:

class MainPageState
{
    public int Counter { get; set; }
}

class MainPage : Component<MainPageState>
{
    public override VisualNode Render()
        => new Shell
        {
            new ShellContent("Home")
                .Icon("home.png")
                .RenderContent(()=> new HomePage()),

            new ShellContent("Comments")
                .Icon("comments.png")
                .RenderContent(()=> new CommentsPage()),
        };
}

class HomePage : Component
{
    public override VisualNode Render()
    {
        return new ContentPage("Home")
        {
            new Label("Home")
                .VCenter()
                .HCenter()
        };
    }
}

class CommentsPage : Component
{
    public override VisualNode Render()
    {
        return new ContentPage("Comments")
        {
            new Label("Comments")
                .VCenter()
                .HCenter()
        };
    }
}

Shell with Tab and FlyoutItem (AsMultipleItems)

Following it's another sample of Shell with more items arranged inside a FlyoutItem and Tab:

    public override VisualNode Render()
        => new Shell
        {
            new FlyoutItem
            {
                new Tab
                {
                    new ShellContent("Home")
                        .Icon("home.png")
                        .RenderContent(()=> new HomePage()),

                    new ShellContent("Comments")
                        .Icon("comments.png")
                        .RenderContent(()=> new CommentsPage()),
                }
                .Title("Notifications")
                .Icon("bell.png"),


                new ShellContent("Home")
                    .Icon("database.png")
                    .RenderContent(()=> new DatabasePage()),

                new ShellContent("Comments")
                    .Icon("bell.png")
                    .RenderContent(()=> new NotificationsPage()),
            }
            .FlyoutDisplayOptions(MauiControls.FlyoutDisplayOptions.AsMultipleItems)
        };

Custom FlyoutItem appearance

In the following code, FlyoutItems appearance is customized:

public override VisualNode Render()
    => new Shell
    {
        new ShellContent("Home")
            .Icon("home.png")
            .RenderContent(()=> new HomePage()),

        new ShellContent("Comments")
            .Icon("comments.png")
            .RenderContent(()=> new CommentsPage()),
    }
    .ItemTemplate(RenderItemTemplate);

static VisualNode RenderItemTemplate(MauiControls.BaseShellItem item)
     => new Grid("68", "Auto, *")
     {
         new Image()
            .Source(item.FlyoutIcon)
            .Margin(4),

         new Label(item.Title)
            .GridColumn(1)
            .VCenter()
            .TextDecorations(TextDecorations.Underline)
            .FontAttributes(MauiControls.FontAttributes.Bold)
            .Margin(10,0)
     };

Custom FlyoutContent

You can also provide custom content for the Flyout as shown below:

public override VisualNode Render()
    => new Shell
    {
        new ShellContent("Home")
            .Route(nameof(HomePage))
            .Icon("home.png")
            .RenderContent(()=> new HomePage()),

        new ShellContent("Comments")
            .Route(nameof(CommentsPage))
            .Icon("comments.png")
            .RenderContent(()=> new CommentsPage()),
    }
    .FlyoutContent(RenderFlyoutContent());


VisualNode RenderFlyoutContent()
{
    return new ScrollView
    {
        new VStack(spacing: 5)
        {
            new Button("Home")
                .OnClicked(async ()=> await MauiControls.Shell.Current.GoToAsync($"//{nameof(HomePage)}")),

            new Button("Comments")
                .OnClicked(async ()=> await MauiControls.Shell.Current.GoToAsync($"//{nameof(CommentsPage)}")),
        }
    };
}

Shell menu items

You can also create a simple menu item inside the shell with a custom command:

public override VisualNode Render()
    => new Shell
    {
        new ShellContent("Home")
            .Route(nameof(HomePage))
            .Icon("home.png")
            .RenderContent(()=> new HomePage()),

        new ShellContent("Comments")
            .Route(nameof(CommentsPage))
            .Icon("comments.png")
            .RenderContent(()=> new CommentsPage()),

        new MenuItem("Click me!")
            .OnClicked(async ()=> await ContainerPage.DisplayAlert("MauiReactor", "Clicked!", "OK"))
    };

In the following sample code, we're going to customize the MenuItems:

public override VisualNode Render()
    => new Shell
    {
        new MenuItem("Click me!")
            .IconImageSource("gear.png")
            .OnClicked(async ()=> await ContainerPage.DisplayAlert("MauiReactor", "Clicked!", "OK"))
    }
    .MenuItemTemplate(menuItem =>
        new Grid("65", "Auto, *")
        {
            new Image()
                .Source(menuItem.IconImageSource)
                .VCenter(),

            new Label(menuItem.Text)
                .TextColor(Colors.Red)
                .VCenter()
                .Margin(10,0)
                .FontAttributes(MauiControls.FontAttributes.Bold)
                .GridColumn(1)
        }
        .Padding(10,0)
        );

Flyout Header and Footer

You can customize the flyout header and footer as well:

The following code uses the LinearGradient class provided by the MauiReactor framework ideal for describing a linear gradient brush in a single line

public override VisualNode Render()
    => new Shell
    {
        new ShellContent("Home")
            .Route(nameof(HomePage))
            .Icon("home.png")
            .RenderContent(()=> new HomePage()),

        new ShellContent("Comments")
            .Route(nameof(CommentsPage))
            .Icon("comments.png")
            .RenderContent(()=> new CommentsPage()),
    }
    .FlyoutBackground(new LinearGradient(45.0, new Color(255, 175, 189), new Color(100, 216, 243)))
    .FlyoutHeader(RenderHeader())
    .FlyoutFooter(RenderFooter())
    ;

private VisualNode RenderHeader()
{
    return new VStack(spacing: 5)
    {
        new Label("MauiReactor")
            .TextColor(Colors.White)
            .FontSize(24)
            .HorizontalTextAlignment(TextAlignment.Center)
            .FontAttributes(MauiControls.FontAttributes.Bold)
    };
}

private VisualNode RenderFooter()
{
    return new Image("dotnet_bot.png");
}

This is the resulting effect:

Tabs

You can create Tabs on top and bottom; just nest shell contents within Tab and TabBar objects as shown in the below example:

public override VisualNode Render()
    => new Shell
    {
        new TabBar
        {
            new ShellContent("Home")
                .Icon("home.png")
                .RenderContent(()=> new HomePage()),

            new Tab("Engage")
            {
                new ShellContent("Notifications")
                    .RenderContent(()=> new NotificationsPage()),

                new ShellContent("Comments")
                    .RenderContent(()=> new CommentsPage()),
            }
            .Icon("comments.png")
        }
    };

You can also change tab bar properties like the background color or select a specific tab. The following code shows how to:

private MauiControls.ShellContent _notificationsPage;

public override VisualNode Render()
    => new Shell
    {
        new TabBar
        {
            new ShellContent("Home")
                .Icon("home.png")
                .RenderContent(()=> new HomePage()),

            new Tab("Engage")
            {
                new ShellContent(pageRef => _notificationsPage = pageRef)
                    .Title("Notifications")
                    .RenderContent(()=> new NotificationsPage()),

                new ShellContent("Comments")
                { 
                    new ContentPage
                    {
                        new Button("Go to notifications")
                            .VCenter()
                            .HCenter()
                            .OnClicked(()=> MauiControls.Shell.Current.CurrentItem = _notificationsPage)
                    }
                }
            }
            .Icon("comments.png")
        }
        .Set(MauiControls.Shell.TabBarBackgroundColorProperty, Colors.Aquamarine)
    };

To set an attached dependency property for a control in MauiReactor you have to use the Set() method.

For example, to set the TabBarIsVisible for a ShellContent use a code like this:

Set(MauiControls.Shell.TabBarIsVisibleProperty, true)

Navigation

.NET Multi-platform App UI (.NET MAUI) Shell includes a URI-based navigation experience that uses routes to navigate to any page in the app, without having to follow a set navigation hierarchy. In addition, it also provides the ability to navigate backwards without having to visit all of the pages on the navigation stack.

MauiReactor allows the registration of components with routes just like you do with Page in normal Maui applications. To register a route you have to use the Routing.RegisterRoute<Component>("page name") method.

The following example shows how to register a few routes and how to navigate to them:

class MainPage : Component
{
    protected override void OnMounted()
    {
        Routing.RegisterRoute<Page2>(nameof(Page2));
        Routing.RegisterRoute<Page3>(nameof(Page3));

        base.OnMounted();
    }


    public override VisualNode Render()
        => new Shell
        {
            new Page1()
        };
}

class Page1 : Component
{
    public override VisualNode Render()
    {
        return new ContentPage("Page1")
        {
            new VStack
            {
                new Button("Goto Page2")
                     .OnClicked(async ()=> await MauiControls.Shell.Current.GoToAsync(nameof(Page2)))
            }
            .HCenter()
            .VCenter()
        };
    }
}

class Page2 : Component
{
    public override VisualNode Render()
    {
        return new ContentPage("Page2")
        {
            new VStack
            {
                new Button("Goto Page3")
                    .OnClicked(async ()=> await MauiControls.Shell.Current.GoToAsync(nameof(Page3)))
            }
            .HCenter()
            .VCenter()
        };
    }
}

class Page3 : Component
{
    public override VisualNode Render()
    {
        return new ContentPage("Page3")
        {
            new VStack
            {
                new Button("Open ModalPage")
                    .OnClicked(async () => await Navigation.PushModalAsync<ModalPage>())
            }
            .HCenter()
            .VCenter()
        };
    }
}

class ModalPage : Component
{
    public override VisualNode Render()
    {
        return new ContentPage("Modal Page")
        {
            new VStack
            {
                new Button("Back")
                    .OnClicked(async () => await Navigation.PopModalAsync())
            }
            .HCenter()
            .VCenter()
        };
    }
}

Passing arguments to pages (components) would mean creating a Props class for the target page and using an overload of the GotoToAsync as shown below:

class Page2 : Component
{
    public override VisualNode Render()
    {
        return new ContentPage("Page2")
        {
            new VStack
            {
                new Button("Goto Page3")
                    .OnClicked(async ()=> await MauiControls.Shell.Current.GoToAsync<PageWithArgumentsProps>(nameof(PageWithArguments), props => props.ParameterPassed = "Hello from Page2!"))
            }
            .HCenter()
            .VCenter()
        };
    }
}


class PageWithArgumentsState
{ }

class PageWithArgumentsProps
{
    public string ParameterPassed { get; set; }
}

class PageWithArguments : Component<PageWithArgumentsState, PageWithArgumentsProps>
{
    public override VisualNode Render()
    {
        return new ContentPage("PageWithArguments")
        {
            new VStack(spacing: 10)
            {
                new Label($"Parameter: {Props.ParameterPassed}")
                    .HCenter(),

                new Button("Open ModalPage")
                    .OnClicked(async () => await Navigation.PushModalAsync<ModalPage>())
            }
            //.HCenter()
            .VCenter()
        };
    }
}

If you want to pass arguments to a modal page use the overload of the Navigation.PushModalAsync<Page, Props>() method.

https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/shell/
https://github.com/adospace/mauireactor-samples/tree/main/Controls/ShellTestPage
https://github.com/adospace/mauireactor-samples/tree/main/Controls/ShellNavTestPage
Shell in action in Android
Shell in action under Windows
Shell under Windows
Shell under Android
Customized FlyoutItems
Custom Shell FlyoutContent
Custom MenuItem
Custom Flyout Header and Footer + custom background
Shell top and bottom tab bar
Custom tab bar color and selection of tab
Shell navigation in action
Passing arguments to pages