Fire a command on tap in ListView.ItemSource - xamarin

In a Xamarin project I have this ListView inside a view called menu.xaml:
<ListView x:Name="listView" x:FieldModifier="public">
<ListView.ItemsSource>
<x:Array Type="{x:Type local1:MasterPageItem}">
<local1:MasterPageItem Title="foo" TargetType="{x:Type local:FooPage}" />
<local1:MasterPageItem Title="bar" TargetType="{x:Type local:BarPage}" />
<local1:MasterPageItem Title="logout" TargetType="{x:Type local:LogoutPage}" />
</x:Array>
</ListView.ItemsSource>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="5,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source="{Binding IconSource}" />
<Label Grid.Column="1" Text="{Binding Title}" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
These produces this list:
In my viewmodel, I have this command:
class MasterViewModel : BaseViewModel
{
public ICommand LogoutActivity { get; private set; }
public MasterViewModel()
{
LogoutActivity = new Command(async () => await LogoutAsync());
}
}
Tapping one of these items opens a corresponding page. I want the logout link to fire a command instead of opening a page. How do I accomplish this?

You can add Tapped Gesture Recognizer to your grid:
<Grid Padding="5,10">
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding LoginActivity}" CommandParameter="{Binding .}">
</TapGestureRecognizer>
</Grid.GestureRecognizers>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source="{Binding IconSource}" />
<Label Grid.Column="1" Text="{Binding Title}" />
</Grid>
For executing specific action for menu items, you can add unique property to your MasterPageItem model and set for each of them.

public partial class MainPage : MasterDetailPage
{
public MainPage ()
{
masterPage.listView.ItemSelected += OnItemSelected;
}
void OnItemSelected (object sender, SelectedItemChangedEventArgs e)
{
var item = e.SelectedItem as MasterPageItem;
if (item != null) {
// Check here if is the logout link that is clicked and perform the required a action
}
}
}

Related

Xamarin App opens different Page from Navigation.PushAsync() Page

