I am trying to create a ListView that supports the pull to refresh feature of ListView.
My XAML is:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:Local="clr-namespace:FireLearn.ViewModels"
x:Class="FireLearn.MainPage"
Title="Categories">
<ContentPage.BindingContext>
<Local:CategoryViewModel/>
</ContentPage.BindingContext>
<ListView
ItemsSource="{Binding Categories}"
ItemSelected="ListView_ItemSelected"
HasUnevenRows="True"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding ListRefreshing}"
RefreshCommand="{Binding RefreshCommand}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<HorizontalStackLayout
Padding="8">
<Image Source="cafs_bubbles.png"
HeightRequest="64"/>
<VerticalStackLayout
Padding="8">
<Label Text="{Binding FormattedName}"
SemanticProperties.HeadingLevel="Level1"
FontSize="Title"
HorizontalOptions="Start"/>
<Label Text="{Binding ItemCount}"
FontSize="Subtitle"/>
<Label Text="{Binding Description}"
HorizontalOptions="Center"
LineBreakMode="WordWrap"
FontSize="Caption"
VerticalOptions="CenterAndExpand"/>
</VerticalStackLayout>
</HorizontalStackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
and the code behind is:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using FireLearn.Models;
namespace FireLearn.ViewModels
{
class CategoryViewModel : ObservableObject
{
public ObservableCollection<CategoryModel> categories = new ObservableCollection<CategoryModel>();
public ObservableCollection<CategoryModel> Categories
{
get => categories;
set => SetProperty(ref categories, value);
}
public bool ListRefreshing = false;
public ICommand RefreshCommand;
public CategoryViewModel()
{
loadFromSource();
RefreshCommand = new Command(async () =>
{
if (!ListRefreshing)
{
ListRefreshing = true;
try
{
await loadFromSource();
}
finally
{
ListRefreshing = false;
}
}
});
}
public async Task loadFromSource()
{
HttpClient httpClient = new()
{
Timeout = new TimeSpan(0,0,10)
};
Uri uri = new Uri("https://somewodpresssite/wp-json/wp/v2/categories");
HttpResponseMessage msg = await httpClient.GetAsync(uri);
if (msg.IsSuccessStatusCode)
{
var result = CategoryModel.FromJson(await msg.Content.ReadAsStringAsync());
Categories = new ObservableCollection<CategoryModel>(result);
}
Debug.WriteLine("List Refreshed");
}
}
}
The binding of the list works great and items show as expected (based on loadFromSource() being called in the constructor but the ICommand doesnt get called when I pull to refresh and I get the following error around the IsRefreshing binding:
[0:] Microsoft.Maui.Controls.Xaml.Diagnostics.BindingDiagnostics:
Warning: 'ListRefreshing' property not found on
'FireLearn.ViewModels.CategoryViewModel', target property:
'Microsoft.Maui.Controls.ListView.IsRefreshing'
I have tried on Android and iOs with the same result and am at a loss as to what I've missed here.
You can only bind to public properties
public bool ListRefreshing { get; set; }
Related
I am trying to bind the ItemSelected of a ListView to a View Model, but am experiencing some issues (due to my own misunderstands around how it all works).
I have view:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:Local="clr-namespace:FireLearn.ViewModels"
x:Class="FireLearn.MainPage"
Title="Categories">
<ContentPage.BindingContext>
<Local:CategoryViewModel/>
</ContentPage.BindingContext>
<NavigationPage.TitleView>
<Label Text="Home"/>
</NavigationPage.TitleView>
<ListView
ItemsSource="{Binding Categories}"
HasUnevenRows="True"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding ListRefreshing, Mode=OneWay}"
RefreshCommand="{Binding RefreshCommand}"
ItemSelected="{Binding OnItemTappedChanged}"
SelectionMode="Single"
SelectedItem="{Binding SelectedCategory}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<HorizontalStackLayout
Padding="8"
VerticalOptions="Fill"
HorizontalOptions="Fill">
<Image Source="cafs_bubbles.png"
HeightRequest="64"
MaximumWidthRequest="64"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"/>
<VerticalStackLayout
Padding="8"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<Label Text="{Binding FormattedName}"
SemanticProperties.HeadingLevel="Level1"
FontSize="Title"
HorizontalOptions="Start"/>
<Label Text="{Binding ItemCount}"
FontSize="Subtitle"/>
<Label Text="{Binding Description}"
HorizontalOptions="Center"
LineBreakMode="WordWrap"
FontSize="Caption"
VerticalOptions="CenterAndExpand"
MaxLines="0"/>
</VerticalStackLayout>
</HorizontalStackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
This is linked to a view model:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FireLearn.Models;
namespace FireLearn.ViewModels
{
public partial class CategoryViewModel : ObservableObject
{
public ObservableCollection<CategoryModel> categories = new ObservableCollection<CategoryModel>();
public ObservableCollection<CategoryModel> Categories
{
get => categories;
set => SetProperty(ref categories, value);
}
public bool listRefreshing = false;
public bool ListRefreshing
{
get => listRefreshing;
set => SetProperty(ref listRefreshing, value);
}
public CategoryModel selectedCategory = new CategoryModel();
public CategoryModel SelectedCategory
{
get => selectedCategory;
set
{
SetProperty(ref selectedCategory, value);
// Tap(value);
}
}
public RelayCommand RefreshCommand { get; set; }
//public RelayCommand TapCellCommand { get; set; }
public CategoryViewModel()
{
loadFromSource();
RefreshCommand = new RelayCommand(async () =>
{
Debug.WriteLine($"STARTED::{ListRefreshing}");
if (!ListRefreshing)
{
ListRefreshing = true;
try
{
await loadFromSource();
}
finally
{
ListRefreshing = false;
Debug.WriteLine($"DONE::{ListRefreshing}");
}
}
});
}
public async Task loadFromSource()
{
HttpClient httpClient = new()
{
Timeout = new TimeSpan(0, 0, 10)
};
Uri uri = new Uri("https://somewebsite.co.uk/wp-json/wp/v2/categories");
HttpResponseMessage msg = await httpClient.GetAsync(uri);
if (msg.IsSuccessStatusCode)
{
var result = CategoryModel.FromJson(await msg.Content.ReadAsStringAsync());
Categories = new ObservableCollection<CategoryModel>(result);
}
Debug.WriteLine("List Refreshed");
}
public void OnItemTappedChanged(System.Object sender, Microsoft.Maui.Controls.SelectedItemChangedEventArgs e)
{
var x = new ShellNavigationState();
Shell.Current.GoToAsync(nameof(NewPage1),
new Dictionary<string, object>
{
{
nameof(NewPage1),
SelectedCategory
}
});
}
}
}
I get compiler error "No property, BindableProperty, or event found for "ItemSelected", or mismatching type between value and property" and am really unsure of how to resolve. If I let XAML create a new event for me, it adds it in MainPage.Xaml.Cs rather than the VM
ItemSelected expects an event handler which usually only exists in the View's code behind. Since the ViewModel shouldn't know anything about the View, it's better not to mix concepts. You have a couple of options to get around this without breaking the MVVM pattern.
Option 1: Use Event Handler and invoke method of ViewModel
First, set up the code behind with the ViewModel by passing it in via the constructor and also add the event handler, e.g.:
public partial class MainPage : ContentPage
{
private CategoryViewModel _viewModel;
public MainPage(CategoryViewModel viewModel)
{
_viewModel = viewModel;
}
public void OnItemSelectedChanged(object sender, SelectedItemChangedEventArgs e)
{
//call a method from the ViewModel, e.g.
_viewModel.DoSomething(e.SelectedItem);
}
//...
}
Then use the event handler from within the XAML:
<ListView
ItemsSource="{Binding Categories}"
HasUnevenRows="True"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding ListRefreshing, Mode=OneWay}"
RefreshCommand="{Binding RefreshCommand}"
ItemSelected="OnItemSelectedChanged"
SelectionMode="Single"
SelectedItem="{Binding SelectedCategory}">
<!-- skipping irrelevant stuff -->
</ListView>
Mind that this does not use bindings.
In your CategoryViewModel you could then define a method that takes in the selected item as an argument:
public partial class CategoryViewModel : ObservableObject
{
//...
public void DoSomething(object item)
{
//do something with the item, e.g. cast it to Category
}
}
Option 2: Use EventToCommandBehavior
Instead of handling the invocation of a ViewModel method from your code behind, you could also use the EventToCommandBehavior from the MAUI Community Toolkit:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:Local="clr-namespace:FireLearn.ViewModels"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="FireLearn.MainPage"
Title="Categories">
<ContentPage.Resources>
<ResourceDictionary>
<toolkit:SelectedItemEventArgsConverter x:Key="SelectedItemEventArgsConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<ListView
ItemsSource="{Binding Categories}"
HasUnevenRows="True"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding ListRefreshing, Mode=OneWay}"
RefreshCommand="{Binding RefreshCommand}"
SelectionMode="Single"
SelectedItem="{Binding SelectedCategory}">
<ListView.Behaviors>
<toolkit:EventToCommandBehavior
EventName="ItemSelected"
Command="{Binding ItemSelectedCommand}"
EventArgsConverter="{StaticResource SelectedItemEventArgsConverter}" />
</ListView.Behaviors>
<!-- skipping irrelevant stuff -->
</ListView>
</ContentPage>
Then, in your ViewModel, you can define the ItemSelectedCommand:
public partial class CategoryViewModel : ObservableObject
{
[RelayCommand]
private void ItemSelected(object item)
{
//do something with the item, e.g. cast it to Category
}
// ...
}
This is the preferred way to do it. Option 1 is just another possiblity, but the EventToCommandBehavior is the better choice.
Note that this is an example using MVVM Source Generators (since you're already using the MVVM Community Toolkit). The full Command would normally be implemented like this:
public partial class CategoryViewModel : ObservableObject
{
private IRelayCommand<object> _itemSelectedCommand;
public IRelayCommand<object> ItemSelectedCommand => _itemSelectedCommand ?? (_itemSelectedCommand = new RelayCommand<object>(ItemSelected));
private void ItemSelected(object item)
{
//do something with the item, e.g. cast it to Category
}
// ...
}
I am running through a Pluralsight Xamarin.Forms tutorial where in the video, tapping the StackLayout in iOS opens a detail page for the specific item by firing an ItemTapped command.
For me, the ItemTapped command is not firing for iOS.
Any suggestions on what I am missing ?
Below is the ItemsPage.xaml, ItemsPage.xaml.cs, and ItemsViewModel.cs.
Please let me know if more information is needed, or any feedback :)
ItemsPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="App1.Views.ItemsPage"
Title="{Binding Title}"
xmlns:local="clr-namespace:App1.ViewModels"
xmlns:model="clr-namespace:App1.Models"
x:Name="BrowseItemsPage">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Command="{Binding AddItemCommand}" />
</ContentPage.ToolbarItems>
<!--
x:DataType enables compiled bindings for better performance and compile time validation of binding expressions.
https://learn.microsoft.com/xamarin/xamarin-forms/app-fundamentals/data-binding/compiled-bindings
-->
<RefreshView x:DataType="local:ItemsViewModel" Command="{Binding LoadItemsCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
<CollectionView x:Name="ItemsListView"
ItemsSource="{Binding Items}"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10" x:DataType="model:Item">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer
NumberOfTapsRequired="1"
Command="{Binding Source={RelativeSource AncestorType={x:Type local:ItemsViewModel}}, Path=ItemTapped}"
CommandParameter="{Binding .}">
</TapGestureRecognizer>
</StackLayout.GestureRecognizers>
<Label Text="{Binding Text}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}"
FontSize="16"
InputTransparent="True"/>
<Label Text="{Binding Description}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemDetailTextStyle}"
FontSize="13"
InputTransparent="True"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
</ContentPage>
ItemsPage.xaml.cs
using App1.Models;
using App1.ViewModels;
using App1.Views;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace App1.Views
{
public partial class ItemsPage : ContentPage
{
ItemsViewModel _viewModel;
public ItemsPage()
{
InitializeComponent();
BindingContext = _viewModel = new ItemsViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
_viewModel.OnAppearing();
}
}
}
ItemsViewModel.cs
using App1.Models;
using App1.Views;
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace App1.ViewModels
{
public class ItemsViewModel : BaseViewModel
{
private Item _selectedItem;
public ObservableCollection<Item> Items { get; }
public Command LoadItemsCommand { get; }
public Command AddItemCommand { get; }
public Command<Item> ItemTapped { get; }
public ItemsViewModel()
{
Title = "Browse";
Items = new ObservableCollection<Item>();
LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());
ItemTapped = new Command<Item>(OnItemSelected);
AddItemCommand = new Command(OnAddItem);
}
async Task ExecuteLoadItemsCommand()
{
IsBusy = true;
try
{
Items.Clear();
var items = await DataStore.GetItemsAsync(true);
foreach (var item in items)
{
Items.Add(item);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
public void OnAppearing()
{
IsBusy = true;
SelectedItem = null;
}
public Item SelectedItem
{
get => _selectedItem;
set
{
SetProperty(ref _selectedItem, value);
OnItemSelected(value);
}
}
private async void OnAddItem(object obj)
{
await Shell.Current.GoToAsync(nameof(NewItemPage));
}
async void OnItemSelected(Item item)
{
if (item == null)
return;
// This will push the ItemDetailPage onto the navigation stack
await Shell.Current.GoToAsync($"{nameof(ItemDetailPage)}?{nameof(ItemDetailViewModel.ItemId)}={item.Id}");
}
}
}
UPDATE - Issue #1 is Solved, Issue#2 is still unsolved
You can view a very crude demonstration video of my issue at https://www.youtube.com/watch?v=5_6KJ0QJouM
I am building have a Xamarin.Forms app with an SQLite database using the MVVM design pattern and C#
When try to Save a record to the database from a View the update/save does not appear to be saving to the SQLite database or reflect in other Views.
I know the database Save method does work as I have created some dummy data when the application first loads (in App.xaml.cs) using the DeveloperData.cs file.
I have two issues.
(SOLVED) Issue 1 - Data not Saving to Database
when I call the Save command from the MerchandiserEditPage.xaml, which uses the MerchandiserEditPageViewModel.cs ViewModel, the record does not appear to save.
Issue 2 - Changes Reflecting in other Views
Once the updated data is saved to the database, how can I reflect that change in other views? After I Save a record from the MerchandiserEditPage that View is "Popped" off the stack and the user is returned to the MerchandiserProfileView. I want the updated data to be reflected in all other views on the stack. But this doesn't appear to be happening? (I tested this using hardcoded data and the same issue occurred, so problem is not directly related to issue 1)
There are many files in my project, that can be viewed/downloaded from my GitHub repository but I will concentrate on the following in this question.
MerchandiserEditPage.xaml (View)
MerchandiserProfilePage.xaml (View)
MerchandiserDatabase.cs (Database Functions)x
MerchandiserEditPageViewModel.cs x
View my GitHub repository for the full project.
MerchandiserDatabase.cs (Database Functions)
using SQLite;
namespace MobileApp.Database
{
public class MerchandiserDatabase
{
private static SQLiteConnection database = DependencyService.Get<IDatabaseConnection>().DbConnection();
private readonly static object collisionLock = new object();
public MerchandiserDatabase()
{
database.CreateTable<Models.Merchandiser>();
}
public static void SaveMerchandiser(Models.Merchandiser merchandiser)
{
lock (collisionLock)
{
if (merchandiser.Id != 0)
{
database.Update(merchandiser);
}
else
{
database.Insert(merchandiser);
}
}
}
}
}
MerchandiserEditPageViewModel.cs (ViewModel) UPDATED
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
namespace MobileApp.ViewModels
{
public class MerchandiserEditPageViewModel : BaseViewModel
{
public string PageTitle { get; } = "Edit Merchandiser Profile";
public Command SaveCommand { get; set; }
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged();
}
}
private string phone;
public string Phone
{
get { return phone; }
set
{
phone = value;
OnPropertyChanged();
}
}
private string email;
public string Email
{
get { return email; }
set
{
email = value;
OnPropertyChanged();
}
}
public MerchandiserEditPageViewModel(Models.Merchandiser selectedMerchandiser)
{
Name = selectedMerchandiser.Name;
Phone = selectedMerchandiser.Phone;
Email = selectedMerchandiser.Email;
SaveCommand = new Command( async ()=> {
selectedMerchandiser.Name = this.Name;
selectedMerchandiser.Phone = this.Phone;
selectedMerchandiser.Email = this.Email;
Database.MerchandiserDatabase.SaveMerchandiser(selectedMerchandiser);
await Application.Current.MainPage.Navigation.PopModalAsync();
});
}
}
}
MerchandiserEditPage.xaml (View)
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MobileApp.Views.MerchandiserEditPage">
<ContentPage.Content>
<StackLayout>
<!--Page Heading-->
<StackLayout Spacing="0">
<Label Text="{Binding PageTitle}"
Style="{StaticResource PageTitle}"/>
<BoxView HeightRequest="1" Color="LightGray" />
</StackLayout>
<!-- Merchandiser Profile -->
<StackLayout Margin="10">
<Label Text="Name"/>
<Entry Text="{Binding Name}"/>
<Label Text="Phone"/>
<Entry Text="{Binding Phone}"/>
<Label Text="Email"/>
<Entry Text="{Binding Email}"/>
<StackLayout Orientation="Horizontal"
HorizontalOptions="Center">
<Button Text="Cancel"
Clicked="CancelButton_Clicked"/>
<Button Text="Save"
Command="{Binding SaveCommand}"/>
</StackLayout>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
MerchandiserEditPage.xaml.cs (View - Code Behind)
public partial class MerchandiserEditPage : ContentPage
{
Models.Merchandiser SelectedMerchandiser { get; set; }
public MerchandiserEditPage (Models.Merchandiser selectedMerchandiser)
{
InitializeComponent ();
SelectedMerchandiser = selectedMerchandiser;
this.BindingContext = new ViewModels.MerchandiserEditPageViewModel(selectedMerchandiser);
}
async private void CancelButton_Clicked(object sender, EventArgs e)
{
await Navigation.PopModalAsync();
}
}
MerchandiserProfilePage.xaml (View - XAML)
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MobileApp.Views.MerchandiserProfilePage"
NavigationPage.HasNavigationBar="False">
<ContentPage.Content>
<StackLayout>
<!--Page Heading-->
<StackLayout Spacing="0">
<Label Text="{Binding PageTitle}"
Style="{StaticResource PageTitle}"/>
<BoxView HeightRequest="1" Color="LightGray" />
</StackLayout>
<!-- Merchandiser Profile -->
<StackLayout Margin="10">
<Label Text="Name"/>
<Entry Text="{Binding Name}"
IsEnabled="False"/>
<Label Text="Phone"/>
<Entry Text="{Binding Phone}"
IsEnabled="False"/>
<Label Text="Email"/>
<Entry Text="{Binding Email}"
IsEnabled="False"/>
<StackLayout Orientation="Horizontal"
HorizontalOptions="Center">
<Button Text="Back"
Clicked="BackButton_Clicked"/>
<Button Text="Edit"
Clicked="EditButton_Clicked"/>
</StackLayout>
<Button Text="Delete"
Command="{Binding DeleteCommand}"/>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
MerchandiserProfilePage.xaml.cs - (View - Code Behind)
public partial class MerchandiserProfilePage : ContentPage
{
private Models.Merchandiser SelectedMerchandister { get; set; }
public MerchandiserProfilePage (Models.Merchandiser selectedMerchandiser)
{
InitializeComponent ();
SelectedMerchandister = selectedMerchandiser;
this.BindingContext = new ViewModels.MerchandiserProfilePageViewModel(selectedMerchandiser);
}
async private void BackButton_Clicked(object sender, EventArgs e)
{
await Navigation.PopModalAsync();
}
async private void EditButton_Clicked(object sender, EventArgs e)
{
await Navigation.PushModalAsync(new Views.MerchandiserEditPage(SelectedMerchandister));
}
}
MerchandiserProfilePageViewModel.cs (ViewModel)
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
namespace MobileApp.ViewModels
{
public class MerchandiserProfilePageViewModel : BaseViewModel
{
public string PageTitle { get; } = "Merchandiser Profile";
public Command DeleteCommand { get; }
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged();
}
}
private string phone;
public string Phone
{
get { return phone; }
set
{
phone = value;
OnPropertyChanged();
}
}
private string email;
public string Email
{
get { return email; }
set
{
email = value;
OnPropertyChanged();
}
}
public MerchandiserProfilePageViewModel(Models.Merchandiser selectedMerchandiser)
{
Name = selectedMerchandiser.Name;
Phone = selectedMerchandiser.Phone;
Email = selectedMerchandiser.Email;
DeleteCommand = new Command( async()=> {
bool deleteConfirmed = await Application.Current.MainPage.DisplayAlert("Confirm Delete",$"Are you sure you want to delete {selectedMerchandiser.Name} as a Merchandiser?","Yes","No");
if (deleteConfirmed)
{
// TODO: Delete Merchandiser
await Application.Current.MainPage.Navigation.PopModalAsync();
}
});
}
}
}
you have a hardcoded set of data in your VM instead of loading it from the db
public MerchandisersPageViewModel()
{
//Merchandisers = new ObservableCollection<Models.Merchandiser>(Database.MerchandiserDatabase.GetMerchandisers());
Merchandisers = new ObservableCollection<Models.Merchandiser>()
{
new Models.Merchandiser { Id=1, Name="Barney Rubble", Phone="021 321 654", Email="barney#rubble.com"},
new Models.Merchandiser { Id=2, Name="Frank Grimes", Phone="022 456 789", Email="grimey#homersfriend.com"},
new Models.Merchandiser { Id=3, Name="Perry Platypus", Phone="023 789 456", Email="perry#agentp.com"},
};
}
Update:
in MerchandiserProfilePageViewModel, get rid of the properties for Name, Phone and EMail
then in MerchandiserProfilePage.xaml change the bindings
<Entry Text="{Binding SelectedMerchandiser.Name}" IsEnabled="False"/>
Below is the code I'm using for checkbox in xamarin forms, but here I'm able to select only one item, I wanted to select multiple items from the checkbox. To the checkbox the data is binded from the database. Please help me
Checkforms.xaml.cs
public partial class Checkforms : ContentPage
{
private ObservableCollection<HelperModel> statusRecords;
string[] statusList;
public Checkforms()
{
InitializeComponent();
GetUserRoles();
}
public async void GetUserRoles()
{
HttpClient client = new HttpClient();
var response = await client.GetStringAsync("http://**********/api/Masters/getRoles");
var details = JsonConvert.DeserializeObject<List<HelperModel>>(response);
ListView1.ItemsSource = details;
}
private async void ListView1_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem == null) return;
var statusData = e.SelectedItem as HelperModel;
((ListView)sender).SelectedItem = null;
HttpClient client = new HttpClient();
var response = await client.GetStringAsync("http://********/api/Masters/getRoles");
var details = JsonConvert.DeserializeObject<List<HelperModel>>(response);
ListView1.ItemsSource = details;
var item = details.Where(x => x.name == statusData.name).FirstOrDefault();
if (item != null)
item.IsSelected = !item.IsSelected;
}
}
Checkforms.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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Checkbox_listview.Checkforms"
xmlns:lv="clr-namespace:Xamarin.Forms.MultiSelectListView;assembly=Xamarin.Forms.MultiSelectListView" Padding="0,20,0,0">
<ContentPage.Content>
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<!-- Place new controls here -->
<ListView x:Name="ListView1" ItemSelected="ListView1_ItemSelected" lv:MultiSelect.Enable="true">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout HorizontalOptions="FillAndExpand"
Orientation="Horizontal" Padding="10 ">
<Label Text="{Binding name}" HorizontalOptions="StartAndExpand"/>
<Image Source="select.png" IsVisible="{Binding IsSelected}"
VerticalOptions="Center" HeightRequest="40"
WidthRequest="40"/>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
HelperModel.cs
public class HelperModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isSelected = false;
public string name { get; set; }
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
//OnProperty changed method
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I'm trying to select multiple items from the checkbox after binding it from the database, from here only one item is selected at a time. please help how to select multiple items
Thanks in advance
You can try use CollectionView to replace the listview like following code. CollectionView have SelectionMode, you can set it to Multiple
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<!-- Place new controls here -->
<CollectionView x:Name="ListView1" ItemsSource="{Binding StatusRecords}" SelectionMode="Multiple"
SelectionChanged="ListView1_SelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout HorizontalOptions="FillAndExpand" Orientation="Horizontal" Padding="10 ">
<Label Text="{Binding name}" HorizontalOptions="StartAndExpand"/>
<Image Source="select.png" IsVisible="{Binding IsSelected}" VerticalOptions="Center" HeightRequest="40" WidthRequest="40"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
Here is running GIF.
=========Update===============
Do you want to Multiple pre-selection result?
If so, you should add the Property in your ViewModel. Note: No matter what is your model, please set the type of ObservableCollection to object
ObservableCollection<object> selectedHelperModels;
public ObservableCollection<object> SelectedHelperModels
{
get
{
return selectedHelperModels;
}
set
{
if (selectedHelperModels != value)
{
selectedHelperModels = value;
OnPropertyChanged("SelectedHelperModels");
}
}
}
Then If the IsSelected was selected to true. I will add it to the SelectedHelperModels.
public MyHelperViewModel()
{
StatusRecords = new ObservableCollection<HelperModel>();
StatusRecords.Add(new HelperModel() { IsSelected=false, name="test1" });
StatusRecords.Add(new HelperModel() { IsSelected = true, name = "test2" });
StatusRecords.Add(new HelperModel() { IsSelected = true, name = "test3" });
StatusRecords.Add(new HelperModel() { IsSelected = true, name = "test4" });
StatusRecords.Add(new HelperModel() { IsSelected = false, name = "test5" });
StatusRecords.Add(new HelperModel() { IsSelected = false, name = "test6" });
SelectedHelperModels = new ObservableCollection<object>();
foreach (var item in StatusRecords)
{
if (item.IsSelected)
{
SelectedHelperModels.Add(item);
}
}
}
In the foreground xaml. Add the SelectedItems="{Binding SelectedHelperModels}" in the CollectionView.
<CollectionView x:Name="ListView1" ItemsSource="{Binding StatusRecords}" SelectedItems="{Binding SelectedHelperModels}" SelectionMode="Multiple"
SelectionChanged="ListView1_SelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout HorizontalOptions="FillAndExpand" Orientation="Horizontal" Padding="10 ">
<Label Text="{Binding name}" HorizontalOptions="StartAndExpand"/>
<Image Source="{Binding IsSelected, Converter={StaticResource imageToBool}}" IsVisible="{Binding IsSelected} " VerticalOptions="Center" HeightRequest="40" WidthRequest="40"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
As you comment, you lack of the ListView1_SelectionChanged event. just add it in the layout background code.
public partial class MainPage : ContentPage
{
MyHelperViewModel myHelperViewModel;
public MainPage()
{
InitializeComponent();
myHelperViewModel= new MyHelperViewModel();
this.BindingContext = myHelperViewModel;
}
private void ListView1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}
}
=========Update2============
Do you want to achieve the result like following GIF?
If so, I found the SelectionChanged event cannot achieve it easliy, and it cannot meet the MVVM requirement, So I add a TapGestureRecognizer for StackLayout in the CollectionView.
Here is code.
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<CollectionView x:Name="ListView1"
ItemsSource="{Binding StatusRecords}"
SelectedItems="{Binding SelectedHelperModels}"
SelectionMode="Multiple"
>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout HorizontalOptions="FillAndExpand" Orientation="Horizontal" Padding="10">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BindingContext.ChangeCommand, Source={x:Reference Name=ListView1}}"
CommandParameter="{Binding .}"
/>
</StackLayout.GestureRecognizers>
<Label Text="{Binding name}" HorizontalOptions="StartAndExpand"/>
<Image Source="{Binding IsSelected, Converter={StaticResource imageToBool},Mode=TwoWay}" IsVisible="{Binding IsSelected, Mode=TwoWay}" VerticalOptions="Center" HeightRequest="40" WidthRequest="40"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
Here is ViewModel.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Input;
using Xamarin.Forms;
namespace SelectMutiPlyDemo
{
public class MyHelperViewModel: INotifyPropertyChanged
{
public ObservableCollection<HelperModel> StatusRecords { get; set; }
public ICommand ChangeCommand { protected set; get; }
ObservableCollection<object> selectedHelperModels;
public ObservableCollection<object> SelectedHelperModels
{
get
{
return selectedHelperModels;
}
set
{
if (selectedHelperModels != value)
{
selectedHelperModels = value;
OnPropertyChanged("SelectedHelperModels");
}
}
}
public MyHelperViewModel()
{
StatusRecords = new ObservableCollection<HelperModel>();
StatusRecords.Add(new HelperModel() { IsSelected=false, name="test1" });
StatusRecords.Add(new HelperModel() { IsSelected = true, name = "test2" });
StatusRecords.Add(new HelperModel() { IsSelected = true, name = "test3" });
StatusRecords.Add(new HelperModel() { IsSelected = true, name = "test4" });
StatusRecords.Add(new HelperModel() { IsSelected = false, name = "test5" });
StatusRecords.Add(new HelperModel() { IsSelected = false, name = "test6" });
SelectedHelperModels = new ObservableCollection<object>();
foreach (var item in StatusRecords)
{
if (item.IsSelected)
{
SelectedHelperModels.Add(item);
}
}
ChangeCommand=new Command<HelperModel>((key) =>
{
if (SelectedHelperModels.Contains<object>(key))
{
SelectedHelperModels.Remove(key);
}
else
{
SelectedHelperModels.Add(key);
}
key.IsSelected = !key.IsSelected;
});
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
I want all the records created in ASP.NET Web Application to be shown in my Mobile App Xamarin.Forms. What's happening to my program is that I was able to create records in my Web Application and save it, but I wasn't able to make it appear in my Xamarin.Forms Mobile app. I have created a MainViewModel that will get the records from the Web Application which I have binded to my MainPage.
These are my codes:
MainPageMain.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamarinDemoApp"
x:Class="XamarinDemoApp.MainPageMain"
xmlns:ViewModels="clr-namespace:XamarinDemoApp.ViewModels;assembly=XamarinDemoApp"
BackgroundColor="Teal"
Title=" Title Bar">
<ContentPage.BindingContext>
<ViewModels:MainViewModel/>
</ContentPage.BindingContext>
<StackLayout Orientation="Vertical">
<ListView ItemsSource="{Binding EmployeesList}"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Name}"
FontSize="24"/>
<Label Text="{Binding Department}"
FontSize="24"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Label Text="This is the MainPage"/>
</StackLayout>
MainViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using XamarinDemoApp.Models;
using XamarinDemoApp.Services;
namespace XamarinDemoApp.ViewModels
{
public class MainViewModel : INotifyPropertyChanged
{
private List<Employee> _employeesList;
public List<Employee> EmployeesList
{
get { return _employeesList; }
set
{
_employeesList = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
InitializeDataAsync();
}
private async Task InitializeDataAsync()
{
var employeesServices = new EmployeesServices();
EmployeesList = await employeesServices.GetEmployeesAsync();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Make a separate API controller that you can use to call EmployeeList as a JSON object. That is the preferred way to do this kind of thing. Example:
public class EmployeeApiController : ApiController
{
[HttpGet]
public async Task<List<Employee>> Get()
{
return await employeesServices.GetEmployeesAsync();
}
}