Can not pass binding context through Navigation.pushasync with collectionview in xamarin - c#

I am new to Xamarin and am making a small prototype application. Since this is a pilot project, I am keeping it simple and not using MVVM.
Expected output: I have a collection view and when I select an item from that view, I would like to bind the data from that Item, and navigate to a new page. I want the new page to be binded with the data I selected from the collection view as there will be a couple buttons on that page with different options.
Problem: When the item is selected from the collection view, I use Navigation.pushasync to open a new page. Inside of that routing action, I set the binding context to the data from the selected item. When I navigate to the page, none of the page is populated with data from the binding context I set in the previous page.
Comment: I had this working with a list view. But I wanted more flexibility in my styles for my list, so I am trying a collection view. I am having trouble trying to understand the way to bind data with a collection list vs a listview.
Main Page View:
MainPage.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="Notes.MainPage"
Title="My Numbers">
<ContentPage.ToolbarItems>
<ToolbarItem Text="+"
Clicked="OnNumberAddedClicked" />
<ToolbarItem Text="Q"
Clicked="GetCount"/>
<ToolbarItem Text="L"
Clicked="GetLast"/>
</ContentPage.ToolbarItems>
<ContentPage.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<CollectionView x:Name="listView"
SelectionMode="Single"
ItemsSource="{Binding listView}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackLayout Orientation="Vertical"
Grid.Column="1">
<Label Text="{Binding MyNumber}" FontSize="Title"/>
<Label Text="{Binding DateAdded}" FontSize="Subtitle"/>
</StackLayout>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
</ContentPage.Content>
</ContentPage>
MainPage.xaml.cs:
using System;
using Xamarin.Forms;
using Notes.Models;
namespace Notes
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
listView.SelectionChanged += ListView_SelectionChanged;
}
async void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var num = e.CurrentSelection;
if (num != null)
{
await Navigation.PushAsync(new ActionsPage
{
BindingContext = num as NUM
});
}
}
protected override async void OnAppearing()
{
base.OnAppearing();
listView.ItemsSource = await App.Database.GetNumbersAsync();
}
}
}
Action Page View:
The circle is where the label is supposed to be, but the data won't bind.
ActionsPage.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="Notes.ActionsPage"
Title="NUM Actions">
<StackLayout>
<Label Text="{Binding myNumber}"
FontSize="Large"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center" />
<Button Text="Delete NUM"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Clicked="OnDeleteButtonClicked" />
</StackLayout>
</ContentPage>
ActionsPage.xaml.cs:
using Notes.Models;
using System;
using Xamarin.Forms;
namespace Notes
{
public partial class ActionsPage : ContentPage
{
public ActionsPage()
{
InitializeComponent();
}
async void OnDeleteButtonClicked(object sender, EventArgs e)
{
var num = (NUM)BindingContext;
await App.Database.DeleteNumAsync(num);
await Navigation.PopAsync();
}
}
}
Any help is appreciated

The simple way to pass value on navigation is just to pass an argument in the constructor.
MainPage SelectionChanged event:
private async void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var num = e.CurrentSelection[0] as NUM;
if (num != null)
{
await Navigation.PushAsync(new ActionsPage(num));
}
}
ActionPage:
public partial class ActionsPage : ContentPage
{
public int myNumber { get; set; }
public ActionsPage(NUM num)
{
InitializeComponent();
myNumber = num.MyNumber;
this.BindingContext = this;
}
async void OnDeleteButtonClicked(System.Object sender, System.EventArgs e)
{
//var num = (NUM)BindingContext;
//await App.Database.DeleteNumAsync(num);
//await Navigation.PopAsync();
}
}

Related

Xamarin forms list view items source not updating

