Using MVVM with xamarin forms. ProductPage contains a ListView of products. Each product has a picker. User selects quantity for what items they wish to purchase. User clicks shopping cart image. ShoppingCartPage loads showing the items the user has selected but the picker is blank. I would like the picker to display the quantity chosen from the productaPage I have stepped through the code behind and the
Model.ListQuantites = List_Quantites;
Model.SelectedQuantity = Model.ListQuantites.SingleOrDefault(p => p.Key == tempQuantity.Key && p.Value == tempQuantity.Value)
is storing the correct quantity value on the OnAppearing() in ShoppingCartPage, but I cant figure out why picker is not showing correct values.
Have tried all suggestions from the following:
How to Display Picker when page load without tapping on title Xamarin.Forms
https://forums.xamarin.com/discussion/153788/set-a-picker-selecteditem-on-page-startup
xamarin.forms Update Listview with picker
Item to Display for Picker don't show up on load
but still cant get it working does anyone have any idea what I am doing wrong?
Thanks for any advice
public class ProductModel : INotifyPropertyChanged
{
//Event
public event PropertyChangedEventHandler PropertyChanged;
//Fields
//[PrimaryKey, AutoIncrement]
private int _ProductId;
private string _ProductName;
private string _Quantity;
private string _Description;
private string _Image;
private decimal _Price;
private decimal _SubTotalForItem;
private string _Genre;
public ObservableCollection<Quantity> _ListQuantites;
//Constructor
public ProductModel()
{
//Subscription
this.PropertyChanged += OnPropertyChanged;
}
[PrimaryKey, AutoIncrement]
public int ProductId
{
get { return _ProductId; }
set
{
if (_ProductId == value) return;
_ProductId = value;
OnPropertyChanged();
}
}
//Properties
public string Quantity
{
get
{
return _Quantity;
}
set
{
_Quantity = value;
OnPropertyChanged();
}
}
public ObservableCollection<Quantity> ListQuantites
{
get
{
return _ListQuantites;
}
set
{
_ListQuantites = value;
OnPropertyChanged();
}
}
private Quantity _selectedQuantity;
public Quantity SelectedQuantity
{
get
{
return _selectedQuantity;
}
set
{
if (value == null)
{
_selectedQuantity = _selectedQuantity;
}
else
{
_selectedQuantity = value;
OnPropertyChanged();
}
}
}
[MaxLength(50)]
public string Description
{
get
{
return _Description;
}
set
{
_Description = value;
OnPropertyChanged();
}
}
public string Image
{
get
{
return _Image;
}
set
{
_Image = value;
OnPropertyChanged();
}
}
public decimal Price
{
get
{
return _Price;
}
set
{
_Price = value;
OnPropertyChanged();
}
}
public decimal SubTotalForItem
{
get
{
return _SubTotalForItem;
}
set
{
_SubTotalForItem = value;
OnPropertyChanged();
}
}
public string Genre
{
get
{
return _Genre;
}
set
{
_Genre = value;
OnPropertyChanged();
}
}
public string ProductName
{
get { return _ProductName; }
set
{
_ProductName = value;
OnPropertyChanged();
}
}
//OnPropertyChanged
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(SelectedQuantity))
{
//test quantity amount
}
}
// [NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
----------------
public class Quantity
{
public int Key { get; set; }
public string Value { get; set; }
}
----------------
public class PickerService
{
public static ObservableCollection<Quantity> GetQuantitiesForProductPage()
{
var quantities = new ObservableCollection<Quantity>()
{
new Quantity() {Key=1, Value="0"},
new Quantity() {Key=2, Value="1"},
new Quantity() {Key=3, Value="2"},
new Quantity() {Key=4, Value="3"},
new Quantity() {Key=5, Value="4"},
new Quantity() {Key=6, Value="5"},
new Quantity() {Key=7, Value="6"},
new Quantity() {Key=8, Value="7"},
new Quantity() {Key=9, Value="8"},
new Quantity() {Key=10, Value="9"},
new Quantity() {Key=11, Value="10"}
};
return quantities;
}
public static ObservableCollection<Quantity> GetQuantitiesForShoppingcart()
{
var quantities = new ObservableCollection<Quantity>()
{
new Quantity() {Key=1, Value="1"},
new Quantity() {Key=2, Value="2"},
new Quantity() {Key=3, Value="3"},
new Quantity() {Key=4, Value="4"},
new Quantity() {Key=5, Value="5"},
new Quantity() {Key=6, Value="6"},
new Quantity() {Key=7, Value="7"},
new Quantity() {Key=8, Value="8"},
new Quantity() {Key=9, Value="9"},
new Quantity() {Key=10, Value="10"},
};
return quantities;
}
}
--------------
public class ShoppingCartViewModel //: INotifyPropertyChanged
{
private ObservableCollection<ProductModel> _shoppingCartList;
public ObservableCollection<ProductModel> ShoppingCartList
{
get
{
return _shoppingCartList;
}
set
{
if (_shoppingCartList == value) return;
_shoppingCartList = value;
}
}
public ShoppingCartViewModel()
{
ShoppingCartList = new ObservableCollection<ProductModel>();
}
}
----------------
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ShoppingCartPage : ContentPage
{
ShoppingCartViewModel ShoppingCartViewModel = new ShoppingCartViewModel();
public decimal TotalForAllItems;
public ObservableCollection<Quantity> List_Quantites { get; set; }
public ShoppingCartPage()
{
InitializeComponent();
BindingContext = ShoppingCartViewModel;
}
public int CalculateQuantityOfItemsInShoppingCart()
{
int quantityOfProducts = 0;
if (ShoppingCartViewModel.ShoppingCartList != null)
{
foreach (var product in ShoppingCartViewModel.ShoppingCartList)
{
if (product.SelectedQuantity != null)
{
quantityOfProducts += Convert.ToInt32(product.SelectedQuantity.Value);
}
}
}
return quantityOfProducts;
}
protected override void OnDisappearing()
{
App.globalShoppingCartOC = ShoppingCartViewModel.ShoppingCartList;
}
protected override void OnAppearing()
{
base.OnAppearing();
TotalForAllItems = 0.00M;
List_Quantites = PickerService.GetQuantitiesForShoppingcart();
ShoppingCartViewModel.ShoppingCartList.Clear();
if (App.globalShoppingCartOC != null)
{
foreach (ProductModel Model in App.globalShoppingCartOC)
{
if (Model.SelectedQuantity != null)
{
var _quantity = Convert.ToDecimal(Model.SelectedQuantity.Value);
if (_quantity > 0)
{
Model.SubTotalForItem = _quantity * Model.Price;
//this line resets Model.SelectedQuantity to 0, so need to re-populate with tempQuantity value
Quantity tempQuantity = Model.SelectedQuantity;
Model.ListQuantites = List_Quantites;
Model.SelectedQuantity = Model.ListQuantites.SingleOrDefault(p => p.Key == tempQuantity.Key && p.Value == tempQuantity.Value);
ShoppingCartViewModel.ShoppingCartList.Add(Model);
TotalForAllItems += Model.SubTotalForItem;
}
}
}
SubTotalForAllItems.Text = TotalForAllItems.ToString();
}
NoItemsInShoppingCart.Text = CalculateQuantityOfItemsInShoppingCart().ToString();
}
protected async void SI_Invoked(object sender, EventArgs e)
{
var si = sender as SwipeItem;
var productToRemove = si.CommandParameter as ProductModel;
var action = await DisplayAlert("Are you sure you want to remove ", productToRemove.ProductName + ".", "Yes", "No");
if (action)
{
ShoppingCartViewModel.ShoppingCartList.Remove(productToRemove);
Quantity tempQuantity = productToRemove.SelectedQuantity;
decimal tempQuantityValue = Convert.ToDecimal(tempQuantity.Value);
decimal subtotalToRemove = productToRemove.Price * tempQuantityValue;
TotalForAllItems -= subtotalToRemove;
SubTotalForAllItems.Text = TotalForAllItems.ToString();
NoItemsInShoppingCart.Text = CalculateQuantityOfItemsInShoppingCart().ToString();
}
}
public void SCQuantityAndSubtotalUpdated(object sender, EventArgs e)
{
TotalForAllItems = 0.00M;
if (ShoppingCartViewModel.ShoppingCartList != null)
{
foreach (ProductModel Model in ShoppingCartViewModel.ShoppingCartList)
{
var _quantity = Convert.ToDecimal(Model.SelectedQuantity.Value);
if (_quantity > 0)
{
Model.SubTotalForItem = _quantity * Model.Price;
TotalForAllItems += Model.SubTotalForItem;
}
}
SubTotalForAllItems.Text = TotalForAllItems.ToString();
}
NoItemsInShoppingCart.Text = CalculateQuantityOfItemsInShoppingCart().ToString();
}
private void PlaceOrder_BtnClicked(object sender, EventArgs e)
{
}
}
-------------------------
<ContentPage.ToolbarItems>
<ToolbarItem Name="shoppingCartImg" Icon="shopping_cart.png" Priority="0" Order="Primary"/>
<ToolbarItem x:Name="NoItemsInShoppingCart" Priority="0" Order="Primary"/>
</ContentPage.ToolbarItems>
<ContentPage.Content>
<StackLayout>
<ListView ItemsSource="{Binding ShoppingCartList}" IsVisible="True" VerticalOptions="FillAndExpand" HasUnevenRows="True">
<ListView.Header>
<Button Text="Place Order" Clicked="PlaceOrder_BtnClicked"/>
</ListView.Header>
<ListView.Footer>
<Label x:Name="SubTotalForAllItems" HorizontalTextAlignment="End" VerticalTextAlignment="Start" Margin="20,20" FontAttributes="Bold"/>
</ListView.Footer>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<SwipeView>
<SwipeView.LeftItems>
<SwipeItems Mode="Reveal">
<SwipeItem Text="Details" IconImageSource="xamarin_logo.png" CommandParameter="{Binding .}" BackgroundColor="LightBlue" Invoked="SI_Invoked">
</SwipeItem>
</SwipeItems>
</SwipeView.LeftItems>
<!--Content of Swipe View -->
<StackLayout BackgroundColor="Green" HorizontalOptions="StartAndExpand">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" Text="{Binding ProductId}" VerticalOptions="End" IsVisible="False"/>
<controls:CircleImage Grid.Column="1" Grid.Row="0" HeightRequest="60" HorizontalOptions="CenterAndExpand" VerticalOptions="Center" Aspect="AspectFill" WidthRequest="66" Grid.RowSpan="2" Source="{Binding Image}"/>
<Label Grid.Column="2" Grid.Row="0" Text="{Binding ProductName}" VerticalOptions="Start"/>
<Label Grid.Column="2" Grid.Row="1" Text="{Binding Description}" VerticalOptions="End"/>
<Label Grid.Column="3" Grid.Row="0" VerticalOptions="Start" Text="{Binding SubTotalForItem, StringFormat='£{0:0.00}'}"/>
<Picker x:Name="scPicker" ItemsSource="{Binding ListQuantites, Mode=TwoWay}" SelectedItem="{Binding SelectedQuantity, Mode=TwoWay}" ItemDisplayBinding="{Binding Value}" SelectedIndexChanged="SCQuantityAndSubtotalUpdated"/>
</Grid>
</StackLayout>
</SwipeView>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
-------------------------
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ProductPage : ContentPage
{
public ProductPageViewModel productPage_ViewModal;
MainPage RootPage { get => Application.Current.MainPage as MainPage; }
public ProductPage()
{
InitializeComponent();
productPage_ViewModal = new ProductPageViewModel();
BindingContext = productPage_ViewModal;
}
protected override void OnDisappearing()
{
App.globalShoppingCartOC = productPage_ViewModal.WineList;
}
protected override void OnAppearing()
{
base.OnAppearing();
if (App.globalShoppingCartOC != null)
{
//First need to check shoppingCart list for product, if it has been removed in SC,
//then set quantity to 0
foreach (var product in productPage_ViewModal.WineList)
{
var doesProductExistInShoppingCart = App.globalShoppingCartOC.Where(x => x.ProductId == product.ProductId).FirstOrDefault();
if(doesProductExistInShoppingCart == null)
{
if (productPage_ViewModal.WineList.Where(x => x.ProductId == product.ProductId).FirstOrDefault().SelectedQuantity != null)
{
Quantity tempQuantity = new Quantity() { Key = 1, Value = "0" };
product.SelectedQuantity = productPage_ViewModal.List_Quantites.SingleOrDefault(p => p.Key == tempQuantity.Key && p.Value == tempQuantity.Value);
product.ListQuantites = PickerService.GetQuantitiesForProductPage();
// productPage_ViewModal.WineList.Where(x => x.ProductId == product.ProductId).FirstOrDefault().SelectedQuantity.Value = "0";
}
}
}
//Can then update correct quantity for other products still in SC list
foreach (var product in App.globalShoppingCartOC)
{
if (productPage_ViewModal.WineList.Where(x => x.ProductId == product.ProductId).FirstOrDefault().SelectedQuantity != null)
{
Quantity tempQuantity = product.SelectedQuantity;
product.SelectedQuantity = productPage_ViewModal.List_Quantites.SingleOrDefault(p => p.Key == tempQuantity.Key && p.Value == tempQuantity.Value);
product.ListQuantites = PickerService.GetQuantitiesForProductPage();
//productPage_ViewModal.WineList.Where(x => x.ProductId == product.ProductId).FirstOrDefault().SelectedQuantity.Value = product.SelectedQuantity.Value;
}
}
}
NoItemsInShoppingCart.Text = CalculateQuantityOfItemsInShoppingCart().ToString();
}
private async void ShoppingCartClicked(object sender, EventArgs e)
{
MenuPage tempMenu = new MenuPage();
int IdOfMenuClicked = tempMenu.GetIdForNavigationMenu("Shopping Cart");
await RootPage.NavigateFromMenu(IdOfMenuClicked);
}
public void QuantityChanged(object sender, EventArgs e)
{
NoItemsInShoppingCart.Text = CalculateQuantityOfItemsInShoppingCart().ToString();
}
public int CalculateQuantityOfItemsInShoppingCart()
{
int quantityOfProducts = 0;
if (productPage_ViewModal.WineList != null)
{
foreach (var product in productPage_ViewModal.WineList)
{
if (product.SelectedQuantity != null)
{
quantityOfProducts += Convert.ToInt32(product.SelectedQuantity.Value);
}
}
}
return quantityOfProducts;
}
}
-------------------------
Title="ProductPage">
<ContentPage.ToolbarItems>
<ToolbarItem Name="shoppingCartImg" Icon="shopping_cart.png" Priority="0" Order="Primary" Activated="ShoppingCartClicked"/>
<ToolbarItem x:Name="NoItemsInShoppingCart" Priority="0" Order="Primary" Activated="ShoppingCartClicked"/>
</ContentPage.ToolbarItems>
<ContentPage.Content>
<StackLayout>
<ListView IsVisible="True" VerticalOptions="FillAndExpand" HasUnevenRows="True" ItemsSource="{Binding WineList}"> <!--HeightRequest="1500"-->
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout BackgroundColor="Green" HorizontalOptions="StartAndExpand">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" Text="{Binding ProductId}" VerticalOptions="End" IsVisible="False"/>
<controls:CircleImage Grid.Column="1" Grid.Row="0" HeightRequest="60" HorizontalOptions="CenterAndExpand" VerticalOptions="Center" Aspect="AspectFill" WidthRequest="66" Grid.RowSpan="2" Source="{Binding Image}"/>
<Label Grid.Column="2" Grid.Row="0" Text="{Binding ProductName}" VerticalOptions="Start"/>
<Label Grid.Column="2" Grid.Row="1" Text="{Binding Description}" VerticalOptions="End"/>
<Label Grid.Column="3" Grid.Row="0" VerticalOptions="Start" Text="{Binding Price, StringFormat='£{0:0.00}'}"/>
<Picker x:Name="productPicker" ItemsSource="{Binding ListQuantites}" ItemDisplayBinding="{Binding Value}" SelectedIndexChanged="QuantityChanged" SelectedItem ="{Binding SelectedQuantity}"/>
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
----------------------------------------------
public class ProductPageViewModel : BindableObject, INotifyPropertyChanged
{
public ObservableCollection<ProductModel> WineList { get; set; }
public ObservableCollection<Quantity> List_Quantites { get; set; }
public ProductPageViewModel()
{
List_Quantites = PickerService.GetQuantitiesForProductPage();//.OrderBy(c => c.Value).ToList();
WineList = new ObservableCollection<ProductModel>();
WineList.Add(new ProductModel { ProductId = 1, ProductName = "Wine 1", ListQuantites = List_Quantites, Image = "wine.jpg", Quantity = "0", Description = "700ml", Price = 10.00M, SubTotalForItem = 0.00M, Genre = "Wine" });
WineList.Add(new ProductModel { ProductId = 2, ProductName = "Wine 2", ListQuantites = List_Quantites, Image = "wine.jpg", Quantity = "0", Description = "700ml", Price = 10.00M, SubTotalForItem = 0.00M, Genre = "Wine" });
WineList.Add(new ProductModel { ProductId = 3, ProductName = "Wine 3", ListQuantites = List_Quantites, Image = "wine.jpg", Quantity = "0", Description = "700ml", Price = 5.50M, SubTotalForItem = 0.00M, Genre = "Wine" });
WineList.Add(new ProductModel { ProductId = 4, ProductName = "Wine 4", ListQuantites = List_Quantites, Image = "wine.jpg", Quantity = "0", Description = "700ml", Price = 5.50M, SubTotalForItem = 0.00M, Genre = "Wine" });
}
UPDATE
On the ProductPage if the user changes the quantity for a product it is updated automatically in the productPage_ViewModal.WineList,
when the user clicks the shoppingCart icon OnDisappearing() is called on the ProductPage adding the productPage_ViewModal.WineList to the
global ObservableCollection<ProductModel> globalShoppingCartOC
, (stored in the App.xaml.cs page)
From The OnAppearing() in the ShoppingCartPage.xaml.cs, the ShoppingCartViewModel.ShoppingCartList is populated by iterating through the globalShoppingCartOC,
if the quantity is greater than 0, add to ShoppingCartViewModel.ShoppingCartList, and pre select the quantity chosen in the picker with:
Quantity tempQuantity = Model.SelectedQuantity;
Model.ListQuantites = List_Quantites;
Model.SelectedQuantity = Model.ListQuantites.SingleOrDefault(p => p.Key == tempQuantity.Key && p.Value == tempQuantity.Value);
The problem being I can see by stepping through the code that the 'Model.SelectedQuantity' will have the correct value stored, but it doesnt update on ShoppingCart screen with the picker
<Picker x:Name="scPicker" Title="--Select--" ItemsSource="{Binding ListQuantites, Mode=TwoWay}" SelectedItem="{Binding SelectedQuantity, Mode=TwoWay}" ItemDisplayBinding="{Binding Value}" SelectedIndexChanged="SCQuantityAndSubtotalUpdated"/>