Xamarin Forms : How to Handle Listview Image Click Event in ViewModel? - xamarin.forms

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>

Related

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:

Passing Data from CollectionView to details page Shell Navigation

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

A Single Label can display 2 Data fields alternately select by user

I'm trying to use a single Label to display one of the two data fields alternately in Xamarin Forms. Only Label 1 Display the binding field and second Label which I am trying to use a variable "DisplayField" is not displaying either 'Contact_Address' or 'Contact_eMail'
Model class
public class Contacts
{
[PrimaryKey][Autoincrement]
public int Contact_ID { get; set; }
public string Contact_Name { get; set; }
public string Contact_Address { get; set; }
public string Contact_eMail { get; set; }
public string DisplayField { get; set; }
}
XAML page
<StackLayout>
<Button Text="Display Address" FontSize="Large" HorizontalOptions="Center" VerticalOptions="Fill" Clicked="Display_Address" />
<Button Text="Display Email" FontSize="Large" HorizontalOptions="Center" VerticalOptions="Fill" Clicked="Display_eMail" />
<Entry HorizontalOptions="FillAndExpand" Text="{Binding DisplayField}" />
<ListView x:Name="listView" HasUnevenRows="True" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell >
<StackLayout Orientation="Vertical" VerticalOptions="CenterAndExpand" >
<Frame >
<StackLayout Orientation="Vertical" VerticalOptions="Center">
<Label Text="{Binding Contact_Name}" FontSize="Medium" LineBreakMode="WordWrap" />
<Label Text="{Binding DisplayField}" LineBreakMode="WordWrap" />
</StackLayout>
</Frame>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Code behind
public partial class FieldSwap : ContentPage
{
readonly FieldViewModel _fieldViewModel;
readonly SQLiteAsyncConnection _connection = DependencyService.Get<ISQLite>().GetConnection();
public ObservableCollection<Contacts> CList { get; set; }
public static string DisplayField { get; private set; }
public static int caseSwitch { get; private set; }
public FieldSwap()
{
InitializeComponent();
_fieldViewModel = new FieldViewModel();
_fieldViewModel.Field = "Contact_Address";
this.BindingContext = _fieldViewModel;
}
public static void SelectField()
{
switch (caseSwitch)
{
case 1:
DisplayField = "Contact_Address";
break;
case 2:
DisplayField = "Contact_eMail";
break;
default:
DisplayField = ("Contact_Address");
break;
}
}
private void Display_Address(object sender, EventArgs e)
{
caseSwitch = 1;
SelectField();
ReadData();
}
private void Display_eMail(object sender, EventArgs e)
{
caseSwitch = 2;
SelectField();
ReadData();
}
public void ReadData()
{
var list = _connection.Table<Contacts>().ToListAsync().Result;
CList = new ObservableCollection<Contacts>(list);
listView.ItemsSource = CList;
}
}
View model class
public class FieldViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
String _field;
public string Field
{
set
{
if (!value.Equals(_field, StringComparison.Ordinal))
{
_field = value;
OnPropertyChanged("DisplayField");
}
}
get
{
return _field;
}
}
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(propertyName));
}
}
Screen Shot
Screen Shot 2
If you want to display different value in ListView by user selected, I suggest you can use Picker to choose, I do one sample that you can take a look.
<ContentPage.Content>
<StackLayout>
<Picker x:Name="choose" SelectedIndexChanged="choose_SelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Contact_Address</x:String>
<x:String>Contact_eMail</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
<ListView
x:Name="listview1"
HasUnevenRows="True"
ItemsSource="{Binding items}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" VerticalOptions="Center">
<Label
FontSize="Medium"
LineBreakMode="WordWrap"
Text="{Binding Contact_Name}" />
<Label
IsVisible="{Binding Source={x:Reference root}, Path=BindingContext.selectedm}"
LineBreakMode="WordWrap"
Text="{Binding Contact_eMail}" />
<Label
IsVisible="{Binding Source={x:Reference root}, Path=BindingContext.selecteda}"
LineBreakMode="WordWrap"
Text="{Binding Contact_Address}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
public partial class Page31 : ContentPage, INotifyPropertyChanged
{
public ObservableCollection<Contacts> items { get; set; }
private Boolean _selecteda;
public Boolean selecteda
{
get { return _selecteda; }
set
{
_selecteda = value;
RaisePropertyChanged("selecteda");
}
}
private Boolean _selectedm;
public Boolean selectedm
{
get { return _selectedm; }
set
{
_selectedm = value;
RaisePropertyChanged("selectedm");
}
}
public Page31()
{
InitializeComponent();
items = new ObservableCollection<Contacts>();
for(int i=0;i<20;i++)
{
Contacts contact = new Contacts()
{
Contact_ID = i, Contact_Name = "cherry " + i, Contact_Address = "the street " + i, Contact_eMail = "cherry#outlook.com "+i
};
items.Add(contact);
}
this.BindingContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private void choose_SelectedIndexChanged(object sender, EventArgs e)
{
var picker = (Picker)sender;
int selectedIndex = picker.SelectedIndex;
if (selectedIndex ==0)
{
selecteda = true;
selectedm = false;
}
else
{
selectedm = true;
selecteda = false;
}
}
}

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 - 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/

Resources