Description
Xamarin application opens different Page upon NavigationPage.Pushaync().
NavigationPage.Asyncpush() is called within a ButtonClicked Method.
That different page used to be the one being pushed but it no longer is.
ViewModel.cs
private async void OnAboutUsClicked()
{
var modifiedContactUs = new ModifiedContactUs() { BackgroundColor = ResourceColourModel.BackgroundColor };
await Application.Current.MainPage.Navigation.PushAsync(modifiedContactUs);
}
ModifiedContactUs.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="Project.Views.Pages.ModifiedContact"
xmlns:mapObject="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps">
<ContentPage.Content>
<StackLayout>
<Grid BackgroundColor="white">
<mapObject:Map x:Name="mapId" HasZoomEnabled="True" MapType="Street">
</mapObject:Map>
</Grid>
</StackLayout>
</ContentPage.Content>
</ContentPage>
ModifiedContactUs.xaml.cs
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Project.Views.Pages
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ModifiedContactUs : ContentPage
{
public ModifiedContactUs()
{
InitializeComponent();
}
}
ContactUs.xaml(The different Page)
<?xml version="1.0" encoding="utf-8" ?>
<customControl:CustomContentPage
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:map ="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"
xmlns:ffimageloading="clr-namespace:FFImageLoading.Svg.Forms;assembly=FFImageLoading.Svg.Forms"
xmlns:customControl="clr-namespace:Project.CustomControls.Renderers"
xmlns:templates_navbar="clr-namespace:Project.Views.Templates.Menus"
xmlns:fontAwesome="clr-namespace:Project._Utilities"
mc:Ignorable="d"
x:Class="Project.Views.Pages.ContactUs"
>
<ContentPage.Content>
<StackLayout BackgroundColor="Transparent" Spacing="0">
<templates_navbar:NavBar DotMenu_IsVisible="False" BackButton_IsVisible="True"/>
<Grid BackgroundColor="white">
<Grid.RowDefinitions>
<RowDefinition Height=".33*"/>
<RowDefinition Height=".07*"/>
<RowDefinition Height=".6*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".7*"/>
<ColumnDefinition Width=".3*"/>
</Grid.ColumnDefinitions>
<StackLayout Grid.Column="0" Padding="10">
<StackLayout>
<Label
Text="Company Name"
FontAttributes="Bold"
FontSize="Medium"
FontFamily="Lato-Bold"
VerticalOptions="CenterAndExpand"
HorizontalOptions="StartAndExpand"
/>
<Label
Text="{Binding Address}"
FontFamily="Lato-Bold"
VerticalOptions="CenterAndExpand"
HorizontalOptions="StartAndExpand"
/>
</StackLayout>
<StackLayout>
<Label
Text="Opening Times"
FontAttributes="Bold"
FontSize="Medium"
FontFamily="Lato-Bold"
VerticalOptions="CenterAndExpand"
HorizontalOptions="StartAndExpand"
/>
<Label
x:Name="weekdays_lbl"
Text="{Binding Weekday}"
FontFamily="Lato-Bold"
VerticalOptions="CenterAndExpand"
HorizontalOptions="StartAndExpand"
/>
<Label
x:Name="weekend_lbl"
Text="{Binding Weekend}"
FontFamily="Lato-Bold"
VerticalOptions="CenterAndExpand"
HorizontalOptions="StartAndExpand"
/>
</StackLayout>
</StackLayout>
<StackLayout Grid.Column="1">
<StackLayout VerticalOptions="CenterAndExpand">
<Button
x:Name="CallUsBtn"
Text="Call Us"
TextColor="White"
BackgroundColor="{StaticResource PrimaryColour}"
FontFamily="Lato-Bold"
FontSize="12"
BorderWidth="2"
BorderColor="White"
Margin="1"
CornerRadius="22"
Command="{Binding PhoneCommand}"
/>
<Button
x:Name="MessageUsBtn"
Text="Message"
TextColor="White"
BackgroundColor="{StaticResource PrimaryColour}"
FontFamily="Lato-Bold"
FontSize="12"
BorderWidth="2"
BorderColor="White"
Margin="1"
CornerRadius="22"
Command="{Binding EmailCommand}"
/>
<Button
x:Name="RequestCallBack"
Text="Request CallBack"
TextColor="White"
BackgroundColor="{StaticResource AccentColour}"
FontFamily="Lato-Bold"
FontSize="11"
BorderWidth="2"
BorderColor="White"
Margin="1"
CornerRadius="22"
Command="{Binding CallbackCommand}"
/>
</StackLayout>
</StackLayout>
</Grid>
<Grid Grid.Row="1" Padding="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".25*"/>
<ColumnDefinition Width=".25*"/>
<ColumnDefinition Width=".25*"/>
<ColumnDefinition Width=".25*"/>
</Grid.ColumnDefinitions>
<StackLayout Grid.Column="0">
<Grid VerticalOptions="CenterAndExpand">
<Image
Source="{FontImage FontFamily={StaticResource MaterialFontFamily},
Glyph={x:Static fontAwesome:IconFonts.Facebook}, Color=#1877F2,Size=60}"
HeightRequest="80"
/>
<Button Clicked="SocialMediaBtnHandler" BackgroundColor="Transparent" Margin="5, 0" CommandParameter="facebook"/>
</Grid>
</StackLayout>
<StackLayout Grid.Column="1">
<Grid VerticalOptions="CenterAndExpand">
<Image
Source="{FontImage FontFamily={StaticResource MaterialFontFamily},
Glyph={x:Static fontAwesome:IconFonts.Twitter}, Color=#00ACED, Size=60}"/>
<Button Clicked="SocialMediaBtnHandler" BackgroundColor="Transparent" Margin="5, 0" CommandParameter="twitter"/>
</Grid>
</StackLayout>
<StackLayout Grid.Column="2">
<Grid VerticalOptions="CenterAndExpand">
<Image
Source="{FontImage FontFamily={StaticResource MaterialFontFamily},
Glyph={x:Static fontAwesome:IconFonts.Linkedin}, Color=#0E76A8, Size=60}"/>
<Button Clicked="SocialMediaBtnHandler" BackgroundColor="Transparent" Margin="5, 0" CommandParameter="linkedIn"/>
</Grid>
</StackLayout>
<StackLayout Grid.Column="3">
<Grid VerticalOptions="CenterAndExpand" Padding="2">
<ffimageloading:SvgCachedImage
Source="social_logo_instagram.png"
HeightRequest="40"/>
<Button Clicked="SocialMediaBtnHandler" BackgroundColor="Transparent" Margin="5, 0" CommandParameter="instagram"/>
</Grid>
</StackLayout>
</Grid>
<Grid Grid.Row="2">
<Grid>
<StackLayout Grid.Row="0" Padding="5">
<map:Map x:Name="mapObject" HasZoomEnabled="True" MapType="Street">
<map:Map.ItemTemplate>
<DataTemplate>
<map:Pin
Position="{Binding Position}"
Address="{Binding Address}"
Label="{Binding PlaceName}"
/>
</DataTemplate>
</map:Map.ItemTemplate>
</map:Map>
</StackLayout>
</Grid>
</Grid>
</Grid>
<AbsoluteLayout BackgroundColor="Red" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" AbsoluteLayout.LayoutBounds="0, 0, 1, 1" AbsoluteLayout.LayoutFlags="SizeProportional">
</AbsoluteLayout>
</StackLayout>
</ContentPage.Content>
</customControl:CustomContentPage>
ContactUs.xaml.cs (The different page)
using Project._SharedData;
using Project.CustomControls.Renderers;
using Project.Models;
using Project.ViewModels;
using Rg.Plugins.Popup.Services;
using System;
using Xamarin.Essentials;
using Xamarin.Forms;
using Xamarin.Forms.Maps;
using Xamarin.Forms.Xaml;
namespace Project.Views.Pages
{
[XamlCompilation(XamlCompilationOptions.Compile)]
[System.ComponentModel.DesignTimeVisible(false)]
public partial class ContactUsPage : CustomContentPage
{
ContactUsVM _ContactUsVM;
public ContactUsPage()
{
InitializeComponent();
BindingContext = _ContactUsVM = new ContactUsVM();
initMap();
configureDisplay();
setFontOpeningTime();
}
protected override void OnAppearing()
{
base.OnAppearing();
_ContactUsVM.OnAppearing();
configureDisplay();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_ContactUsVM.OnDisappearing();
try
{
if (PopupNavigation.Instance != null)
PopupNavigation.Instance.PopAsync(true);
}
catch (Exception) { }
}
private void setFontOpeningTime()
{
DayOfWeek day = DateTime.Now.DayOfWeek;
// Set the font based on whether current day is weekday or weekend
if ((day == DayOfWeek.Saturday) || (day == DayOfWeek.Sunday))
{
weekend_lbl.FontAttributes = FontAttributes.Bold;
}
else
{
weekdays_lbl.FontAttributes = FontAttributes.Bold;
}
}
private void configureDisplay()
{
if (!string.IsNullOrEmpty(SharedPrefs.UserSessionEmail))
{
RequestCallBack.IsVisible = true;
MessageUsBtn.Text = "Email";
}
else
{
RequestCallBack.IsVisible = false;
MessageUsBtn.Text = "Message";
}
}
private async void SocialMediaBtnHandler(object sender, EventArgs e)
{
string socialMediaName = (sender as Button).CommandParameter.ToString().ToLower();
await Launcher.OpenAsync(SocialMediaModel.WebLink(socialMediaName));
}
private void initMap()
{
try
{
double latitude = Convert.ToDouble(SharedData.CompanyDetailsList[0].Latitude);
double longitude = Convert.ToDouble(SharedData.CompanyDetailsList[0].Longitude);
mapObject.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(latitude, longitude), Distance.FromMeters(100)));
mapObject.ItemsSource = SharedData.CompanyDetailsList;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
}
Expected behavior
To navigate to a new Page named ModifiedContactUs
Actual behavior:
Navigates to page called named ContactUs
Basic Information:
Android Build:
-minimum level 21
-Target level 30
Devices:
-Huawei Y6 2019
-API level 29
-Pixel 5
-API level 30
Notes
This question is different to from the first question I asked recently (Where the device crashes). This is behavior happens on all devices I test on except the one from that first question
Expected behavior
To navigate to a new Page named ModifiedContactPage
Actual behavior:
Navigates to page called named ContactUsPage
From your code I saw you just push to ModifiedContactUs not ModifiedContactPage .
var modifiedContactUs = new ModifiedContactUs() { BackgroundColor = ResourceColourModel.BackgroundColor };
await Application.Current.MainPage.Navigation.PushAsync(modifiedContactUs);
Suggestion
Try to add breakpoint to see if it triggers button click event es expected .
Delete the old class and try again .
Delete the app from the device and try again .
This worked: Clone the repo into a new folder as a new project and copy the files and changes to this new repo.
Attempted configuration solutions that did not work: Restarting visual studio. Untick and Tick Debug Mode. Untick and tick Deploy in Configuration Manager under Build. In Android Properties> android Options making sure debugging is checked and debugger is Xamarin.

Xamarin Forms CollectionView TapGestureRecognizer not firing on label

I have a XF app with the following collection view defined. The 2nd label has a TapGestureRecognizer that doesn't fire DoSomethingInteresting in the model when I tap the label (trying this on Android). Can someone see what the issue is please?
A working sample can be cloned from here.
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:local="clr-namespace:SampleApp"
x:Class="SampleApp.MainPage">
<StackLayout>
<CollectionView ItemsSource="{Binding GaugeSites}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Label Grid.Column="0"
Text="{Binding Description}"
FontSize="20"
Margin="10"
TextColor="Black"
FontAttributes="Bold" />
<Label Grid.Column="1"
Margin="10"
FontSize="20"
Text="Click Me!">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding DoSomethingInteresting}" />
</Label.GestureRecognizers>
</Label>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage>
Model
namespace SampleApp
{
public class MainPageModel : FreshBasePageModel
{
public MainPageModel() : base()
{
GaugeSites = new List<GaugeSite>();
for (var index = 1; index <= 5; index++)
{
GaugeSites.Add(new GaugeSite()
{
Description = $"Gauge Site {index}"
});
}
}
public List<GaugeSite> GaugeSites { get; set; }
public Command DoSomethingInteresting
{
get
{
return new Command(() =>
{
});
}
}
}
[AddINotifyPropertyChangedInterface]
public class GaugeSite
{
public string Description { get; set; }
}
}
Maybe this is redundant but I will take my chances. As other answers indicated you need to set the source within your TapGestureRecognizer to the name of the CollectionView. However, it is probably useful to know which GaugeSite instance was associated with the CollectionView item whose TabGestureRecognizer was tapped. To add this info add a CommandParamter, i.e.
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BindingContext.DoSomethingInteresting,
CommandParameter="{Binding}"
Source={x:Reference YourCollectionName}}" />
</Label.GestureRecognizers>
When it comes to the command you could use
DoSomethingInteresting = new Command<GaugeSite>((a) => DoSomething(a));
in your viewmodels contructor. And the method referenced by the lambda would be
void DoSomething(GaugeSite gaugeSite)
{
// ....
}
Hope this helps
I have download your sample, please take a look the following step.
1.Binding MainpageModel for MainPage bindingContext, add this line in MainPage.cs.
this.BindingContext = new MainPageModel();
2.Name Collectionview as collection1, then modify label command.
<CollectionView x:Name="collection1" ItemsSource="{Binding GaugeSites}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Label
Grid.Column="0"
Margin="10"
FontAttributes="Bold"
FontSize="20"
Text="{Binding Description}"
TextColor="Black" />
<Label
Grid.Column="1"
Margin="10"
FontSize="20"
Text="Click Me!">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BindingContext.DoSomethingInteresting, Source={x:Reference collection1}}" />
</Label.GestureRecognizers>
</Label>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Then you can try again.