I am having a problem with my xamarin forms list view. I have created a list view that has an items source that is binded to a List of a custom class. In the app.xaml.cs, I have the exact same list. In another form, I am updating the list that resides in app.xaml.cs. When the first form with the list view is brought back up, on appearing I set the local list that is binded to the list in app.xaml.cs. But, the items in the list view UI dont update. Can someone please help me with this?
MainPage.xaml (with list view):
`
<?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="TrackExpensesApp.MainPage">
<ContentPage.ToolbarItems>
<ToolbarItem Text="+"
Clicked="OnAddExpenseClicked" />
</ContentPage.ToolbarItems>
<ListView x:Name="ExpenseEntriesListView"
ItemsSource="{Binding ExpenseEntries}"
SelectionMode="Single"
ItemSelected="ExpenseEntriesItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Padding="10"
RowDefinitions="Auto, *"
ColumnDefinitions="Auto, *">
<Label Grid.ColumnSpan="2"
Text="{Binding Name}"
VerticalOptions="End" />
<Label Grid.Row="1"
Text="{Binding DateCreated}"
VerticalOptions="End" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Category}"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
`
MainPage.xaml.cs (with list view):
`
public partial class MainPage : ContentPage
{
public IList<ExpenseEntry> ExpenseEntries { get; set; }
public MainPage()
{
InitializeComponent();
}
async void OnAddExpenseClicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new ExpensesEntryPage());
}
protected override void OnAppearing()
{
ExpenseEntries = (Application.Current as TrackExpensesApp.App).ExpenseEntries;
BindingContext = this;
}
async void ExpenseEntriesItemSelected(object sender, SelectedItemChangedEventArgs e)
{
ExpenseEntry selectedItem = e.SelectedItem as ExpenseEntry;
await Navigation.PushAsync(new ExpensePage());
}
}
`
App.xaml.cs:
`
public partial class App : Application
{
public IList<ExpenseEntry> ExpenseEntries { get; set; }
public IList<string> Categories { get; set; }
public App()
{
InitializeComponent();
// Load in all expenses before loading in the main page
ExpenseEntries = new List<ExpenseEntry>();
Categories = new List<string>();
Categories.Add("Monthly");
Categories.Add("Vacation");
Categories.Add("Other");
MainPage = new NavigationPage(new MainPage());
BindingContext = this;
}
protected override void OnStart()
{
}
protected override void OnSleep()
{
}
protected override void OnResume()
{
}
}
`
I would truly appreciate it if someone would get back to me as soon as possible. Thank you!
We need to call OnPropertyChanged in setter method to notify that a change happened on a property ,so that the UI would change after then.
MainPage
Change your code as below
private IList<string> expenseEntries;
public IList<string> ExpenseEntries {
get
{
return expenseEntries;
}
set
{
expenseEntries = value;
OnPropertyChanged(); //add this line
}
}

ListView not filling up when I try to fill it from another page using MessagingCenter to comunicate both classes

I'm trying to fill up or populate a ListView that is instantiated in PageA when the user clicks a button in PageB. I'm doing it sending a message with the MessageCenter in PageB and calling the MessageCenter.Subscribe() method in PageA (the Page where I want to add the new ViewCell or row to the ListView)... but it's not working.
I don't think that the problem is coming from the send/subscribe usage because I have alredy debugged the application and the collection that i'm passing to the ListiView.ItemSource property is indeed growning in size. Here you can see the class definitions:
PageA class definition:
public partial class WorkoutRoutineTab : ContentPage
{
public List<Routine> Routines { get; set; } = new List<Routine>();
public WorkoutRoutineTab()
{
InitializeComponent();
routineListView.ItemsSource = Routines;
MessagingCenter.Subscribe<AddExercisePage, Routine>(this, "FillRoutines", (messageSender, arg) =>
{
Routines.Add(new Routine(arg.Name, arg.ExerciseList));
routineListView.ItemsSource = Routines;
});
}
private async void NewRoutineButton_Clicked(object sender, EventArgs e)
{
await DisplayAlert("ALERT", Routines.Count.ToString() , "ok");
await Navigation.PushAsync(new ExerciseBankTab() { Title = "" });
}
PageA .xaml file:
<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"
xmlns:local="clr-namespace:PumpFit"
xmlns:custom="clr-namespace:PumpFit.Entity"
mc:Ignorable="d"
x:Class="PumpFit.WorkoutRoutineTab"
Title="Workout"
BackgroundColor="#343434">
<StackLayout x:Name="routineStackLayout" Orientation="Vertical" HorizontalOptions="Center" VerticalOptions="Center" Margin="20,10">
<ListView x:Name="routineListView" x:FieldModifier="public" SeparatorColor="#2C2C2C">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid RowDefinitions="2*,*" ColumnDefinitions="*,*">
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding custom:Name}" TextColor="White" FontSize="Large" FontFamily="Ubuntu"/>
<Label Grid.Row="1" Grid.Column="0" Text="{Binding custom:TimesDone}" TextColor="#9F9F9F" FontSize="Body" FontFamily="Ubuntu"/>
<Label Grid.Row="1" Grid.Column="1" Text="{Binding custom:TimesDone}" TextColor="#9F9F9F" FontSize="Body" FontFamily="Ubuntu"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button x:Name="newRoutineButton" Text="New routine" FontSize="Body" FontFamily="Geo"
BackgroundColor="#2C2C2C" TextColor="#87BC72" Clicked="NewRoutineButton_Clicked" />
</StackLayout>
</ContentPage>
PageB class definition:
public partial class AddExercisePage : ContentPage
{
public Exercise newExercise;
public AddExercisePage(Exercise selectedExercise)
{
InitializeComponent();
newExercise = selectedExercise;
nameLabel.Text = newExercise.Name;
}
private async void CancelButton_Clicked(object sender, EventArgs e)
{
await Navigation.PopModalAsync();
}
private void AddButton_Clicked(object sender, EventArgs e)
{
if(setsEntry.Text != null && repsEntry.Text != null && restTimePicker.SelectedItem != null)
{
if (int.TryParse(setsEntry.Text, out int sets) && int.TryParse(repsEntry.Text, out int reps) && sets > 0 && reps > 0)
{
List<Exercise> newExerciseList = new List<Exercise>()
{
new Exercise(newExercise.Name, newExercise.MuscleGroup, newExercise.ExerciseDifficulty, newExercise.Equipment, newExercise.Description, sets, reps, restTimePicker.SelectedItem.ToString())
};
MessagingCenter.Send<AddExercisePage, Routine>(this, "FillRoutines", new Routine(routineNameEntry.Text, newExerciseList));
}
else
{
DisplayAlert("ERROR", "You must only enter positive numbers", "OK");
}
}
else
{
DisplayAlert("ERROR", "All fields must be set to add the exercise to the routine", "OK");
}
}
}
ANY HELP WILL BE APRECCIATE IT! :v
The issue is that you're using
public List<Routine> Routines { get; set; } = new List<Routine>();
Change it to :
public ObservableCollection<Routine> Routines { set; get; } = new ObservableCollection<Routine>();
Both List and ObservableCollection implement IList<T>, there isn't much of a difference there, ObservableCollection also implements INotifyCollectionChanged interface.So it will update the Ui automatically when the collection changes.

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.

