How to change image source when a property changes through databinding in XAML from viewmodel in xamarin forms? - xamarin.forms

I am working on providing wishlist feature for my app by tapping wishlist icon on each product in list through MVVM. Once tapped, an API call is made to update database(add/remove from wishlist table). Based on result from api call, I updated the specific product's respective property to either 'True' or 'False'. Once property updated, I want to change the icon image source of corresponding product. I am using trigger on wishlist icon to differentiate non-wishlist and wiahlist products while binding the list itself.
My code is below,
MODEL
public class PublisherProducts
{
public long ProductId { get; set; }
public string ProductName { get; set; }
public string ImageURL { get; set; }
public decimal Price { get; set; }
public bool IsWishlistProduct { get; set; }
}
VIEWMODEL
public class OnlineStoreViewModel : BaseViewModel
{
private ObservableCollection<PublisherProducts> publisherProducts;
public Command<long> WishlistTapCommand { get; }
public OnlineStoreViewModel()
{
publisherProducts = new ObservableCollection<PublisherProducts>();
WishlistTapCommand = new Command<long>(OnWishlistSelected);
}
public ObservableCollection<PublisherProducts> PublisherProducts
{
get { return publisherProducts; }
set
{
publisherProducts = value;
OnPropertyChanged();
}
}
public async Task GetProducts(long selectedCategoryId)
{
try
{
...
PublisherProducts = new ObservableCollection<PublisherProducts>(apiresponse.ProductList);
...
}
catch (Exception ex) { ... }
finally { ... }
}
async void OnWishlistSelected(long tappedProductId)
{
if (tappedProductId <= 0)
return;
else
await UpdateWishlist(tappedProductId);
}
public async Task UpdateWishlist(long productId)
{
try
{
var wishlistResponse = // api call
var item = PublisherProducts.Where(p => p.ProductId == productId).FirstOrDefault();
item.IsWishlistProduct = !item.IsWishlistProduct;
PublisherProducts = publisherProducts; *Stuck here to toggle wishlist icon*
await App.Current.MainPage.DisplayAlert("", wishlistResponse.Message, "Ok");
}
catch (Exception ex) { ... }
finally { ... }
}
}
XAML
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" ... >
<ContentPage.Content>
<ScrollView>
<StackLayout Padding="15,0,15,10">
<FlexLayout x:Name="flxLayout" BindableLayout.ItemsSource="{Binding PublisherProducts}" ...>
<BindableLayout.ItemTemplate>
<DataTemplate>
<AbsoluteLayout Margin="6" WidthRequest="150">
<Frame Padding="0" WidthRequest="150" CornerRadius="10" HasShadow="True">
<StackLayout Orientation="Vertical" Padding="10" HorizontalOptions="FillAndExpand">
<Image Source="{Binding ImageURL}" WidthRequest="130" HeightRequest="130" HorizontalOptions="Center"/>
<Label Text="{Binding ProductName}" Style="{StaticResource ProductNameStyle}"></Label>
...
<StackLayout ...>
...
<Frame x:Name="wlistFrame" Padding="0" WidthRequest="30" HeightRequest="30" CornerRadius="10" BorderColor="#02457A">
<StackLayout Orientation="Horizontal" VerticalOptions="Center" HorizontalOptions="Center">
<Image x:Name="wlImage" WidthRequest="13" HeightRequest="12" HorizontalOptions="Center" VerticalOptions="Center" Source="ic_wishlist_open">
<Image.Triggers>
<DataTrigger TargetType="Image" Binding="{Binding IsWishlistProduct}" Value="true">
<Setter Property="Source" Value="ic_wishlist_close" />
</DataTrigger>
</Image.Triggers>
</Image>
</StackLayout>
<Frame.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type local:OnlineStoreViewModel}}, Path=WishlistTapCommand}" CommandParameter="{Binding ProductId}" NumberOfTapsRequired="1" />
</Frame.GestureRecognizers>
</Frame>
</StackLayout>
</StackLayout>
</Frame>
</AbsoluteLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</FlexLayout>
</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>
I am stuck at this place to change wishlist icon, when 'IsWishlistProduct' property value is changed in UpdateWishlist().

