Shell Navigation - c#

I have a login/register page, and I want to be able to navigate between them routing navigation (//LoginPage) like in a web app. I cant really make it work. I have built a AppShell.xaml page and i have registered my LoginPage and RegisterPage in App.xaml.cs (I have tried registering them in AppShell.xaml.cs too) and I have a viewmodel that has 2 functions for navigating between login and register pages but when I click on the button to navigate I get this
System.NullReferenceException: 'Object reference not set to an instance of an object.'
Here is my view-model
namespace Appointments.ViewModels
{
public class RegLogViewModel : BaseViewModel
{
public AsyncCommand GoToRegisterCommand { get; }
public AsyncCommand GoToLoginCommand { get; }
public RegLogViewModel()
{
GoToLoginCommand = new AsyncCommand(GoToLogin);
GoToRegisterCommand = new AsyncCommand(GoToRegister);
}
async Task GoToRegister()
{
await Shell.Current.GoToAsync($"//{ nameof(RegisterPage)}");
}
async Task GoToLogin()
{
await Shell.Current.GoToAsync($"//{ nameof(LoginPage)}");
}
}
My AppShell.xaml
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Appointments.AppShell"
xmlns:views="clr-namespace:Appointments.Views">
//// I HAVE TRIED WITH AND WITHOUT THIS SHELLCONTENT\\\\\
<ShellContent Route="LoginPage" ContentTemplate="{DataTemplate views:LoginPage}"/>
<ShellContent Route="RegisterPage" ContentTemplate="{DataTemplate views:RegisterPage}"/>
</Shell>
My AppShell.xaml.cs
namespace Appointments
{
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
}
}
}
My App.xaml.cs
namespace Appointments
{
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new LoginPage();
}
}
}
And Login.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:Appointments.ViewModels"
x:DataType="viewmodels:RegLogViewModel"
x:Class="Appointments.Views.LoginPage">
<ContentPage.Content>
<StackLayout>
<Label
Text="Login Page!"
FontSize="Title"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
<Label
Text="{Binding Name}"/>
<Entry
Text="{Binding Name}"/>
<Button
Text="Go To Registration"
Command="{Binding GoToRegisterCommand}"/>
<ActivityIndicator IsRunning="{Binding IsBusy, Mode=OneWay}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>

The problem was that in App.xaml.cs I have
MainPage = new LoginPage();
but I had to use
MainPage = new AppShell();
it will work but now I don't understand how the app knows to open my LoginPage first and not RegisterPage or any other page

Related

.net MAUI, android System.InvalidOperationException: 'Native View not set' on navigation

Can't find source of evil, have two pages, added as Singleton service, when i navigate to the second page for a first time everything great, when i navigate second time i get:
System.InvalidOperationException: 'Native View not set'
MAUI version 6.0.300-rc.2.5513+sha.8a017cf73-azdo.6048189
Emulator Android11 API 30
Tested on Desktop everything seems ok...
MauiProgram.cs
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
builder.Services.AddSingleton<ShellViewModel>();
builder.Services.AddSingleton<AppShell>();
builder.Services.AddSingleton<MainPage>();
builder.Services.AddSingleton<SettingsPage>();
return builder.Build();
}
}
App.xaml - default to new project
App.cs
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new AppShell();
Routing.RegisterRoute(nameof(MainPage), typeof(MainPage));
Routing.RegisterRoute(nameof(SettingsPage), typeof(SettingsPage));
}
}
AppShell.xaml
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="MauiAppTest.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MauiAppTest"
xmlns:vm="clr-namespace:MauiAppTest.ViewModels"
x:DataType="vm:ShellViewModel"
Shell.FlyoutBehavior="Disabled">
<Shell.ToolbarItems>
<ToolbarItem Command="{Binding GoToSettings}" Text="Settings"/>
</Shell.ToolbarItems>
<ShellContent
Title="Hello, World!"
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage" />
</Shell>
AppShell.cs
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
BindingContext = new ShellViewModel();
}
}
ShellViewModel.cs
public class ShellViewModel: ObservableObject
{
public AsyncRelayCommand GoToSettings { get; private set; }
public ShellViewModel()
{
GoToSettings = new AsyncRelayCommand(async () => await Shell.Current.GoToAsync("SettingsPage"));
}
}
MainPage and SettingsPage just two default blank pages
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiAppTest.MainPage"
Title="MainPage">
<StackLayout>
<Label Text="Welcome to .NET MAUI!"
VerticalOptions="Center"
HorizontalOptions="Center" />
</StackLayout>
</ContentPage>
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiAppTest.SettingsPage"
Title="SettingsPage">
<StackLayout>
<Label Text="Welcome to .NET MAUI!"
VerticalOptions="Center"
HorizontalOptions="Center" />
</StackLayout>
</ContentPage>

