Passing Data from CollectionView to details page Shell Navigation - xamarin.forms

I have a CollectionView with a list of names and a label view profile. When I click view profile I want to navigate to the details page. However I am confused with the shell navigation and how to pass data from one page to another.
As well as with CollectionView. This is my first time working with it. I am clicking on viewprofile and navigating to the details page however the data I pass through doesn't pass. The PetName doesn't appear as I guess there is no value. I am using a command to call navigation.
I guess I am unsure how to get the Petsname when I click view profile then pass it on.
MyIDPageViewModel:
class MyIDPageViewModel : INotifyPropertyChanged
{
FirebaseHelper firebaseHelper = new FirebaseHelper();
public Command NavToDetailCommand { get; set; }
public ObservableCollection<PetProfile> source;
public ObservableCollection<PetProfile> PetInfo { get; private set; }
public ObservableCollection<PetProfile> EmptyPetInfo
{
get => source;
private set
{
if (value != source)
{
source = value;
OnPropertyChanged(nameof(EmptyPetInfo));
}
}
}
public MyIDPageViewModel()
{
source = new ObservableCollection<PetProfile>();
CreatePetProfileCollection();
NavToDetailCommand = new Command<PetProfile>(OnNav);
}
private async void OnNav(PetProfile PetDetailsPage)
{
if (PetDetailsPage == null)
return;
await Shell.Current.GoToAsync($"//MyIDPage/PetDetailsPage?PetName={PetDetailsPage.PetName}");
}
public async void CreatePetProfileCollection()
{
var petProfiles = await firebaseHelper.GetAllUserPetInfos();
if (petProfiles != null)
{
EmptyPetInfo = new ObservableCollection<PetProfile>();
foreach (var groupitems in petProfiles)
{
EmptyPetInfo.Add(new PetProfile() { PetName = groupitems.PetName, UserEmail = groupitems.UserEmail, Breed = groupitems.Breed, DOB = groupitems.DOB, Gender = groupitems.Gender, Weight = groupitems.Weight, CareInformation = groupitems.CareInformation });
}
}
}
public async void Refresh()
{
EmptyPetInfo.Clear();
var petProfiles = await firebaseHelper.GetAllUserPetInfos();
if (petProfiles != null)
{
EmptyPetInfo = new ObservableCollection<PetProfile>();
foreach (var groupitems in petProfiles)
{
EmptyPetInfo.Add(new PetProfile() { PetName = groupitems.PetName, UserEmail = groupitems.UserEmail, Breed = groupitems.Breed, DOB = groupitems.DOB, Gender = groupitems.Gender, Weight = groupitems.Weight, CareInformation = groupitems.CareInformation });
}
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
My collection View:
<CollectionView ItemsSource="{Binding EmptyPetInfo}" SelectedItem="{Binding SelectedPet, Mode=OneWay}" SelectionChanged="OnCollectionViewSelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Grid.Row="0"
Text="Image Goes Here"
FontAttributes="Bold" />
<Label Grid.Column="1"
Grid.Row="0"
Text="{Binding PetName}"
FontAttributes="Bold"
x:Name="labelpetname"/>
<Label Grid.Row="0"
Grid.Column="2"
Text="View Profile"
FontAttributes="Italic"
VerticalOptions="End"
>
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding NavToDetailCommand, Source={
RelativeSource AncestorType={x:Type local:MyIDPageViewModel}
}}"
CommandParameter="{Binding .}">
</TapGestureRecognizer>
</Label.GestureRecognizers>
</Label>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
PetDetailsPage:
public partial class PetDetailsPage : ContentPage
{
FirebaseHelper firebaseHelper = new FirebaseHelper();
private string _PetName;
public string PetName
{
get => _PetName;
set
{
if (value != _PetName)
{
_PetName = value;
}
}
}
public PetDetailsPage()
{
InitializeComponent();
RetrivePetInfo();
}
private async void RetrivePetInfo()
{
var pet = await firebaseHelper.GetPet(_PetName);
if (pet != null)
{
petname.Text = pet.PetName;
}
}
private void Button_Clicked(object sender, EventArgs e)
{
}
}
Code for retrieving PetInfo from firebase:
public async Task<PetProfile> GetPet(string petname)
{
var useremail = Preferences.Get("UserSignInEmail", "");
var PetProfiles = await GetAllPetInfos();
await firebase
.Child("PetProfiles")
.OnceAsync<PetProfile>();
return PetProfiles.Where(a => a.UserEmail == useremail && a.PetName == petname).FirstOrDefault();
}

As Cfun mentioned in comment , Add a QueryProperty attribute above the class .
[QueryProperty(nameof(PetName), "PetName")]
public partial class PetDetailsPage : ContentPage{}
set method of propery PetName is invoked later than class constructor, so _PetName is null in RetrivePetInfo method , as a workaround , you could call RetrivePetInfo directly in set method instead in constructor .
public string PetName
{
get => _PetName;
set
{
if (value != _PetName)
{
_PetName = value;
RetrivePetInfo();
}
}
}

Related

ListView is not updating

I have an ItemCart. I would like to show the CartItems in a listview, with ItemName and quantity.
When I press a button, the quantity of the item in listView should increase.
My problem:
The CartItem of the observableCollection is increased each time i pressed the button correctly, but the UI is not updating/refreshing. Is anyone there who can help me??
Here is the code:
public class AboutViewModel : BaseViewModel
{
public AboutViewModel()
{
AddCartItem = new Command(AddItem);
cartItems = new ObservableCollection<CartItem>();
cartItems.Add(new CartItem { ItemName = "Item1", Quantity = 4 });
cartItems.Add(new CartItem { ItemName = "Item2", Quantity = 2 });
}
public ICommand AddCartItem { get; }
public ObservableCollection<CartItem> cartItems;
public ObservableCollection<CartItem> CartItems
{
get { return cartItems; }
set
{
cartItems = value;
OnPropertyChanged("CartItems");
}
}
void AddItem()
{
cartItems[0].Quantity++;
CartItems = cartItems;
}
}
<StackLayout>
<ListView ItemsSource="{Binding CartItems">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding ItemName, Mode=TwoWay}"
Detail="{Binding Quantity, Mode=TwoWay}"></TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackLayout>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<BoxView
Color="Blue" />
<Button Text="Add Name" Command="{Binding AddCartItem}"/>
If you want to refresh UI once changing the value of property Quantity in model CartItem , you need to implement interface INotifyPropertyChanged for CartItem.
Based on your code, I have achieved this function, you can refer to the following code:
CartItem.cs
public class CartItem: INotifyPropertyChanged
{
public string ItemName { get; set; }
//public int Quantity { get; set; }
int _quantity;
public int Quantity
{
get => _quantity;
set => SetProperty(ref _quantity, value);
}
bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
AboutViewModel.cs
public class AboutViewModel
{
public AboutViewModel()
{
AddCartItem = new Command(AddItem);
CartItems = new ObservableCollection<CartItem>();
CartItems.Add(new CartItem { ItemName = "Item1", Quantity = 4 });
CartItems.Add(new CartItem { ItemName = "Item2", Quantity = 2 });
}
public ICommand AddCartItem { get; }
public ObservableCollection<CartItem> CartItems { get; set; }
void AddItem()
{
CartItems[0].Quantity++;
}
}
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" xmlns:listviewapp="clr-namespace:ListViewApp"
x:Class="ListViewApp.MainPage">
<ContentPage.BindingContext>
<listviewapp:AboutViewModel></listviewapp:AboutViewModel>
</ContentPage.BindingContext>
<StackLayout>
<ListView ItemsSource="{Binding CartItems}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding ItemName, Mode=TwoWay}"
Detail="{Binding Quantity, Mode=TwoWay}"></TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Text="Add Name" Command="{Binding AddCartItem}"/>
</StackLayout>
</ContentPage>
Note:
1.In fact, you don't have to define another variable cartItems at all.
You can only define variable CartItems:
public ObservableCollection<CartItem> CartItems { get; set; }
2.For document ObservableCollection Class, we know that this class:
Represents a dynamic data collection that provides notifications when
items get added or removed, or when the whole list is refreshed.
So, if you add modifiers public ObservableCollection<CartItem> for variable CartItems,when you add or remove one or more Items to this list, the UI will be updated. And if you want the UI refresh automatically after you change the value of property Quantity in model CartItem , you need to implement interface INotifyPropertyChanged for CartItem.
public ObservableCollection<CartItem> CartItems { get; set; }

How to make a checkbox stay checked when i navigate through pages? Xamarin

I have this listview and in the listview I have an ItemTemplate with a DataTemplate and a ViewCell in which I have a checkbox named "box1". I want to make it stay checked when i switch pages, but i can't acess it via name because it is in a DataTemplate and in a ViewCell. I have tried to name all the controls down to the checkbox and get access to it like that, but it does not seem to work...
This is my xaml:
<ListView SeparatorVisibility="None"
BackgroundColor="Transparent"
VerticalOptions="Center"
x:Name="listView"
HasUnevenRows="True"
>
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:Meal"
x:Name="mydt"
>
<ViewCell
x:Name="myvc"
>
<Grid BackgroundColor="Transparent"
x:Name="mygrid"
>
<Frame BackgroundColor="Transparent"
CornerRadius="20"
x:Name="myframe"
>
<StackLayout Orientation="Horizontal"
>
<Image Source="meal.png" WidthRequest="59" Margin="0, 0, 15, 0"/>
<StackLayout Orientation="Vertical" WidthRequest="300">
<Label VerticalOptions="Start"
FontSize="20"
Text="{Binding Name}"
FontAttributes="Bold"/>
<Label VerticalOptions="Start"
FontSize="15"
Text="{Binding Ingredients}"/>
<StackLayout Orientation="Horizontal">
<Label VerticalOptions="Start"
FontSize="15"
Text="{Binding Calories}"
TextColor="OrangeRed"/>
<Label Text="kcal"
FontSize="15"
TextColor="OrangeRed"/>
</StackLayout>
</StackLayout>
<CheckBox
x:Name="box1"
IsChecked="{Binding Checked}"
Color="Green"
Margin="60, 0, 0, 0"
CheckedChanged="box1_CheckedChanged"
BindingContext="{Binding ., Mode=TwoWay}"
/>
</StackLayout>
</Frame>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is my event handler from the Content Page in cs:
private void box1_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
var meal = listView.SelectedItem as Meal;
if (listView.SelectedItem != null)
{
if (e.Value == true)
{
long cal = long.Parse(meal.Calories);
calories_consumed = calories_consumed + cal;
ch = true;
}
else
{
long cal = long.Parse(meal.Calories);
calories_consumed = calories_consumed - cal;
ch = false;
}
}
label_cal.Text = calories_consumed.ToString();
}
This is my updated Meal class using INotifyPropertyChanged:
public class Meal : INotifyPropertyChanged
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
private string ingredients;
public string Ingredients
{
get { return ingredients; }
set
{
ingredients = value;
OnPropertyChanged(nameof(Ingredients));
}
}
private string calories;
public string Calories
{
get { return calories; }
set
{
calories = value;
OnPropertyChanged(nameof(Calories));
}
}
private bool isChecked;
public bool IsChecked
{
get
{
return isChecked;
}
set
{
isChecked = value;
OnPropertyChanged(nameof(IsChecked));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And this is my code behind for the ContentPage:
protected override async void OnAppearing()
{
base.OnAppearing();
listView.ItemsSource = new ObservableCollection<Meal>(await App.Database.GetMealAsync());
}
public static bool ch;
Event handler for the checkbox in the code behind:
private void box1_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
var meal = listView.SelectedItem as Meal;
if (listView.SelectedItem != null)
{
if (e.Value == true)
{
long cal = long.Parse(meal.Calories);
calories_consumed = calories_consumed + cal;
ch = true;
}
else
{
long cal = long.Parse(meal.Calories);
calories_consumed = calories_consumed - cal;
ch = false;
}
}
meal.IsChecked = ch;
label_cal.Text = calories_consumed.ToString();
}
To be short you can just save the value in Preferences or make a variable in a model that is static and bind that values OnAppearing
There are mainly two ways that you could store the checkbox's state.
1.You could use Json to Serialize and deserialize the model that has a IsChecked property that binding with the checkbox.For more details, you could refer to this thread.
Code in checkbox_CheckedChanged event:
private void checkbox_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
var checkbox = (CheckBox)sender;
var selectMeal = checkbox.BindingContext as Meal;
selectMeal.Checked = e.Value;
//save the data and checkbox state,you could save the data as a json string
string json = JsonConvert.SerializeObject(blistView);
Preferences.Set("listmeals", json);
}
2.You could store the state of the checkbox using sqlite-net-pcl.Please refer to below MS official docs for more details.
And then retrieve the check state of the checkbox via OnAppearing Method.
protected override void OnAppearing()
{
// retrieve the check state of the checkbox in your sqlite database.
base.OnAppearing();
TodoItemDatabase database = await TodoItemDatabase.Instance;
listView.ItemsSource = await database.GetItemsAsync();
}
MS official docs:https://learn.microsoft.com/en-us/xamarin/xamarin-forms/data-cloud/data/databases
The way you are connecting your class your view/xaml is incorrect. If you want to do it without MVVM you can go about it by creating a model that implements INotifyPropertyChanged and an ObservableCollection as listView itemssource.
Model class Meal could look like this:
public class Meal : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
private string ingredients;
public string Ingredients
{
get { return ingredients; }
set
{
ingredients = value;
OnPropertyChanged(nameof(Ingredients));
}
}
private string calories;
public string Calories
{
get { return calories; }
set
{
calories = value;
OnPropertyChanged(nameof(Calories));
}
}
private bool isChecked;
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
OnPropertyChanged(nameof(IsChecked));
}
}
#region INotify property changed
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
The MainPage.xaml like this:
<StackLayout>
<ListView SeparatorVisibility="None"
BackgroundColor="Transparent"
VerticalOptions="Center"
x:Name="listView"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:Meal" x:Name="mydt" >
<ViewCell x:Name="myvc" >
<Grid BackgroundColor="Transparent"
x:Name="mygrid" >
<Frame BackgroundColor="Transparent"
CornerRadius="20"
x:Name="myframe">
<StackLayout Orientation="Horizontal">
<Image Source="meal.png" WidthRequest="59" Margin="0, 0, 15, 0"/>
<StackLayout Orientation="Vertical" WidthRequest="300">
<Label VerticalOptions="Start"
FontSize="20"
Text="{Binding Name}"
FontAttributes="Bold"/>
<Label VerticalOptions="Start"
FontSize="15"
Text="{Binding Ingredients}"/>
<StackLayout Orientation="Horizontal">
<Label VerticalOptions="Start"
FontSize="15"
Text="{Binding Calories}"
TextColor="OrangeRed"/>
<Label Text="kcal" FontSize="15" TextColor="OrangeRed"/>
</StackLayout>
</StackLayout>
<CheckBox x:Name="box1" IsChecked="{Binding IsChecked}" Color="Green" Margin="60, 0, 0, 0" />
</StackLayout>
</Frame>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Clicked="Button_Clicked" Text="Check status items"/>
</StackLayout>
And your MainPage.xaml.cs code behind like this:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
listView.ItemsSource = new ObservableCollection<Meal>(new List<Meal>
{
new Meal { Name = "Meal 01", Ingredients = "Ingredients 01", Calories = "250" },
new Meal { Name = "Meal 02", Ingredients = "Ingredients 02", Calories = "350" },
new Meal { Name = "Meal 03", Ingredients = "Ingredients 03", Calories = "450" }
});
}
/// <summary>
/// check items ischecked status
/// </summary>
private void Button_Clicked(object sender, EventArgs e)
{
foreach (var item in listView.ItemsSource)
{
if (item is Meal meal && meal.IsChecked)
System.Diagnostics.Debug.WriteLine($"{meal.Name} is checked");
}
}
}
Look at the other answer for saving the model and/or list. When saved the list can be retrieved in the OnAppearing and set instead of recreated as shown in this example. I've added a button on the bottom that prints out which Meals are selected in the output window. See screenshot:

can I refresh after edit of text

I am displaying results and I added the ability to edit the displayed result. That works I get pop up with the text I want to edit. However I need to go up and down to display the edited text. I have tried to add IsRefreshong property but that has the same result. Do you have any suggestions? I need to display the edited text after I click on "ok " in my pop up not have to scroll down or up and then see the updated property
here is my xaml
<ListView BackgroundColor="{DynamicResource PageBackgroundColor}" x:Name="list"
HasUnevenRows="True" IsRefreshing="{Binding IsRefreshing}"
HorizontalOptions="CenterAndExpand"
VerticalOptions="FillAndExpand"
VerticalScrollBarVisibility="Never"
CachingStrategy="RecycleElement"
ItemsSource="{Binding Results, Mode=TwoWay}"
SeparatorVisibility="Default"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid BackgroundColor="{DynamicResource PageBackgroundColor}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="12*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="12*"/>
<ColumnDefinition Width="4*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="1" Padding="0,3,3,0" Text ="{Binding FieldDescriptor}" Style="{StaticResource SubLabelBlackStyle}" HorizontalOptions="Start" BackgroundColor="{DynamicResource PageBackgroundColor}" HorizontalTextAlignment="Start" />
<Label Grid.Column="3" Padding="0,3,3,0" Text="{Binding FieldValue}" Style="{StaticResource SubLabelBlackStyle}" HorizontalOptions="Start" BackgroundColor="{DynamicResource PageBackgroundColor}" HorizontalTextAlignment="Start" >
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BindingContext.EditTextCommand, Source={x:Reference Name=list}}" CommandParameter="{Binding .}"/>
</Label.GestureRecognizers>
</Label>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
here is the back of my page
public DisplayResult(RemoteCallResult<IEnumerable<DocumentData>> data)
{
InitializeComponent();
BindingContext = new ResultPageViewModel(data);
}
ViewModel property isRefreshing
public bool IsRefreshing
{
get => _isRefreshing;
private set
{
_isRefreshing = value;
NotifyPropertyChanged("IsRefreshing");
}
}
here is the edit method
public async Task EditTextAsync(DocumentData param)
{
PromptResult pResult = await UserDialogs.Instance.PromptAsync(new PromptConfig
{
InputType = InputType.Default,
Text = param.FieldValue,
Title = param.FieldValue,
});
if(pResult != null)
{
_isRefreshing = true;
param.FieldValue = pResult.Text;
Thread.Sleep(5);
_isRefreshing = false;
}
}
MYmodel
public string FieldValue { get; set; }
public string FieldDescriptor { get; set; }
Text="{Binding FieldValue,Mode=TwoWay}"
Add a two-way binding mode and you have not posted the DocumentData Model, Raise this FieldValue property in the model as well. It should do it.
You do not need to use is IsRefreshing tag for changing the value in the MVVM.
Here is running gif.
Please change the viewModel like following format. Achieve the INotifyPropertyChanged interface for all of your properties.
using System.ComponentModel;
using System.Text;
namespace PanCakeView
{
public class MyModel: INotifyPropertyChanged
{
string fieldValue;
public string FieldValue
{
set
{
if (fieldValue != value)
{
fieldValue = value;
OnPropertyChanged("FieldValue");
}
}
get
{
return fieldValue;
}
}
string fieldDescriptor;
public string FieldDescriptor
{
set
{
if (fieldDescriptor != value)
{
fieldDescriptor = value;
OnPropertyChanged("FieldDescriptor");
}
}
get
{
return fieldDescriptor;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Here is my viewModel. I used ObservableCollection for testing.
public class ResultPageViewModel
{
public ObservableCollection<MyModel> Results { get; set; }
public ICommand EditTextCommand { protected set; get; }
public ResultPageViewModel(ObservableCollection<MyModel> myModels)
{
Results = new ObservableCollection<MyModel>();
foreach (var item in myModels)
{
Results.Add(item);
}
EditTextCommand = new Command<MyModel>(async (key) =>
{
PromptResult pResult = await UserDialogs.Instance.PromptAsync(new PromptConfig
{
InputType = InputType.Default,
Text = key.FieldValue,
Title = "change value",
});
if (pResult!=null)
{
key.FieldValue = pResult.Text;
}
});
}
}
I do not know which style you used. I just use style that I setted.
<ListView BackgroundColor="AliceBlue" x:Name="list"
HasUnevenRows="True"
HorizontalOptions="CenterAndExpand"
VerticalOptions="FillAndExpand"
VerticalScrollBarVisibility="Never"
CachingStrategy="RecycleElement"
ItemsSource="{Binding Results, Mode=TwoWay}"
SeparatorVisibility="Default"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid BackgroundColor="Gray">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="12*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="12*"/>
<ColumnDefinition Width="4*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="1" Padding="0,3,3,0" Text ="{Binding FieldDescriptor}" HorizontalOptions="Start" HorizontalTextAlignment="Start" />
<Label Grid.Column="3" Padding="0,3,3,0" Text="{Binding FieldValue}" HorizontalOptions="Start" HorizontalTextAlignment="Start" >
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BindingContext.EditTextCommand, Source={x:Reference Name=list}}" CommandParameter="{Binding .}"/>
</Label.GestureRecognizers>
</Label>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here is layout's background code.
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
ObservableCollection<MyModel> data = new ObservableCollection<MyModel>();
data.Add(new MyModel() { FieldDescriptor= "this is a Descriptor", FieldValue="1" });
data.Add(new MyModel() { FieldDescriptor = "this is a Descriptor", FieldValue = "2" });
data.Add(new MyModel() { FieldDescriptor = "this is a Descriptor", FieldValue = "3" });
data.Add(new MyModel() { FieldDescriptor = "this is a Descriptor", FieldValue = "4" });
data.Add(new MyModel() { FieldDescriptor = "this is a Descriptor", FieldValue = "5" });
data.Add(new MyModel() { FieldDescriptor = "this is a Descriptor", FieldValue = "6" });
this.BindingContext = new ResultPageViewModel(data);
}
}
==============Update=====================
If you use PopUp pageļ¼ŒHere is running gif.
Here is my popup page code.
<?xml version="1.0" encoding="utf-8" ?>
<pages:PopupPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:Rg.Plugins.Popup.Pages;assembly=Rg.Plugins.Popup"
x:Class="PanCakeView.MyPopUpPage">
<Frame
VerticalOptions="Center"
HorizontalOptions="Center"
Padding="20, 20, 20, 20">
<StackLayout>
<Entry x:Name="entryCardName"
FontSize="Small"
Placeholder="{Binding FieldValue}"
Text="{Binding FieldValue}"
TextColor="Black"
ReturnType="Next">
</Entry>
<Button Text="Ok" Command="{Binding ConfirmPopUpCommand}" CommandParameter="{Binding }"/>
</StackLayout>
</Frame>
</pages:PopupPage>
PopUp page's background code.
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MyPopUpPage : Rg.Plugins.Popup.Pages.PopupPage
{
//public string FieldValue { get; set; }
private string _fieldValue;
public Command ConfirmPopUpCommand { get; }
public string FieldValue
{
set
{
_fieldValue = value;
}
get { return _fieldValue; }
}
public MyPopUpPage(MyModel myModel)
{
InitializeComponent();
_fieldValue = myModel.FieldValue;
ConfirmPopUpCommand = new Command(async (key) => {
myModel.FieldValue = FieldValue;
await PopupNavigation.Instance.PopAsync(true);
});
this.BindingContext = this;
}
}
}
Here is ResultPageViewModel.cs new code.
public class ResultPageViewModel
{
private ObservableCollection<MyModel> data;
private INavigation navigation;
public ObservableCollection<MyModel> Results { get; set; }
public ICommand EditTextCommand { protected set; get; }
public ResultPageViewModel(ObservableCollection<MyModel> myModels, INavigation navigation)
{
this.navigation = navigation;
Results = new ObservableCollection<MyModel>();
foreach (var item in myModels)
{
Results.Add(item);
}
EditTextCommand = new Command<MyModel>(async (key) =>
{
//PromptResult pResult = await UserDialogs.Instance.PromptAsync(new PromptConfig
//{
// InputType = InputType.Default,
// Text = key.FieldValue,
// Title = "change value",
//});
//if (pResult!=null)
//{
// key.FieldValue = pResult.Text;
//}
await navigation.PushPopupAsync(new MyPopUpPage(key));
});
}
}
}
You need add Navigation attribute in DisplayResult page. this.BindingContext = new ResultPageViewModel(data, Navigation);