Guessing through your code, the BaseViewModel contains code similar to the following:
public class BaseViewModel : INotifyPropertyChanged
{
...
public event PropertyChangedEventHandler PropertyChanged;
...
public void OnPropertyChanged(string name)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
...
}
And your viewmodel should be like this:
...
public ObservableCollection<PublisherProducts> PublisherProducts
{
get { return publisherProducts; }
set
{
publisherProducts = value;
OnPropertyChanged(nameof(PublisherProducts));
}
}
...
As Jason mentioned, If there is a change in data in the ViewModel, it is reflected in the UI when it is notified to the View through NotifyPropertyChanged. You already implemented "OnPropertyChanged" function in your BaseViewModel but it seems you don't pass the object name.

Related

How one ViewModel contains multiple ViewModel xamatin

I have 2 ViewModels (MVVM). I let show 2 as shown, it only shows data 1 ViewModel (the one below).
I put 1 and it shows up as normal.
This is how I display the data
<RefreshView x:DataType="locals:SliderViewModel"
Command="{Binding LoadSliderCommand}"
IsRefreshing="{Binding IsBusy, Mode=OneWay}">
<StackLayout Padding="8,0,8,4"
BindableLayout.ItemsSource="{Binding SliderShowInfos}"
Orientation="Horizontal"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout x:DataType="model:SliderShowInfo">
<Frame Padding="4"
HasShadow="False"
IsClippedToBounds="True"
BackgroundColor="Transparent">
<StackLayout Orientation="Horizontal">
<Frame Padding="0"
HasShadow="False"
CornerRadius="7"
IsClippedToBounds="True">
<Image Source="{Binding ImagesSlider}">
</Image>
</Frame>
</StackLayout>
</Frame>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</RefreshView>
<RefreshView x:DataType="locals:ProductViewModel"
Command="{Binding LoadProductCommand}"
IsRefreshing="{Binding IsBusy, Mode=OneWay}">
<StackLayout Padding="8"
Orientation="Horizontal"
BindableLayout.ItemsSource="{Binding ProductInfos}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Frame Padding="5,0"
HasShadow="False"
IsClippedToBounds="True"
BackgroundColor="#fff">
<StackLayout x:DataType="model:ProductInfo">
</StackLayout>
</Frame>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</RefreshView>
This is how I display the data. I'm trying to display product listing and photo listing data. Please give me a solution that can combine 2 ViewModel
Update
SliderViewModel.cs
public class SliderViewModel:BaseSliderViewModel
{
ISliderShowRepository sliderShowRepository = new SliderShowService();
public Command LoadSliderCommand { get; }
public ObservableCollection<SliderShowInfo> SliderShowInfos { get; }
public SliderViewModel()
{
LoadSliderCommand = new Command(async () => await ExecuteLoadSliderCommand());
SliderShowInfos = new ObservableCollection<SliderShowInfo>();
}
public void OnAppearing()
{
IsBusy = true;
}
async Task ExecuteLoadSliderCommand()
{
}
}
ProductViewModel.cs
public class ProductViewModel : BaseProductViewModel
{
IProductRepository productRepository = new ProductService();
public Command LoadProductCommand { get; }
public ObservableCollection<ProductInfo> ProductInfos { get; }
public Command ProductTappedView { get; }
public ProductViewModel(INavigation _navigation)
{
LoadProductCommand = new Command(async () => await ExecuteLoadProductCommand());
ProductInfos = new ObservableCollection<ProductInfo>();
ProductTappedView = new Command<ProductInfo>(OnViewDetailProduct);
Navigation = _navigation;
}
private async void OnViewDetailProduct(ProductInfo prod)
{
await Navigation.PushAsync(new DetailProduct(prod));
}
public void OnAppearing()
{
IsBusy = true;
}
async Task ExecuteLoadProductCommand()
{
IsBusy = true;
try
{
ProductInfos.Clear();
var prodList = await productRepository.GetProductsAsync();
foreach (var prod in prodList)
{
ProductInfos.Add(prod);
}
}
catch(Exception)
{
throw;
}
finally
{
IsBusy = false;
}
}
}
DashboardViewModel.cs
public class DashboardViewModel
{
public SliderViewModel SliderShowVM { get; set; }
public ProductViewModel ProductVM { get; set; }
}
Dashboard.xaml.cs
public Dashboard()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, true);
BindingContext = new DashboardViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
}
You cant just have 2 ViewModels for a single page. Instead, you can create another ViewModel, which will contains those 2 ViewModels that you need.
public class DashboardViewModel
{
public SliderShowViewModel SliderShowVM { get; set; }
public ProductViewModel ProductVM { get; set; }
}
And in dashboard constructor, assign the BindingContext
BindingContext = new DashboardViewModel();
And in your view, bind data like this:
...
<Image Source="{Binding SliderShowVM.ImagesSlider}">
...
BindableLayout.ItemsSource="{Binding ProductVM.ProductInfos}"
...
Also, if you don't need all data from SliderShowViewModel or ProductViewModel on your Dashboard, then you can define the properties that you really need for the dashboard inside DashboardViewModel, and to inject SliderShowViewModel and ProductViewModel instances via constructor (Might not be the best solution, but this way you will keep you view cleaner)
public class DashboardViewModel
{
public string RelevantProperty1 { get; set; }
public int RelevantProperty2 { get; set; }
...
public DashboardViewModel(SliderShowViewModel sliderVm, ProductViewModel productVm)
{
RelevantProperty1 = sliderVm.Something;
RelevantProperty2 = productVm.SomethingElse;
...
}
}
More explicit
DashboardViewModel class: It is a class with two properties of type SliderShowViewModel and ProductViewModel. These properties will store some instances (aka objects) of SliderShowViewModel and ProductViewModel respectively.
Create a DashboardViewModel instance and pass it as BindingContext for your Dashboard View:
public Dashboard()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, true);
var viewModel = new DashboardViewModel();
// Now we have the viewModel, but there is a problem.
// Remember those two viewModels (SliderShowViewModel and ProductViewModel)?
// Well, those properties are null, we have to provide values for them
// before setting the binding context.
viewModel.SliderViewModel = new SliderViewModel();
viewModel.ProductViewModel = new ProductViewModel();
BindingContext = viewModel;
}
Bind values from DashboardViewModel on our View:
<RefreshView Command="{Binding SliderViewModel.LoadSliderCommand}"
IsRefreshing="{Binding SliderViewModel.IsBusy, Mode=OneWay}">
<StackLayout Padding="8,0,8,4"
BindableLayout.ItemsSource="{Binding SliderViewModel.SliderShowInfos}"
Orientation="Horizontal"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout>
<Frame Padding="4"
HasShadow="False"
IsClippedToBounds="True"
BackgroundColor="Transparent">
<StackLayout Orientation="Horizontal">
<Frame Padding="0"
HasShadow="False"
CornerRadius="7"
IsClippedToBounds="True">
<Image Source="{Binding SliderViewModel.ImagesSlider}">
</Image>
</Frame>
</StackLayout>
</Frame>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</RefreshView>
<RefreshView Command="{Binding ProductViewModel.LoadProductCommand}"
IsRefreshing="{Binding ProductViewModel.IsBusy, Mode=OneWay}">
<StackLayout Padding="8"
Orientation="Horizontal"
BindableLayout.ItemsSource="{Binding ProductViewModel.ProductInfos}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Frame Padding="5,0"
HasShadow="False"
IsClippedToBounds="True"
BackgroundColor="#fff">
</Frame>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</RefreshView>
Give it a try and let me know if it works.

