Reputation: 104692
I have a Xamarin app made up of several page types. I'm using Prism.
I'd like the navigation target of the app to be a nested view in the app within the main page, not the main page itself, so that the MainPage
is used as an outer frame that's always there, and a nested page hosts the app's navigation service.
In this way I can have some controls (i.e. back/forward/home) always present.
How do I configure the NavigationService
's 'MainPage
' to be a nested page within the actual MainPage
?
I'm pretty new to XF, and haven't used the NavigationPage
before, but could it be of any use here?
Upvotes: 0
Views: 562
Reputation:
You don't. Prism's navigation service will not support what you are trying to do because the INavigationService
is Page
based. It sounds like your approach is very unorthodox. Why not just use a NavigationPage
and provide your options in a custom renderer or as toolbar items?
Upvotes: 1
Reputation: 5799
Prism's INavigationService
isn't designed to do what you're looking to do. Luckily custom Views are! You could do as previously suggested, but I would generally avoid creating a custom page, and instead create a custom View which would have it's own content property.
The terminology can get particularly confusing with Xamarin Forms, especially if you are new to MVVM and Xamarin Forms, and/or have moved from developing on other platforms.
What Xamarin Forms refers to as a View
is more often referred to as a Control
on other platforms. This is especially confusing when working with an MVVM framework such as Prism, because our MVVM Views
are actually Xamarin Forms Pages
.
While from time to time we need to create a custom type of Page, more often than not, we are just implementing some page type. For instance say I have a page that I am going to use for a Login Page:
LoginPage.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
Title="{Binding Title}"
x:Class="MyAwesomeApp.Views.LoginPage">
<Grid BackgroundColor="{DynamicResource primary}">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="3*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.Resources>
<ResourceDictionary>
<Style TargetType="StackLayout">
<Setter Property="BackgroundColor" Value="White" />
<Setter Property="VerticalOptions" Value="FillAndExpand" />
</Style>
<Style TargetType="Entry">
<Setter Property="Margin" Value="40,10" />
</Style>
<Style TargetType="Button">
<Setter Property="Margin" Value="20" />
<Setter Property="BackgroundColor" Value="{DynamicResource primaryDark}" />
<Setter Property="TextColor" Value="White" />
<Setter Property="BorderRadius" Value="5" />
<Setter Property="BorderWidth" Value="1" />
<Setter Property="BorderColor" Value="Black" />
</Style>
</ResourceDictionary>
</Grid.Resources>
<Image Source="logo.png"
Aspect="AspectFit"
HorizontalOptions="Center"
VerticalOptions="Center" />
<StackLayout Grid.Row="1">
<Entry Text="{Binding UserName}"
Placeholder="Enter your username"
x:Name="userNameEntry"
VerticalOptions="EndAndExpand" />
<Entry Text="{Binding Password}"
Placeholder="Enter your password"
IsPassword="True"
x:Name="passwordEntry"
VerticalOptions="StartAndExpand" />
<Button Text="Submit"
Command="{Binding LoginCommand}" />
</StackLayout>
</Grid>
</ContentPage>
LoginPage.xaml.cs
public partial class LoginPage
{
public LoginPage()
{
InitializeComponent();
}
}
While there is a lot going on in the XAML, this is just simply a page that is a ContentPage. It is NOT by any Means a Custom Page. You'll notice there is absolutely nothing in the code behind here. I might create some event handlers for instance if the user hits the Done/Enter on the keyboard that they go to the next field or it invokes a button click, but that still does not make this a Custom Page.
For an example of a "Custom Page" you can check out the XLabs ExtendedTabbedPage. One thing you'll notice is there there are several new properties on their version of the TabbedPage. Of course their page only works because of the renderer
Sticking with the example I already showed, let's say we wanted to take the Login Layout that I created in my page and wanted to be able to reuse it across projects, or perhaps on different pages of my App. Then I would want to do something like:
LoginView.xaml
<?xml version="1.0" encoding="UTF-8"?>
<Grid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
BackgroundColor="{DynamicResource primary}"
x:Name="grid"
x:Class="MyAwesomeApp.Controls.LoginView">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="3*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.Resources>
<ResourceDictionary>
<Style TargetType="StackLayout">
<Setter Property="BackgroundColor" Value="White" />
<Setter Property="VerticalOptions" Value="FillAndExpand" />
</Style>
<Style TargetType="Entry">
<Setter Property="Margin" Value="40,10" />
</Style>
<Style TargetType="Button">
<Setter Property="Margin" Value="20" />
<Setter Property="BackgroundColor" Value="{DynamicResource primaryDark}" />
<Setter Property="TextColor" Value="White" />
<Setter Property="BorderRadius" Value="5" />
<Setter Property="BorderWidth" Value="1" />
<Setter Property="BorderColor" Value="Black" />
</Style>
</ResourceDictionary>
</Grid.Resources>
<Image Source="logo.png"
Aspect="AspectFit"
HorizontalOptions="Center"
VerticalOptions="Center" />
<StackLayout Grid.Row="1" BindingContext="{x:Reference grid}">
<Entry Text="{Binding UserName}"
Placeholder="Enter your username"
x:Name="userNameEntry"
VerticalOptions="EndAndExpand" />
<Entry Text="{Binding Password}"
Placeholder="Enter your password"
IsPassword="True"
x:Name="passwordEntry"
VerticalOptions="StartAndExpand" />
<Button Text="Submit"
Command="{Binding LoginCommand}" />
</StackLayout>
</Grid>
LoginView.xaml.cs
public partial class LoginView : Grid
{
public static readonly BindableProperty UserNameProperty =
BindableProperty.Create(nameof(UserName), typeof(string), typeof(LoginView), string.Empty);
public static readonly BindableProperty PasswordProperty =
BindableProperty.Create(nameof(Password), typeof(string), typeof(LoginView), string.Empty);
public static readonly BindableProperty LoginCommandProperty =
BindableProperty.Create(nameof(LoginCommand), typeof(ICommand), typeof(LoginView), null);
public LoginView()
{
InitializeComponent();
}
public string UserName
{
get { return (string)GetValue(UserNameProperty); }
set { SetValue(UserNameProperty,value); }
}
public string Password
{
get { return (string)GetValue(PasswordProperty); }
set { SetValue(PasswordProperty, value); }
}
public ICommand LoginCommand
{
get { return (ICommand)GetValue(LoginCommandProperty); }
set { SetValue(LoginCommandProperty, value); }
}
}
Refractored LoginPage.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:MyAwesomeApp.Controls"
Title="{Binding Title}"
x:Class="MyAwesomeApp.Views.LoginPage">
<controls:LoginView UserName="{Binding UserName}"
Password="{Binding Password}"
LoginCommand="{Binding LoginCommand}" />
</ContentPage>
What you would end up with for a LoginView
would in fact be a custom control. While it might be deriving from a Grid, you generally don't care about how I decided to create the layout... You only care that I am giving you some control that has UserName
and Password
properties you can attach to and some LoginCommand
that can execute. It is that inherent difference that in fact makes it a Custom View.
Upvotes: 1
Reputation: 3388
I have one approach. Not exactly what you want, but could give you food for thought. You could create SomeBaseContentPage
with SomeContent
property:
public View SomeContent
{
set
{
someGrid.Children.Add(value);
}
}
Then derive your SomeContentPageA
from SomeBaseContentPage
and in XAML
put your specific content into property <SomeBaseContentPage.SomeContent>
.
Upvotes: 0