Accessing name when toggling switch in listview xamarin forms

I have the below codes which consist of a list view. The listview contains a label and a custom switch.
<ListView x:Name="myListView" Margin="20" HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell IsEnabled="False">
<Grid Margin="0,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackLayout VerticalOptions="CenterAndExpand">
<Label Text="{Binding .}" />
</StackLayout>
<local:CustomSwitch x:Name="customSwitch"
SwitchOffColor="Gray"
SwitchOnColor="Red"
SwitchThumbColor="White"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
Grid.Column="1"
/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
What I want to achieve is to get the label text when I toggle the switch. How can In achieve this in Xamarin Forms from my ViewModel ?
The simplest way would be to create a Bindable property in your CustomSwitch like
BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(object), null);
public object CommandParameter
{
get { return GetValue(CommandParameterProperty ); }
set
{
SetValue(CommandParameterProperty , value);
}
}
Then in your xaml adapt your declaration to
<local:CustomSwitch x:Name="customSwitch"
SwitchOffColor="Gray"
SwitchOnColor="Red"
SwitchThumbColor="White"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
Grid.Column="1"
CommandParameter={Binding .}
/>
Now in your codebehind you can easily access and consume the CommandParameter and cast it to string:
string labelName = (string) ((sender as CustomSwitch).CommandParameter);