Multilevel Listview in xamarin forms

I need to make a Multilevel Listview in Xamarin forms.
Currently, I am using this for my Single level Grouping in my Menu Page
http://www.compliancestudio.io/blog/xamarin-forms-expandable-listview
I need to update something like the attached image. I have tried some of the Solution but all in vain.
Anyone has any idea about this.
Thanks
You could do this with Expander of Xamarin Community Toolkit.
Install Xamarin Community Toolkit from NuGet: https://www.nuget.org/packages/Xamarin.CommunityToolkit/
Usage: xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
Xaml:
<ContentPage.BindingContext>
<viewmodels:RootViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<ScrollView Margin="20">
<StackLayout BindableLayout.ItemsSource="{Binding roots}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<xct:Expander>
<xct:Expander.Header>
<Label Text="{Binding Root}"
FontAttributes="Bold"
FontSize="Large" />
</xct:Expander.Header>
<StackLayout BindableLayout.ItemsSource="{Binding Node}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<xct:Expander Padding="10">
<xct:Expander.Header>
<Label Text="{Binding Key.Node}" FontSize="Medium" />
</xct:Expander.Header>
<StackLayout BindableLayout.ItemsSource="{Binding Value}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label Text="{Binding SubRoot}" FontSize="Small" />
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</xct:Expander>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</xct:Expander>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
</ContentPage.Content>
Model:
public class Roots
{
public string Root { get; set; }
public Dictionary<Nodes, List<SubRoots>> Node { get; set; }
}
public class Nodes
{
public string Node { get; set; }
}
public class SubRoots
{
public string SubRoot { get; set; }
}
ViewModel:
public class RootViewModel
{
public List<Roots> roots { get; private set; }
public RootViewModel()
{
CreateMonkeyCollection();
}
public void CreateMonkeyCollection()
{
List<SubRoots> subRoot = new List<SubRoots>() { new SubRoots() { SubRoot = "SubNode" } };
Dictionary<Nodes, List<SubRoots>> node = new Dictionary<Nodes, List<SubRoots>>();
node.Add(new Nodes() { Node="Nodo1"}, null);
node.Add(new Nodes() { Node="Nodo2"}, null);
node.Add(new Nodes() { Node="Nodo3"}, null);
node.Add(new Nodes() { Node="Nodo4"}, subRoot);
roots = new List<Roots>()
{
new Roots(){ Root="Root1", Node=node},
new Roots(){ Root="Root2", Node= null}
};
}
}