Xamarin Forms : How to Handle Listview Image Click Event in ViewModel?

All,
I am binding my Listview to a collection from a Viewmodel. CellView of the ListView includes an image. I would like to invoke a command in my viewmodel when I click the image in the list item.I am trying to avoid event handling in my model. Any idea ?
thanks !
Given below is the xaml and view model.
ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:jList="clr-namespace:JList;assembly=JList"
x:Class="JList.Pages.ItemDetailPage"
Title="Sub Items"
BindingContext="{Binding Source={StaticResource Locator}, Path=ItemDetailViewModel}"
>
<ContentPage.ToolbarItems >
<ToolbarItem Text="Add" Order="Primary" Priority="1" Command="{Binding AddItemCommand}"></ToolbarItem>
<ToolbarItem Text="Edit" Order="Primary" Priority="2" Command="{Binding EditItemCommand}"></ToolbarItem>
</ContentPage.ToolbarItems>
<StackLayout>
<SearchBar Placeholder="Search..." VerticalOptions="Fill" SearchCommand="{Binding SearchCommand}" Text="{Binding SearchString}" ></SearchBar>
<ListView RowHeight="200" ItemsSource="{Binding SubItemsCollection}" BackgroundColor="Gainsboro" SelectedItem="{Binding SubItemSelected, Mode=TwoWay}" x:Name="List" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell >
<StackLayout>
<Grid VerticalOptions="Fill" BackgroundColor="White" Padding="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Source="{Binding ImagePath}" Aspect="AspectFit">
<Label Grid.Row="0" Grid.Column="0" Text="{Binding Name}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" TextColor="Chocolate" Font="Bold,20" />
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
<ContentPage.Behaviors>
<jList:CustomBehavior />
</ContentPage.Behaviors>
View Model
namespace JList.Core.ViewModels
{
public class ItemDetailViewModel : ViewModelBase, IViewModel
{
private IItemService _itemService;
private ICommandFactory _cmdFactory;
private INavigationService _navService;
private ItemListViewModel _parent;
private IAppInstanceData _appData;
public ItemDetailViewModel(IItemService itemService, ICommandFactory cmdFactory, INavigationService navService, IAppInstanceData appData, ItemListViewModel parent)
{
_itemService = itemService;
_cmdFactory = cmdFactory;
_navService = navService;
_parent = parent;
ParentItemSelected = _parent.ItemSelected.Id;
_appData = appData;
// FetchSubItemsAsync();
}
public int ParentItemSelected { get; set; }
private string _searchString;
public String SearchString
{
get { return _searchString; }
set
{
if (_searchString != value)
{
_searchString = value;
OnPropertyChanged();
}
}
}
private ObservableCollection<SubItem> _subItemsCollection;
public ObservableCollection<SubItem> SubItemsCollection
{
get { return _subItemsCollection; }
set
{
if (_subItemsCollection != null)
{
if (!_subItemsCollection.SequenceEqual(value))
{
_subItemsCollection = value;
OnPropertyChanged();
}
}
else
{
_subItemsCollection = value;
OnPropertyChanged();
}
}
}
private async void FetchSubItemsAsync()
{
ParentItemSelected = _parent.ItemSelected.Id;
var items = await _itemService.GetAllSubItemsAsync(_parent.ItemSelected.Id);
var coll = new ObservableCollection<SubItem>();
foreach (var it in items)
{
coll.Add(it);
}
SubItemsCollection = coll;
}
public void RefreshAsync()
{
FetchSubItemsAsync();
}
private SubItem _itemSelected;
public SubItem SubItemSelected
{
get => _itemSelected;
set
{
_itemSelected = value;
// _navService.PushView(typeof(EditSubItemViewModel).ToString());
}
}
#region FetchCommand
private ICommand _fetchItemsCommand;
public ICommand FetchItemsCommand
{
get
{
if (_fetchItemsCommand == null)
_fetchItemsCommand = _cmdFactory.CreateCommand(FetchSubItemsAsync, () => true);
return _fetchItemsCommand;
}
}
#endregion
#region AddItemCommand
private ICommand _addItemCommand;
public ICommand AddItemCommand
{
get
{
if (_addItemCommand == null)
_addItemCommand = _cmdFactory.CreateCommand(AddItem, () => true);
return _addItemCommand;
}
}
public void AddItem()
{
_appData.IsEditSubItem = false;
_navService.PushView(typeof(SubItemViewModel).ToString());
}
#endregion
#region EditItemCommand
private ICommand _editItemCommand;
public ICommand EditItemCommand
{
get
{
if (_editItemCommand == null)
_editItemCommand = _cmdFactory.CreateCommand(EditItem, () => true);
return _editItemCommand;
}
}
public void EditItem()
{
_appData.IsEditSubItem = true;
_navService.PushView(typeof(SubItemViewModel).ToString());
}
#endregion
#region SearchCommand
private ICommand _searchCommand;
public ICommand SearchCommand
{
get
{
if (_searchCommand == null)
_searchCommand = _cmdFactory.CreateCommand(SearchItemAsync, () => true);
return _searchCommand;
}
}
private async void SearchItemAsync()
{
var items = await _itemService.GetAllSubItemsAsync(_parent.ItemSelected.Id);
var sstring = SearchString.ToLower();
items = items.Where(i => i.Name.ToLower().Contains(sstring));
var coll = new ObservableCollection<SubItem>();
foreach (var it in items)
{
coll.Add(it);
}
SubItemsCollection = coll;
}
#endregion
}
}
You can add TapGestureRecognizer to the image and bind the command in your ViewModel. Also, you are binding the command inside ViewCell, so you need to set the source of BindingContext.
<ContentPage x:Name="ABCPage">
...
<Image Source="abc">
<Image.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Path=BindingContext.ImageCommand, Source={x:Reference Name=ABCPage}}"
CommandParameter="{Binding .}" />
</Image.GestureRecognizers>
</Image>
...
</ContentPage>