New elements added to ObservableCollection from external Page are not persisted

I have two pages. On the first I want to see every elements from ObservableCollection and the second I want to add new element to ObservableCollection. When I give breakpoint I see that the ObservableCollection has new element, but when I back to the first page new element dosen't exist. What should I do?
There is code
public ListViewFlascardViewModel()
{
GoToAddFlashcard = new Command(goToAddFlashcard);
Fiszka = new ObservableCollection<Flashcard>();
Fiszka.Add(new Flashcard {Name = "hello" });
}
public Command GoToAddFlashcard { get; }
Flashcard flashcard = new Flashcard();
async void goToAddFlashcard()
{
await Shell.Current.GoToAsync(nameof(View.NoweFiszki));;
}
public ObservableCollection<Flashcard> Fiszka { get; set; }
}
And there is second page:
class NoweFiszkiViewModel
{
public Command SaveFlashcard { get; }
public NoweFiszkiViewModel()
{
SaveFlashcard = new Command(save);
}
ListViewFlascardViewModel list = new ListViewFlascardViewModel();
private async void save()
{
list.Fiszka.Add(new Flashcard { Name = "Bye" });
await Shell.Current.GoToAsync("..");
}
}
I try a lot of things, but nothing help. I am new in C# and I will be appreciated for every help.
I add whole code.
There is view
ListViewFlashcard
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:MVVM.ViewModel"
xmlns:model="clr-namespace:MVVM.Model"
x:Class="MVVM.View.ListViewFlashcard"
x:DataType="viewmodels:ListViewFlascardViewModel"
>
<ContentPage.BindingContext>
<viewmodels:ListViewFlascardViewModel/>
</ContentPage.BindingContext>
<StackLayout>
<Button
Command="{Binding GoToAddFlashcard}"
Text="Click Me"/>
<ListView
ItemsSource="{Binding Fiszka}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:Flashcard">
<TextCell Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
NoweFiszki
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:MVVM.ViewModel"
x:Class="MVVM.View.NoweFiszki">
<ContentPage.BindingContext>
<viewmodels:NoweFiszkiViewModel/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<Entry Text="{Binding Text, Mode=TwoWay}"/>
<Button Text="Save"
Command="{Binding SaveFlashcard}"/>
</StackLayout>
</ContentPage.Content>
And Model
Flashcard
public class Flashcard
{
public string Name { get; set; }
}
And AppShell
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MVVM.AppShell"
xmlns:local="clr-namespace:MVVM.View"
>
<FlyoutItem Title="Add Flashcard" Icon="icon_about.png">
<ShellContent ContentTemplate="{DataTemplate local:ListViewFlashcard}"/>
</FlyoutItem>
public partial class AppShell : Xamarin.Forms.Shell
{
public AppShell()
{
InitializeComponent();
Routing.RegisterRoute(nameof(NoweFiszki), typeof(NoweFiszki));
}
}
It is everything what I wrote.
A couple of things have to be corrected on your code:
In NoweFiszkiViewModel you are creating a new instance of ListViewFlascardViewModel every time, and adding elements on that new instance, which does not affect the instance to which ListViewFlashcard is actually bound.
To fix this, you will have to create a public static ListViewFlascardViewModel in ListViewFlashcard.xaml.cs and set the binding context to it, as follows
ListViewFlashcard.xaml.cs
public static ListViewFlascardViewModel vm { get; set; }
public ListViewFlashcard()
{
InitializeComponent();
vm = new ListViewFlascardViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
BindingContext = vm;
}
then correct ListViewFlashcard.xaml as follows
ListViewFlashcard.xaml
<!--<ContentPage.BindingContext>
<viewmodels:ListViewFlascardViewModel/>
</ContentPage.BindingContext>-->
<StackLayout>
<Button
Command="{Binding GoToAddFlashcard}"
Text="Click Me"/>
<ListView ItemsSource="{Binding Fiszka}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewmodels:Flashcard">
<TextCell Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Finally you have to correct NoweFiszkiViewModel.cs as follows, adding the new elements to the public static view model:
NoweFiszkiViewModel.cs
public Command SaveFlashcard { get; }
public NoweFiszkiViewModel()
{
SaveFlashcard = new Command(save);
}
//ListViewFlascardViewModel list = new ListViewFlascardViewModel();
private async void save()
{
ListViewFlashcard.vm.Fiszka.Add(new Flashcard { Name = "Bye" });
await Shell.Current.GoToAsync("..");
}