Xamarin Forms - Using bindable layout to access different parts of the model

I'm trying to setup a gallery of images, that also features the ability to click on the image to show/hide a grid with image description, using the MVVM process.
However, i think my model, view model and view is not correctly setup because i am only able to display the images in the stacklayout if i use BindableLayout.ItemsSource="{Binding GalleryList.Gallery}" in the top stacklayout, but not able to access the other parts of the class, i.e. only the Gallery list.
So essentially, I cant access InfoGridVisible binding, since the it can't find/reach it? (i'm not sure to be honest).
Here is a snippet of my work so far:
View - testpage.xaml:
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="testproject.Pages.testpage"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:styles="clr-namespace:testproject.Styles"
xmlns:behaviours="clr-namespace:testproject.ViewModels"
mc:Ignorable="d"
xmlns:pancakeview="clr-namespace:Xamarin.Forms.PancakeView;assembly=Xamarin.Forms.PancakeView"
xmlns:controls="clr-namespace:ImageCircle.Forms.Plugin.Abstractions;assembly=ImageCircle.Forms.Plugin"
BackgroundColor="Orange"
x:Name="Root">
<ContentPage.Content>
<ScrollView>
<Grid
Grid.Row="3">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!--PROBLEM STARTS HERE -->
<StackLayout x:Name="GalleryStk" Grid.Row="1" Orientation="Vertical" BindableLayout.ItemsSource="{Binding GalleryList.Gallery}" Margin="10,0,10,0">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Grid RowSpacing="5" ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
</Grid.RowDefinitions>
<Frame x:Name="ImageFrame" CornerRadius="20" Padding="0" IsClippedToBounds="True" Grid.Row="0">
<Image Source="{Binding Image}" Aspect="AspectFill" Grid.RowSpan="2" HorizontalOptions="Center" VerticalOptions="Center">
<Image.GestureRecognizers>
<TapGestureRecognizer BindingContext="{Binding Source={x:Reference Root}, Path=BindingContext}" Command="{Binding TapCommand}"/>
</Image.GestureRecognizers>
</Image>
</Frame>
<!--<Image Source="ShadowOverlay" Grid.RowSpan="2" Aspect="Fill" VerticalOptions="End" HorizontalOptions="Fill" />-->
<Grid x:Name="InfoGrid" RowSpacing="10" ColumnSpacing="10" Grid.Row="0" IsEnabled="False" IsVisible="{Binding GalleryList.InfoGridVisible, Source={x:Reference InfoGrid}}">
<Grid.RowDefinitions>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="AUTO"/>
</Grid.ColumnDefinitions>
<BoxView Color="Black" Opacity="0.5" CornerRadius="20" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Grid.Row="0" Grid.RowSpan="5" Grid.ColumnSpan="3"/>
<Label Text="{Binding Title}" Padding="10,10,0,0" Grid.Row="0" Grid.ColumnSpan="3" Style="{StaticResource TitleLabel}" LineBreakMode="NoWrap"/>
<!--<controls:CircleImage Source="{Binding ProfileImage}" Aspect="AspectFill" Grid.Row="1" Grid.Column="0" WidthRequest="25" HeightRequest="25" />-->
<Label Text="{Binding Description}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" Padding="10,0,10,0" Grid.Row="1" Grid.RowSpan="4" Grid.ColumnSpan="3" Style="{StaticResource HandleLabel}"/>
<!--<Button Text="See More" x:Name="ExpandContractButton" Clicked="ExpandContractButton_Clicked" Padding="10,0,10,0" Grid.Row="2" Grid.RowSpan="3" Grid.ColumnSpan="3"/>-->
<!--<StackLayout Orientation="Horizontal" VerticalOptions="Center" Grid.Column="2" Grid.Row="1" Spacing="5">
<Image Source="Eye"/>
<Label Text="{Binding ViewCount, StringFormat='{0:N0}'}" Style="{StaticResource HandleLabel}" />
<Label Text="views" Style="{StaticResource BodyLabel}"/>
</StackLayout>-->
</Grid>
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</Grid>
</ScrollView>
</ContentPage.Content>
</ContentPage>
Code behind for view - testpage.xaml.cs
public partial class testpage : ContentPage
{
private Image _galleryImage;
public Grid GalleryInfoGrid;
private static testpage _instance;
public UserProfileViewModel vm { get; }
public static testpage Instance
{
get
{
if (_instance == null)
_instance = new testpage();
return _instance;
}
}
public testpage()
{
InitializeComponent();
vm = new UserProfileViewModel();
BindingContext = new UserProfileViewModel();
//var _galleryInfoGrid = (Grid)Root.FindByName("InfoGrid");
//_galleryInfoGrid.IsEnabled = true;
//_galleryInfoGrid.IsVisible = true;
}
Model:
{
public List<GalleryImage> Gallery { get; set; }
public string InfoGridVisible;
public string InfoGridEnabled;
}
public class GalleryImage
{
public string Title { get; set; }
public string Image { get; set; }
public string Description { get; set; }
}
Services (data):
public class ProfileService
{
private static ProfileService _instance;
public static ProfileService Instance
{
get
{
if (_instance == null)
_instance = new ProfileService();
return _instance;
}
}
public GalleryList GetGallery()
{
return new GalleryList
{
InfoGridEnabled = "False",
InfoGridVisible = "False",
Gallery = new List<GalleryImage>
{
new GalleryImage { Title="sevilla01.jpg", Image = "sevilla01.jpg", Description="Description1Description1Description1Description1 Description1Description1Description1 Description1Description1" },
new GalleryImage { Title="sevilla02.jpg", Image = "sevilla02.jpg", Description="Description2 Description1Description1Description1Description1Descript ion1Description1" },
new GalleryImage {Title="sevilla03.jpg", Image = "sevilla03.jpg", Description="Description3Description1Description1Description1" },
new GalleryImage {Title="sevilla04.jpg", Image = "sevilla04.jpg", Description="Description4Description1Description1" },
new GalleryImage {Title="sevilla05.jpg", Image = "sevilla05.jpg", Description="Description5Description1" },
new GalleryImage {Title="sevilla06.jpg", Image = "sevilla06.jpg", Description="Description6" },
new GalleryImage {Title="sevilla07.jpg", Image = "sevilla07.jpg", Description="Description7Description1Description1Description1Description1Description1Description1Description1Description1 Description1" }
}
};
}
}
View Model - UserProfileViewModel.cs:
public class UserProfileViewModel : BindableObject
{
//Models
private Profile _profile;
private GalleryList _galleryList;
int taps = 0;
ICommand tapCommand;
public UserProfileViewModel()
{
Profile = ProfileService.Instance.GetProfile();
GalleryList = ProfileService.Instance.GetGallery();
tapCommand = new Command(OnTapped);
}
public ICommand TapCommand
{
get { return tapCommand; }
}
void OnTapped(object s)
{
taps++;
//var info1Grid = testpage.Instance.GalleryInfoGrid;
//info1Grid.IsVisible = true;
//info1Grid.IsEnabled = true;
//GalleryList.InfoGridEnabled = "False";
//GalleryList.InfoGridVisible = "False";
Console.WriteLine("parameter: " + taps + " " + GalleryList.InfoGridVisible);
OnPropertyChanged();
}
public Profile Profile
{
get { return _profile; }
set
{
_profile = value;
OnPropertyChanged();
}
}
public GalleryList GalleryList
{
get { return _galleryList; }
set
{
_galleryList = value;
OnPropertyChanged();
}
}
}
Apologies for the code dump, any help would be appreciated as i have been stuck on this for a few days.
If there is a better method of implementing this, i would love to hear it.
What I want you try is something like this:
public class GalleryList : BindableObject {
private string _InfoGridVisible { get; set; }
private string _InfoGridEnabled { get; set; }
public List<GalleryImage> Gallery { get; set; }
public string InfoGridVisible
{
get { return _InfoGridVisible; }
set
{
_InfoGridVisible = value;
OnPropertyChanged();
}
}
public string InfoGridEnabled
{
get { return _InfoGridEnabled; }
set
{
_InfoGridEnabled = value;
OnPropertyChanged();
}
}
}
public class GalleryImage : BindableObject {
private string _Title { get; set; }
private string _Image { get; set; }
private string _Description { get; set; }
public string Title
{
get { return _Title; }
set
{
_Title = value;
OnPropertyChanged();
}
}
public string Image
{
get { return _Image; }
set
{
_Image = value;
OnPropertyChanged();
}
}
public string Description
{
get { return _Description; }
set
{
_Description = value;
OnPropertyChanged();
}
}
}
InfoGridVisible is not a property and hence binding when performs a lookup will never find it, the way to solve this is :
public class GalleryList
{
public List<GalleryImage> Gallery { get; set; }
public string InfoGridVisible { get; set; }
public string InfoGridEnabled { get; set; }
}
In the case of how to be able to get the Object within a Command you need to tell your TapGesture how to access to that command through this 2 options:
<TapGestureRecognizer Command ="{Binding Path=BindingContext.CommandToCall, Source={x:Reference Name=ParentPage}}" CommandParameter="{Binding .}"/>
Or:
<TapGestureRecognizer Command ="{Binding Source={RelativeSource AncestorType={x:Type vm:vmWhereCommandIs}}, Path=CommandToCall}" CommandParameter="{Binding .}"/>
Just in case you need more info here there are some resources:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/data-binding/relative-bindings
https://www.xamarinexpert.it/how-to-correctly-use-databinding-with-listview-and-collectionview/

Xamarin Forms CollectionView Command not working

I have a collection view with the command binded, but for some reason when I select an item the action is never called in the viewmodel, heres my ViewModel code:
public class PlatillosViewModel : INotifyPropertyChanged
{
private INavigation Navigation;
public event PropertyChangedEventHandler PropertyChanged;
public List<PlatilloModel> Platillos { get; set; }
public List<GrupoModel> Grupos { get; set; }
public ICommand SelectedGroupCommand => new Command(SelectedGroup);
public PlatillosViewModel(INavigation navigation)
{
Navigation = navigation;
PlatillosRepository repository = new PlatillosRepository();
Platillos = repository.GetAll().ToList();
GrupoRepository grupoRepository = new GrupoRepository();
Grupos = grupoRepository.GetAll().ToList();
}
public ICommand SelectedPlatilloCommand => new Command<PlatilloModel>(async platillo =>
{
await Navigation.PushAsync(new PlatilloView());
});
void SelectedGroup()
{
PlatillosRepository platillosRepository = new PlatillosRepository();
//Platillos = platillosRepository.GetFilteredByGroup(grupoSeleccionado);
}
protected virtual void OnPropertyChanged(string property = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
And here is my Page:
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ComanderoMovil.Views.PlatillosView"
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
ios:Page.UseSafeArea="true"
xmlns:behaviorsPack="clr-namespace:Xamarin.Forms.BehaviorsPack;assembly=Xamarin.Forms.BehaviorsPack">
<ContentPage.Content>
<StackLayout>
<SearchBar> </SearchBar>
<StackLayout Orientation="Horizontal">
<CollectionView ItemsSource="{Binding Grupos}"
HeightRequest="50"
ItemsLayout="HorizontalList"
SelectionMode="Single"
SelectedItem="{Binding SelectedGroupCommand, Mode=TwoWay}">
<CollectionView.ItemTemplate>
<DataTemplate>
<ContentView>
<Label Margin="2"
BackgroundColor="Black"
Text="{Binding nombre}"
TextColor="White"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Center"
FontSize="Small"></Label>
</ContentView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
<ListView Grid.Column="2"
HasUnevenRows="True"
SeparatorVisibility="None"
ItemsSource="{Binding Platillos}">
<ListView.Behaviors>
<behaviorsPack:SelectedItemBehavior Command="{Binding SelectedPlatilloCommand}"/>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView Padding="2, 5, 5, 0">
<Frame OutlineColor="Black"
Padding="10"
HasShadow="False">
<StackLayout Orientation="Horizontal">
<Label Margin="10"
Text="{Binding clave_platillo}"
FontSize="Large"
HorizontalOptions="Start"></Label>
<Label Margin="10"
HorizontalTextAlignment="End"
Text="{Binding nombre}"></Label>
</StackLayout>
</Frame>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
I have tried adding the command to the items inside the collection view, replacing labels for buttons, but still doesn't work, I've also tried to use SelectionChangedCommand in the collection view, and still the same issue, the only way I can make it work is handling the item selection in the View, but I want to stay true to MVVM.
Here is my GrupoModel:
public class GrupoModel
{
public string clave_grupo { get; set; }
public int id_clasificacion { get; set; }
public int id_grupo { get; set; }
public string nombre { get; set; }
public bool pedirClave { get; set; }
public bool status { get; set; }
public int tipo { get; set; }
}
and here is an image of what im trying to do:
If you read the document:
When the SelectionMode property is set to Single, a single item in the
CollectionView can be selected. When an item is selected, the
SelectedItem property will be set to the value of the selected item.
When this property changes, the SelectionChangedCommand is executed
(with the value of the SelectionChangedCommandParameter being passed
to the ICommand), and the SelectionChanged event fires.
When you want to bind a Commond, you should bind to the SelectionChangedCommand instead of SelectedItem. Change your code like below and it will work:
<CollectionView
HeightRequest="50"
ItemsLayout="HorizontalList"
SelectionMode="Single"
SelectionChangedCommand="{Binding SelectedGroupCommand, Mode=TwoWay}"
>
The command should go in the class of GrupoModel instead of the PlatillosViewModel
public List<GrupoModel> Grupos { get; set; }
Should be "linked" to class GrupoModel that have properties and a commandwhich will listen, something like:
Class GrupoModel
{
public int Id { get; set; }
public string Foo { get; set; }
public ICommand SelectedGroupCommand => new Command(Completar);
private async void Completar()
{
await ViewModels.PlatillosViewModel.GetInstancia().SelectedGroup(this);
}
}
This way each element of Grupos will have a command to listen.
BTW: Shouldn't Grupos be an ObservableCollection?

How to use mvvm to create login page in xamarin forms

I'm trying to create login page in xamarin forms using mvvm design pattern
I got and expectation in my code but i don't know the reason
I Created ViewModel page it's code
public class LoginViewModel:ContentPage,INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public User user { get; set; }
private string username;
public string Username
{
get { return username; }
set
{
username = value;
PropertyChanged(this, new PropertyChangedEventArgs("Username"));
}
}
private string password;
public string Password
{
get { return password; }
set { password = value;
PropertyChanged(this, new PropertyChangedEventArgs("Password"));
}
}
public ICommand SubmitCommand { set; get; }
public LoginViewModel()
{
user = new User();
SubmitCommand = new Command(OnSubmit);
}
private async Task<bool> CheckLogin()
{
user = await Services.LoginService.GetUserByUsername(username);
if (user != null)
{
if (user.Password.Trim().Equals(password))
{
return true;
}
else
{
await LoginView.loginView.DisplayAlert("Error", "Invalid Login, try again", "OK");
return false;
}
}
return false;
}
private async void OnSubmit()
{
await CheckLogin();
}
}
}
and MyLoginView.xaml
<ContentPage
xmlns:vm="clr-namespace:PickNgo.ViewModel"
x:Class="PickNgo.View.LoginView"
BackgroundColor="White">
<ContentPage.Content>
<StackLayout>
<StackLayout BackgroundColor="#F27D0C">
<Image HorizontalOptions="Center" WidthRequest="300" Source="logo.png"/>
</StackLayout>
<StackLayout Padding="100,10,100,0">
<Frame CornerRadius="10" BackgroundColor="White" Padding="0" Margin="0">
<Entry x:Name="Email" Text="{Binding Username}" Placeholder="Enter your username"
PlaceholderColor="Black"
Keyboard="Email"
TextColor="Black"/>
</Frame>
<BoxView HeightRequest="10"/>
<Frame CornerRadius="10" BackgroundColor="White" Padding="0" Margin="0">
<Entry x:Name="Password" Text="{Binding Password}" Placeholder="Enter your password"
PlaceholderColor="Black" HeightRequest="40" WidthRequest="40"
IsPassword="True"
TextColor="Black"/>
</Frame>
</StackLayout>
<StackLayout Padding="100,30,100,0">
<Frame CornerRadius="20" BackgroundColor="#FF6600" Padding="0" Margin="0">
<Button Command="{Binding SubmitCommand}" Text="Login" Margin="0" TextColor="White" BackgroundColor="#FF6600"
FontAttributes="Bold" FontSize="Large"/>
</Frame>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
and my login.cs
public partial class LoginView : ContentPage
{
public Action DisplayInvalidLoginPrompt;
public static LoginView loginView;
public LoginView ()
{
InitializeComponent();
}
}
It throw objectSystem.NullReferenceException: Object reference not set to an instance of an object.
in this line
await LoginView.loginView.Navigation.PushModalAsync(new WelcomeView());
when i debug i found loginview is set to null but i don't know why ... any help ?
Its because you haven't initialized LoginView's loginView variable.
To be honest, you really don't need the public static LoginView loginView variable (the class itself is already of type LoginView). If you want to display an alert, simply use PickNgo.App.Current.MainPage.DisplayAlert("", "", "OK"); (assuming PickNgo is the name of your project)

Resources