Get the click command from template to the view model in xamarin

In the template there is a button and created as below called x:Name="cartbutton"
<?xml version="1.0" encoding="UTF-8"?>
<Grid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
x:Class="FormsApplication.Templates.ListItemTemplate"
xmlns:controls="clr-namespace:FormsControls.Base;assembly=FormsControls.Base"
ColumnSpacing="0" RowSpacing="0" HeightRequest="350" Padding="8,0,8,8" x:Name="ListItemTempPage">
<Grid.RowDefinitions>
<RowDefinition Height="300" />
<RowDefinition Height="30" />
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<controls:RoundedBoxView Grid.RowSpan="2" BackgroundColor="White" BorderColor="Silver" CornerRadius="8" BorderThickness="2"/>
<ffimageloading:CachedImage Grid.RowSpan="2" Source="{Binding ImageSource}" Aspect="AspectFill" DownsampleToViewSize="False"/>
<Button x:Name="cartbutton" Grid.Row="0" HorizontalOptions="End" VerticalOptions="Start" Image="lowim.png" BackgroundColor="Transparent" Margin="0,5,5,0" Command="{Binding ParentContext.Itemtapped, Source={x:Reference ListItemTempPage}}" CommandParameter="{Binding .}"/>
</Grid>
and the parent page called by the reference the template as below
<ListView x:Name="listView" Grid.Row="1" Margin="0,8,0,0"
HasUnevenRows="true" ItemsSource="{Binding FoodItems}"
SeparatorColor="Transparent" SeparatorVisibility="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<templates:ListItemTemplate ParentContext="{Binding BindingContext, Source={x:Reference ListItemPage}}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Behaviors>
<controls:EventToCommandBehavior EventName="ItemTapped" Command="{Binding ListItemTappedCommand}" CommandParameter="{x:Reference listView}"/>
</ListView.Behaviors>
</ListView>
finally the viewmodel goes with the icommand as below
public SelectItemViewModel(IAppContext context, IRestService restService) : base(context)
{
_restService = restService;
Title = "Category Products";
ListItemTappedCommand = new Command<ListView>(OnListItemTappedCommand);
PageAppearingCommand = new Command(OnPageAppearingCommand);
Itemtapped = new Command(OnItemtapped);
}
public ICommand Itemtapped { get; }
private void OnItemtapped()
{
Navigator.PushAsync<SelectItemViewModel>();
}
so the probelm is when compiling there is an error saying that
No property, bindable property, or event found for 'ParentContext', or mismatching type between value and property.
so basically saying that i am unable to give the button command ( in the template page) to trigger in the viewmodel properly, help will be appreciated to figure this out, thanks.
I'd introduce a AddToCartCommand in your child control, which is invoked if the button is clicked.
// in your ListItemTemplate.xaml.cs
public static BindableProperty AddToCartCommandProperty = BindableProperty.Create(/* ... */);
public ICommand AddToCartCommand
{
get => (ICommand)GetValue(AddToCartCommandProperty);
set => SetValue(AddToCartCommandProperty, value);
}
public void Button_OnClick(object sender, EventArgs e)
{
this.AddToCartCommand?.Execute(null);
}
To bind to this from your parent view, you'll need a reference to that parent view with x:Name="Page" (or however you'd like to name your page) and then use a {Binding Source={x:Reference Page}, Path=BindingContext.Itemtapped}
<ContentPage ... x:Name="Page"> <!-- I have elided attributes that are not relevant to my answer -->
<!-- Elided -->
<ListView ...>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<templates:ListItemTemplate AddToCartCommand="{Binding Source={x:Reference Page}, Path=BindingContext.Itemtapped}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<!-- Elided -->
</ListView>
<!-- Elided -->
</ContentPage>