Bug? System.ArgumentException: 'unable to figure out route for:

Error
System.ArgumentException: 'unable to figure out route for:
//RegisterPage Parameter name: uri' System.ArgumentException: 'unable
to figure out route for: //LogoPage Parameter name: uri'
What is wrong? It cant figure out the route...?
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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="MyApp.Views.WelcomePage"
Shell.NavBarIsVisible="False">
<ContentPage.Content>
<StackLayout Padding="10,0,10,0" VerticalOptions="Center">
<Button VerticalOptions="Center" Text="Register" Command="{Binding RegisterCommand}"/>
<Button VerticalOptions="Center" Text="Login" Command="{Binding LoginCommand}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Code behind
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class WelcomePage : ContentPage
{
public WelcomePage()
{
InitializeComponent();
this.BindingContext = new WelcomeViewModel();
}
}
WelcomeViewModel.cs
public Command RegisterCommand { get; }
public Command LoginCommand { get; }
public WelcomeViewModel()
{
RegisterCommand = new Command(OnRegisterClicked);
LoginCommand = new Command(OnLoginClicked);
}
private async void OnRegisterClicked(object obj)
{
await Shell.Current.GoToAsync($"//{nameof(RegisterPage)}");
}
private async void OnLoginClicked(object obj)
{
await Shell.Current.GoToAsync($"//{nameof(LoginPage)}");
}
Your need to register a route for each page you are willing to navigate into it using Shell.Current.GoToAsync(), with this way you can also clarify your pages hierarchy:
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
<ShellContent Title="RegisterPage"
Route="RegisterPage"
ContentTemplate="{DataTemplate local:RegisterPage}"/>
<ShellContent Title="LoginPage"
Route="LoginPage"
ContentTemplate="{DataTemplate local:LoginPage}"/>
<ShellContent Title="Page3"
ContentTemplate="{DataTemplate local:Page3}"/>
</FlyoutItem>
You can also register the route using Routing.RegisterRoute() in the code if you prefer, as long as it runs before a route is invoked:
Routing.RegisterRoute("//Page3", typeof(Page3));
Microsoft Documentation
For more details: Shell Navigation
For my problem, I registered routes behind of ShellPage, It took a while to figure out and add new route to get work.
I am not sure it is the best way to register routes in AppShell.cs
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
Routing.RegisterRoute(nameof(PartPage), typeof(PartPage));
Routing.RegisterRoute(nameof(DetailPage), typeof(DetailPage));
Routing.RegisterRoute(nameof(PersonPage), typeof(PersonPage));
}
}

How to navigate between page with datas