How to add UI elements (like "picker") to View dynamically in Xamarin?

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));
}

What is the best way to start a function in a ViewMomdel just by clicking on the item in the ListView

I want to add the data from my selected item in the ListView to a few entries immediately when clicking on the item.
My XAML Code:
<?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:local="clr-namespace:WishListProject.ViewModels"
x:Class="WishListProject.Views.UpdateGames">
<ContentPage.BindingContext>
<local:GameListViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<ListView ItemsSource="{Binding Games}" SelectedItem="{Binding SelectedGame}" ItemSelected="{Binding InsertGame }">
</ListView>
<Entry Placeholder="ID" IsVisible="False" Text="{Binding IdEntry}"></Entry>
<Entry Placeholder="GameName" Text="{Binding GameNaamEntry}"></Entry>
<Entry Placeholder="GameGenre" Text="{Binding GameGenreEntry}"></Entry>
<Entry Placeholder="GameRelease" Text="{Binding GameReleaseEntry}"></Entry>
<Button Text="Update Game" Command="{Binding UpdateGameCommand}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
And this is the function I want to start for displaying the selected item data in to a few entries USING THE VIEWMODEL:
public void InsertGame(object sender, SelectedItemChangedEventArgs e)
{
Game game = new Game();
game = SelectedGame;
IdEntry.Text = game.Id.ToString();
GameNaamEntry = game.GameNaam;
GameGenreEntry = game.GameGenre;
GameReleaseEntry = game.GameRelease;
}
What is the best way to start a function in a VIEWMODEL just by clicking on the item in the ListView?
You can call your method at the time SelectedItem is set. Since you are using bindings, I assume you have something like this in your ViewModel:
Game _selectedGame;
public Game SelectedGame
{
get { return _selectedGame; }
set {
SetProperty(ref _selectedGame, value);
// code to add in the setter
if (value != null)
InsertGame();
//----------------------------
}
}
Just make sure the method is called after the property is changed.
Then you will also need to simplify your method signature to:
public void InsertGame(){
...
}
And you should be all set.
Happy coding!
ItemSelected is an event and you can't bind it to a method. I will give you a solution about how to start a function in a ViewMomdel just by clicking on the item in the ListView:
In xaml:
<ListView ItemsSource="{Binding Games}" SelectedItem="{Binding SelectedGame}" ItemSelected="ListView_ItemSelected">
And in code behind, get the current ViewModel in the ListView_ItemSelected method and then call InsertGame of ViewModel, you can pass the current selectedGame also:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
Game selectedGame = e.SelectedItem as Game;
var currentVm = this.BindingContext as GameListViewModel;
currentVm.InsertGame(selectedGame);
}
}
And your ViewModel:
public class GameListViewModel : INotifyPropertyChanged
{
public void InsertGame(Game currentGame)
{
GameNaamEntry = currentGame.GameNaam;
}
...
}

Categories

Resources