I create a Xamarin.Forms app with Shell for manage TabBar and Hamburgger menu, i need that the menu items make a push as detail page for the correct navigation
I create the next code:
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Pages.BasePage"
FlyoutBehavior="Flyout"
Shell.TitleColor ="Black"
Shell.TabBarBackgroundColor="#F4F4F4"
Shell.TabBarUnselectedColor="Gray"
Shell.NavBarHasShadow="True"
Shell.PresentationMode="Animated"
x:Name="shell">
<TabBar x:Name="bottomBar" Route="main">
<Tab Title="Tab 1" Icon="img1.png">
<ShellContent Route="tab-1-page" ContentTemplate="{DataTemplate views:page1}" />
</Tab>
<Tab Title="Tab 2" Icon="img2.png">
<ShellContent Route="tab-2-page" ContentTemplate="{DataTemplate views:page2}" />
</Tab>
</TabBar>
<MenuItem Text="hamburg menu item 1"
IconImageSource="menu1.png"
Command="{Binding NavigateCommand}"
CommandParameter="menu1-page" />
<MenuItem Text="hamburg menu item 2"
IconImageSource="menu2.png"
Command="{Binding NavigateCommand}"
CommandParameter="menu2-page" />
<MenuItem Text="hamburg menu item 3"
IconImageSource="menu3.png"
Command="{Binding NavigateCommand}"
CommandParameter="menu3-page" />
<Shell.FlyoutHeader>
<StackLayout BackgroundColor="white">
<Image Source="title.png" VerticalOptions="FillAndExpand" HorizontalOptions="CenterAndExpand" WidthRequest="180" />
</StackLayout>
</Shell.FlyoutHeader>
And Codebehind is:
public BasePage()
{
InitializeComponent();
BindingContext = new BaseViewModel();
}
And ViewModel is:
using HandHeldCashier.Pages;
using System;
using System.Collections.Generic;
using System.Windows.Input;
using Xamarin.Forms;
public class BaseViewModel
{
public Dictionary<string, Type> Routes { get; private set; } = new Dictionary<string, Type>();
public ICommand NavigateCommand => new Command<string>((route) => Shell.Current.GoToAsync(route));
public BaseViewModel() { }
public void RegisterRoutes()
{
Routes.Add("detail1-page", typeof(Detail1Page));
Routes.Add("detail2-page", typeof(Detail2Page));
Routes.Add("detail3-page", typeof(Detail3Page));
Routes.Add("menu1-page", typeof(AboutPage));
Routes.Add("menu2-page", typeof(CommentsPage));
Routes.Add("menu3-page", typeof(SettingsPage));
foreach (var item in Routes)
{
Routing.RegisterRoute(item.Key, item.Value);
}
}
}
This no work at the first time, when go back and access again, it's work fine
anyone know how to replace the implementation or correct this bug?
I make a sample code to check. It works on my side. Please check it below.
Make sure you have registered the routes. Run RegisterRoutes method in BaseViewModel constructure.
public BaseViewModel()
{
RegisterRoutes();
}
public void RegisterRoutes()
{
Routes.Add("detail1-page", typeof(Detail1Page));
Routes.Add("detail2-page", typeof(Detail2Page));
Routes.Add("detail3-page", typeof(Detail3Page));
Routes.Add("menu1-page", typeof(AboutPage));
Routes.Add("menu2-page", typeof(CommentsPage));
Routes.Add("menu3-page", typeof(SettingsPage));
foreach (var item in Routes)
{
Routing.RegisterRoute(item.Key, item.Value);
}
}
For better visual effects, you could close the flyout when you do the navigation for manu items.
public ICommand NavigateCommand => new Command<string>((route) => { Shell.Current.GoToAsync(route); Shell.Current.FlyoutIsPresented = false; });
Related
I am trying to bind the ItemSelected of a ListView to a View Model, but am experiencing some issues (due to my own misunderstands around how it all works).
I have view:
<?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"
xmlns:Local="clr-namespace:FireLearn.ViewModels"
x:Class="FireLearn.MainPage"
Title="Categories">
<ContentPage.BindingContext>
<Local:CategoryViewModel/>
</ContentPage.BindingContext>
<NavigationPage.TitleView>
<Label Text="Home"/>
</NavigationPage.TitleView>
<ListView
ItemsSource="{Binding Categories}"
HasUnevenRows="True"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding ListRefreshing, Mode=OneWay}"
RefreshCommand="{Binding RefreshCommand}"
ItemSelected="{Binding OnItemTappedChanged}"
SelectionMode="Single"
SelectedItem="{Binding SelectedCategory}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<HorizontalStackLayout
Padding="8"
VerticalOptions="Fill"
HorizontalOptions="Fill">
<Image Source="cafs_bubbles.png"
HeightRequest="64"
MaximumWidthRequest="64"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"/>
<VerticalStackLayout
Padding="8"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<Label Text="{Binding FormattedName}"
SemanticProperties.HeadingLevel="Level1"
FontSize="Title"
HorizontalOptions="Start"/>
<Label Text="{Binding ItemCount}"
FontSize="Subtitle"/>
<Label Text="{Binding Description}"
HorizontalOptions="Center"
LineBreakMode="WordWrap"
FontSize="Caption"
VerticalOptions="CenterAndExpand"
MaxLines="0"/>
</VerticalStackLayout>
</HorizontalStackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
This is linked to a view model:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FireLearn.Models;
namespace FireLearn.ViewModels
{
public partial class CategoryViewModel : ObservableObject
{
public ObservableCollection<CategoryModel> categories = new ObservableCollection<CategoryModel>();
public ObservableCollection<CategoryModel> Categories
{
get => categories;
set => SetProperty(ref categories, value);
}
public bool listRefreshing = false;
public bool ListRefreshing
{
get => listRefreshing;
set => SetProperty(ref listRefreshing, value);
}
public CategoryModel selectedCategory = new CategoryModel();
public CategoryModel SelectedCategory
{
get => selectedCategory;
set
{
SetProperty(ref selectedCategory, value);
// Tap(value);
}
}
public RelayCommand RefreshCommand { get; set; }
//public RelayCommand TapCellCommand { get; set; }
public CategoryViewModel()
{
loadFromSource();
RefreshCommand = new RelayCommand(async () =>
{
Debug.WriteLine($"STARTED::{ListRefreshing}");
if (!ListRefreshing)
{
ListRefreshing = true;
try
{
await loadFromSource();
}
finally
{
ListRefreshing = false;
Debug.WriteLine($"DONE::{ListRefreshing}");
}
}
});
}
public async Task loadFromSource()
{
HttpClient httpClient = new()
{
Timeout = new TimeSpan(0, 0, 10)
};
Uri uri = new Uri("https://somewebsite.co.uk/wp-json/wp/v2/categories");
HttpResponseMessage msg = await httpClient.GetAsync(uri);
if (msg.IsSuccessStatusCode)
{
var result = CategoryModel.FromJson(await msg.Content.ReadAsStringAsync());
Categories = new ObservableCollection<CategoryModel>(result);
}
Debug.WriteLine("List Refreshed");
}
public void OnItemTappedChanged(System.Object sender, Microsoft.Maui.Controls.SelectedItemChangedEventArgs e)
{
var x = new ShellNavigationState();
Shell.Current.GoToAsync(nameof(NewPage1),
new Dictionary<string, object>
{
{
nameof(NewPage1),
SelectedCategory
}
});
}
}
}
I get compiler error "No property, BindableProperty, or event found for "ItemSelected", or mismatching type between value and property" and am really unsure of how to resolve. If I let XAML create a new event for me, it adds it in MainPage.Xaml.Cs rather than the VM
ItemSelected expects an event handler which usually only exists in the View's code behind. Since the ViewModel shouldn't know anything about the View, it's better not to mix concepts. You have a couple of options to get around this without breaking the MVVM pattern.
Option 1: Use Event Handler and invoke method of ViewModel
First, set up the code behind with the ViewModel by passing it in via the constructor and also add the event handler, e.g.:
public partial class MainPage : ContentPage
{
private CategoryViewModel _viewModel;
public MainPage(CategoryViewModel viewModel)
{
_viewModel = viewModel;
}
public void OnItemSelectedChanged(object sender, SelectedItemChangedEventArgs e)
{
//call a method from the ViewModel, e.g.
_viewModel.DoSomething(e.SelectedItem);
}
//...
}
Then use the event handler from within the XAML:
<ListView
ItemsSource="{Binding Categories}"
HasUnevenRows="True"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding ListRefreshing, Mode=OneWay}"
RefreshCommand="{Binding RefreshCommand}"
ItemSelected="OnItemSelectedChanged"
SelectionMode="Single"
SelectedItem="{Binding SelectedCategory}">
<!-- skipping irrelevant stuff -->
</ListView>
Mind that this does not use bindings.
In your CategoryViewModel you could then define a method that takes in the selected item as an argument:
public partial class CategoryViewModel : ObservableObject
{
//...
public void DoSomething(object item)
{
//do something with the item, e.g. cast it to Category
}
}
Option 2: Use EventToCommandBehavior
Instead of handling the invocation of a ViewModel method from your code behind, you could also use the EventToCommandBehavior from the MAUI Community Toolkit:
<?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"
xmlns:Local="clr-namespace:FireLearn.ViewModels"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="FireLearn.MainPage"
Title="Categories">
<ContentPage.Resources>
<ResourceDictionary>
<toolkit:SelectedItemEventArgsConverter x:Key="SelectedItemEventArgsConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<ListView
ItemsSource="{Binding Categories}"
HasUnevenRows="True"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding ListRefreshing, Mode=OneWay}"
RefreshCommand="{Binding RefreshCommand}"
SelectionMode="Single"
SelectedItem="{Binding SelectedCategory}">
<ListView.Behaviors>
<toolkit:EventToCommandBehavior
EventName="ItemSelected"
Command="{Binding ItemSelectedCommand}"
EventArgsConverter="{StaticResource SelectedItemEventArgsConverter}" />
</ListView.Behaviors>
<!-- skipping irrelevant stuff -->
</ListView>
</ContentPage>
Then, in your ViewModel, you can define the ItemSelectedCommand:
public partial class CategoryViewModel : ObservableObject
{
[RelayCommand]
private void ItemSelected(object item)
{
//do something with the item, e.g. cast it to Category
}
// ...
}
This is the preferred way to do it. Option 1 is just another possiblity, but the EventToCommandBehavior is the better choice.
Note that this is an example using MVVM Source Generators (since you're already using the MVVM Community Toolkit). The full Command would normally be implemented like this:
public partial class CategoryViewModel : ObservableObject
{
private IRelayCommand<object> _itemSelectedCommand;
public IRelayCommand<object> ItemSelectedCommand => _itemSelectedCommand ?? (_itemSelectedCommand = new RelayCommand<object>(ItemSelected));
private void ItemSelected(object item)
{
//do something with the item, e.g. cast it to Category
}
// ...
}
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("..");
}
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
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));
}
}
As I mentinoned in image discriptions, I have update register form and I want to update groups of my User, But for now I can do that with just one group. I should be able to add this User to multiple groups. For that I need multiple Pickers and like in the images I need to pop up pickers in the screen dynamically according to needs of the User. Maybe the User will want to select one group or maybe want to three groups.
What I am asking here is, How can I add this pickers or any UI element dynamically while app is running. And last question is, user can ,maybe, want to remove that second or third picker from the view. Deselect I mean. How can I do that. Thanks for ideas and codes.
There are many solutions which can implement it . For example you can check the following code .
1. create a custom view which contains the Picker
<?xml version="1.0" encoding="UTF-8"?>
<ContentView 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="xxx.PickerView">
<ContentView.Content>
<Grid HeightRequest="40">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.9*" />
<ColumnDefinition Width="0.1*" />
</Grid.ColumnDefinitions>
<Picker Grid.Column="0" x:Name="picker" Title="Select Groups" TitleColor="Red" />
<Label Grid.Column="1" Text="Canel" TextColor="Red">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped" NumberOfTapsRequired="1" />
</Label.GestureRecognizers>
</Label>
</Grid>
</ContentView.Content>
</ContentView>
using System;
using System.Collections.ObjectModel;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace xxx
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class PickerView : ContentView
{
public ObservableCollection<string> pickerSource { get; set; }
//public PickerView()
//{
// InitializeComponent();
//}
public PickerView(ObservableCollection<string> source)
{
InitializeComponent();
picker.ItemsSource = source;
}
private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
var stack = this.Parent as StackLayout;
stack.Children.Remove(this);
}
}
}
in content page
<StackLayout VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
<StackLayout x:Name="pickerStack" VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
</StackLayout>
<Button Text="+ add another group" Clicked="Button_Clicked" />
</StackLayout>
private void Button_Clicked(object sender, EventArgs e)
{
var source = new ObservableCollection<string>() {"111","222","333" };
pickerStack.Children.Add(new PickerView(source));
}