I hope someone could help me understand how it could works..
I have a project in Xamarin Forms
I have a page1 with code below
Page1.xaml
<StackLayout BindableLayout.ItemsSource="{Binding List1}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<AbsoluteLayout>
<Button Text="{Binding NameP} Clicked="Button_Clicked"/>
</AbsoluteLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
Page1.cs
using System.Collections.Generic;
using Xamarin.Forms;
namespace app.Views
{
public partial class Page1 : ContentPage
{
public Page1()
{
InitializeComponent();
ListPrograms1 = new List<Programmes1>();
{
ListPrograms1.Add(new Programmes1() { NameP = "Tomato", Detail = "xxxxx" });
ListPrograms1.Add(new Programmes1() { NameP = "Pepperoni", Detail = "yyyyy" });
}
BindingContext = this;
}
public List<Programmes1> ListPrograms1
{
get; set;
}
public class Programmes1
{
public string NameP { get; set; }
public string Detail { get; set; }
}
public async void Button_Clicked(object sender, System.EventArgs e)
{
await Navigation.PushAsync(new Page2());
}
}
}
and in the second page
Page2.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"
x:Class="app.Views.Page2">
<StackLayout VerticalOptions="Start" HorizontalOptions="Center">
<Label Text={Binding NameP}" />
<Label Text={Binding Detail}" />
</StackLayout>
</ContentPage>
So when I click on the button, I want to go to Page2 and pass datas from the previous page or another Modelpage.
What is the good way when you have lists of datas and want to use them in several pages ?
Do you recommend use a db ?
My problem is similar as a ListView ( when you click on item , you can go to another detailpage) but here I use a bindable layout.
As Jason's reply, you can pass data to another pages using constructors.
For the first page, you can find the current select item in Button_click event.
private async void Button_Clicked(object sender, EventArgs e)
{
Button btn = (Button)sender;
Programmes1 data = ListPrograms1.Find(s => s.NameP == btn.Text);
await Navigation.PushAsync(new Page26(data));
}
Then modify the second page constructors that having one parameter.
public partial class Page26 : ContentPage
{
public Programmes1 Model { get; set; }
public Page26(Programmes1 model)
{
InitializeComponent();
Model = model;
Console.WriteLine(Model.NameP);
this.BindingContext = this;
}
}
<StackLayout HorizontalOptions="Center" VerticalOptions="Start">
<Label Text="{Binding Model.NameP}" />
<Label Text="{Binding Model.Detail}" />
</StackLayout>
Please note: you need to bind ListPrograms1 to StackLayout BindableLayout.ItemsSource, not List1, because I don't find where is List1.

Data access between .xmal and .cs in xamarin form

I have taken a label in .xmal file.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ListDemo.TableView">
<StackLayout>
<Label Text="Above TableView"></Label>
<TableView>
<TableView.Root>
<TableSection Title="Test">
<EntryCell Label="EntryCell"></EntryCell>
<TextCell Text="Test" Detail="Text Detail"></TextCell>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal" >
<BoxView Color="Red"></BoxView>
<StackLayout>
<Label Text="{Binding Receivename}"></Label>
<Label Text="News URL 1"></Label>
</StackLayout>
<BoxView x:Name="boxView" Color="Blue" ></BoxView>
</StackLayout>
</ViewCell.View>
</ViewCell>
</TableSection>
</TableView.Root>
</TableView>
</StackLayout>
I want to set the Label data from .cs file.
namespace ListDemo
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class TableView : ContentPage
{
public TableView()
{
InitializeComponent();
public string Receivename = "Hello";
}
}
}
Please let me know , how can I set the dynamic data of Label . What to write in .cs file .
Thanks in advance.
First, you can only bind to properties. So you would need:
public string Recievename { get; set; }
Second, you are setting this data in the constructor, when it should be within the scope of the actual class.
You can, however, set the value of the property in the constructor. Just not define it there.
Update per request:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class TableView : ContentPage
{
public string Receivename { get; set; }
public TableView()
{
InitializeComponent();
BindingContext = this; //Need to set data context as well, if not defined in XAML
Receivename = "Hello";
}
}
I also suggest you look more into binding, property notification, etc. This blog on xamarin should give you a hint: https://blog.xamarin.com/introduction-to-data-binding/

Categories

Resources