Can't bind ListView to ViewModel

This my ViewModel:
namespace DietAndFitness.ViewModels
{
public class FoodItemsViewModel
{
private SQLiteAsyncConnection database;
public static ObservableCollection<GlobalFoodItem> FoodItems { get; set; }
public FoodItemsViewModel(SQLiteAsyncConnection _database)
{
database = _database;
}
public async void LoadList()
{
FoodItems = new ObservableCollection<GlobalFoodItem>(await database.Table<GlobalFoodItem>().ToListAsync());
}
public void Add()
{
FoodItems.Add(new GlobalFoodItem("Item"));
}
}
}
I want to bind an instance of this VM to the list in my View but I just can't get it to work properly. The only thing I've managed to get to work is this:
<?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:DietAndFitness.ViewModels"
x:Class="DietAndFitness.Views.FoodDatabasePage">
<ContentPage.Content>
<StackLayout>
<ListView ItemsSource="{Binding FoodItems}" RowHeight="120">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView Padding="5">
<Frame OutlineColor="Accent" Padding="10">
<StackLayout>
<Grid HorizontalOptions="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Name}" FontSize="22" VerticalOptions="CenterAndExpand" HorizontalOptions="Center" Grid.Row="0" Grid.Column="0"/>
<Label Text="{Binding Calories}" FontSize="22" VerticalOptions="CenterAndExpand" HorizontalOptions="Center" Grid.Row="0" Grid.Column="1"/>
</Grid>
<Grid HorizontalOptions="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Text="Carbohydrates" Grid.Row="1" Grid.Column="0" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" FontSize="16"/>
<Label Text="Proteins" Grid.Row="1" Grid.Column="1" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" FontSize="16"/>
<Label Text="Fats" Grid.Row="1" Grid.Column="2" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" FontSize="16"/>
<Label Text="{Binding Carbohydrates}" Grid.Row="2" Grid.Column="0" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" FontSize="16"/>
<Label Text="{Binding Proteins}" Grid.Row="2" Grid.Column="1" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" FontSize="16"/>
<Label Text="{Binding Fats}" Grid.Row="2" Grid.Column="2" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" FontSize="16"/>
</Grid>
</StackLayout>
</Frame>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid HorizontalOptions="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button x:Name="AddFoodItemButton" BackgroundColor="LightGray" HorizontalOptions="CenterAndExpand" Text="Add" Grid.Row="0" Grid.Column="0" Clicked="AddFoodItemButton_Clicked" />
<Button x:Name="EditFoodItemButton" BackgroundColor="LightGray" HorizontalOptions="CenterAndExpand" Text="Edit" Grid.Row="0" Grid.Column="1" />
<Button x:Name="DeleteFoodItemButton" BackgroundColor="LightGray" HorizontalOptions="CenterAndExpand" Text="Delete" Grid.Row="0" Grid.Column="2" />
</Grid>
</StackLayout>
</ContentPage.Content>
But this has problems too:
If I create the VM in the page constructor the list won't load unless I refresh the page
If I create the VM inside App.cs the list will load on the first try but then I can't seem to access the instance of the VM
How do I bind my ListView to this VM? And is it possible to do so without making my ObservableCollection static?
I'm using a Master-Detail page in my app if it makes any difference (this is one of the detail pages).
Edit: Here is what I did in the constructor. SQLiteConnection.Database is just a static class that contains the connection to the database.
public FoodDatabasePage ()
{
FoodDatabase = new FoodItemsViewModel(SQLiteConnection.Database);
FoodDatabase.LoadList();
InitializeComponent ();
}
There's a few things that you have there.
When binding a ViewModel usually it's against the whole Page making the ViewModel its BindingContext specially in cases like yours when you have methods that want to access later.
So to do this you will need a few changes.
Your Page 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:local="clr-namespace:DietAndFitness.ViewModels"
x:Class="DietAndFitness.Views.FoodDatabasePage">
<ContentPage.Content>
<StackLayout>
<ListView ItemsSource="{Binding FoodItems}"
RowHeight="120">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame OutlineColor="Accent" Padding="15">
<StackLayout>
<Grid HorizontalOptions="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Name}"
FontSize="22"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Grid.Row="0"
Grid.Column="0"/>
</Grid>
</StackLayout>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Check that I changed the ListView ItemSource for the name of the collection in your FoodItemsViewModel.
Also, part of your XAML disappeared so I could not complete it.
Your Code Behind class: (FoodDatabasePage.xaml.cs)
public FoodItemsViewModel FoodDatabase {get; set;}
public FoodDatabasePage ()
{
InitializeComponent ();
FoodDatabase = new FoodItemsViewModel(SQLiteConnection.Database);
BindingContext = FoodDatabase;
}
public override void OnAppearing()
{
FoodDatabase.LoadList();
}
Here is where the "magic" happens. We are creating and storing the object of our FoodItemsViewModel so you can access it later as you need and then we are setting this object recently created as the BindingContext of the page (as explained above). Setting the ViewModel as the BindingContext of Page the latter will be able to access any public property of the FoodItemsViewModel.
Also, you will notice the LoadList() method was removed from the constructor, this is because there shouldn't be any heavy transaction (actually we should avoid any transactions) in the Page constructor. We moved it instead to the OnAppearing callback method that is executed by the Page when this is about to be displayed.
Note: This method is called every time the Page is presented on the screen so if you navigate away and come back it will be called again.
Your ViewModel:
namespace DietAndFitness.ViewModels
{
public class FoodItemsViewModel
{
private SQLiteAsyncConnection database;
//You don't need it static
public ObservableCollection<GlobalFoodItem> FoodItems { get; set; }
public FoodItemsViewModel(SQLiteAsyncConnection _database)
{
database = _database;
}
public async void LoadList()
{
// separated it only for readability.
var items = await database.Table<GlobalFoodItem>().ToListAsync();
FoodItems = new ObservableCollection<GlobalFoodItem>(items);
}
public void Add()
{
FoodItems.Add(new GlobalFoodItem("Item"));
}
}
}
This remains almost the same. The only change was removing the static qualifier to the FoodItems property since it does not need it anymore.
The above is just the basic of Mvvm and DataBinding. There're a few other things that you will need to take into consideration like INotifiedPropertyChanged, Commands and other important things.
You can find more information regarding this here
Hope this helps.-
PS: I couldn't check all of your code for any error.

Resources