program blows up on navigate using observable collections WP7

In maine, on my button click event handler I do:
private void addIconButton_Click(object sender, EventArgs e)
{
if (test)
{
MessageBox.Show("enters addIcon Main");
Note note = new Note();
note.Modified = DateTimeOffset.Now;
if (note != null)
{
Settings.NotesList.Add(note); //this causes the issue.
//Settings.NotesList[0] = note;
}
Settings.CurrentNoteIndex = 0;
test = false;
MessageBox.Show("right before navigate");
this.NavigationService.Navigate(new Uri("/DetailsPage.XAML", UriKind.Relative));
MessageBox.Show("after navigate");
}
//DetailsPage mynewPage = new DetailsPage();
//this.Content = mynewPage;
}
and on my on navigatedTo I do:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
MessageBox.Show("enters onNav Main");
DataContext = null;
DataContext = Settings.NotesList;
Settings.CurrentNoteIndex = -1;
Listbox.SelectedIndex = -1;
if (Settings.NotesList != null)
{
if (Settings.NotesList.Count == 0)
{
Notes.Text = "No Notes";
}
else
{
Notes.Text = "";
}
}
}
Inside my front end code I do:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox x:Name="Listbox" SelectionChanged="listbox_SelectionChanged" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Width="800" MinHeight="60">
<StackPanel>
<TextBlock x:Name="Title" VerticalAlignment="Center" FontSize="{Binding TextSize}" Text="{Binding Name}"/>
<TextBlock x:Name="Date" VerticalAlignment="Center" FontSize="{Binding TextSize}" Text="{Binding Modified,
Mode=TwoWay, Converter={StaticResource dateConverter}}"/>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
I have an observable collections in a static class where in the constructor I set it to:
public static class Settings
{
public static ObservableCollection<Note> NotesList;
static IsolatedStorageSettings settings;
private static int currentNoteIndex;
static Settings()
{
NotesList = new ObservableCollection<Note>();
settings = IsolatedStorageSettings.ApplicationSettings;
MessageBox.Show("enters constructor settings");
}
and then inside the Notes class it just looks like this:
public class Note
{
public DateTimeOffset Modified { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int TextSize { get; set; }
}
when I click the app button and it calls the invent handler right after the navigationService is called I get:
// Code to execute on Unhandled Exceptions
private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
if (System.Diagnostics.Debugger.IsAttached)
{
// An unhandled exception has occurred; break into the debugger
System.Diagnostics.Debugger.Break();
}
}
this only happens when Settings.NotesList.Add(note); is added in the addIconButton_click method..
Any suggestions???
I fixed it by setting default values to the instance variables in the Notes class inside the constructor...

Resources