In my xamarin.forms app, I have a listview with checkboxes for the selection of the individual cell. What I am trying to do is multi select the checkboxes inside the listview by providing a "select all" checkbox outside the listview.The Multiselection works fine. For the "select all" checkbox click and individual checkbox click, I am performing some actions like an API Call. The Problem I am facing is Whenever I Click on the "select all" checkbox, the checkbox changed event of individual checkbox gets triggered.I know its natural But is there any way to prevent it like subscribe or unsubscribe the changed event of individual checkbox or something?
Xaml
<Grid >
<Grid.RowDefinitions>
<RowDefinitions Height="Auto"/>
<RowDefinitions Height="Auto"/>
</Grid.RowDefinitions>
<StackLayout Grid.Row="0" Orientation="Horizontal">
<Label Text="Select All" FontSize="Micro" TextColor="LawnGreen" HorizontalOptions="Start" VerticalOptions="Center" >
</Label>
<CheckBox x:Name="MultiselectCheckbox" ScaleX="0.8" ScaleY="0.8" CheckedChanged="MultiSelectCheckBox_CheckedChanged" IsChecked="False" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Color="LawnGreen"></CheckBox>
</StackLayout>
<ListView
x:Name="Listview"
HorizontalOptions="FillAndExpand"
ItemTapped="DistrictList_ItemTapped"
VerticalOptions="FillAndExpand" >
<ListView.ItemTemplate >
<DataTemplate >
<ViewCell >
<ViewCell.View>
<Frame HorizontalOptions="FillAndExpand">
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<Label Text="{Binding name}" FontSize="Micro" HorizontalOptions="StartAndExpand" VerticalOptions="Center" TextColor="Snow" Margin="5,0,0,0">
</Label>
<CheckBox CheckedChanged="Single_CheckedChanged" IsChecked="{Binding Selected}" Color="LightBlue" HorizontalOptions="End" >
</CheckBox>
</StackLayout>
</Frame>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Multiselect Checkbox checked event
private void MultiSelectCheckBox_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
if (!e.Value)
{
foreach (MyData TS in MyObject)
{
TS.Selected = false;
}
}
else{
foreach (MyData TS in MyObject)
{
TS.Selected = true;
}
PerformSomeAction();
}
}
Single selection Checkbox changed event
private void Single_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
if (!e.Value)
{
}
else{
PerformSomeAction();
}
}
Data Model
public class MyData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string name { get; set; }
private bool selected;
public bool Selected
{
get
{
return selected;
}
set
{
if (value != null)
{
selected = value;
NotifyPropertyChanged("Selected");
}
}
}
}
Agree with # Nikhileshwar , you could define some properties to get the different condition .And since you had used MVVM, you would better put all logic handling in your viewmodel .
in xaml
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackLayout Grid.Row="0" Orientation="Horizontal" HeightRequest="40" BackgroundColor="LightPink">
<Label Text="Select All" FontSize="Micro" TextColor="Red" HorizontalOptions="Start" VerticalOptions="Center" >
</Label>
<CheckBox x:Name="MultiselectCheckbox" ScaleX="0.8" ScaleY="0.8" IsChecked="{Binding MultiselectCheck}" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Color="Red"></CheckBox>
</StackLayout>
<ListView Grid.Row="1"
x:Name="Listview"
HorizontalOptions="FillAndExpand"
ItemsSource="{Binding MyItems}"
VerticalOptions="FillAndExpand" >
<ListView.ItemTemplate >
<DataTemplate >
<ViewCell >
<Frame Padding="0" HeightRequest="40" HorizontalOptions="FillAndExpand">
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<Label Text="{Binding name}" FontSize="Micro" HorizontalOptions="StartAndExpand" VerticalOptions="Center" TextColor="Red" Margin="5,0,0,0">
</Label>
<CheckBox IsChecked="{Binding Selected}" Color="Red" HorizontalOptions="End" >
</CheckBox>
</StackLayout>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
in ViewModel
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
bool isMultiselect;
bool isSingleSelect;
public ObservableCollection<MyData> MyItems { get; set; }
private bool multiselectCheck;
public bool MultiselectCheck
{
get
{
return multiselectCheck;
}
set
{
if (multiselectCheck != value)
{
multiselectCheck = value;
if(!isSingleSelect)
{
isMultiselect = true;
foreach (MyData data in MyItems)
{
data.Selected = value;
}
isMultiselect = false;
}
NotifyPropertyChanged("MultiselectCheck");
}
}
}
public MyViewModel()
{
MyItems = new ObservableCollection<MyData>() {
new MyData(){name="Selection1" },
new MyData(){name="Selection2" },
new MyData(){name="Selection3" },
};
foreach(MyData data in MyItems)
{
data.PropertyChanged += Data_PropertyChanged;
}
}
Related
Hy,
I am trying to show a comment input if the item checkbox is checked and hide it else, i have this XAML
<ListView ItemsSource="{Binding TaskItems}" x:Name="TasksItems" HasUnevenRows="True" VerticalScrollBarVisibility="Default" SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout IsVisible="True" Orientation="Vertical">
<Grid BackgroundColor="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<StackLayout Grid.Column="0" Grid.Row="0">
<input:CheckBox Type="Box" IsChecked="{Binding TaskChecked , Mode=TwoWay}"/>
</StackLayout>
<StackLayout Grid.Column="0" Grid.Row="1" IsVisible="{Binding CommentRequired}">
<Entry BackgroundColor="White" PlaceholderColor="Black" HeightRequest="40" TextColor="Black"/>
</StackLayout>
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
and i have this c# code to read the items from database
public class TaskData
{
public bool TaskChecked { get; set; }
public bool CommentRequired { get; set; }
}
public partial class HomePage : ContentPage
{
public HomePage()
{
ObservableCollection<TaskData> TaskItems { get; set; }
ObservableCollection<TaskData> TasksList;
TaskItems = new ObservableCollection<TaskData>();
//Loop the tasks from database result
While...
TaskItems.Add(new TaskData
{
TaskChecked = false,
CommentRequired = false,
});
//End Loop
TasksListView.ItemsSource = TaskItems;
}
}
Now i need to add a "CheckChanged" event to show the comment Entry (IsVisible="True") when the user check the checkbox of the targed listview item
Thanks
First add the event.
<input:CheckBox Type="Box" IsChecked="{Binding TaskChecked , Mode=TwoWay}" CheckedChanged="OnCheckBoxCheckedChanged"/>
Add Name for the second stack
<StackLayout x:Name="StackLayoutEntry" Grid.Column="0" Grid.Row="1" IsVisible="{Binding CommentRequired}">
<Entry BackgroundColor="White" PlaceholderColor="Black" HeightRequest="40" TextColor="Black"/>
</StackLayout>
Then in code Behind use this function to find the entry for the clicked checkbox
void OnCheckBoxCheckedChanged(object sender, CheckedChangedEventArgs e)
{
var Sender = (CheckBox)sender;
var stacklayoutentry = Sender.Parent.FindByName<StackLayout>("StackLayoutEntry");
stacklayoutentry.IsVisible = True;
}
This might also help you also Check
Another approach but you an identifier to your selected item.
Check
I'm working on mobile app that kinda simulates how to build a Desktop Computer by dragging and dropping an image in order. I work with DropGestureRecognizer with AllowDrop set to True for the Drop Zone Image controls and DragGestureRecognizer with CanDrag set to True for the Drag Image Objects.
The idea is the Drag Image Objects will get dragged and dropped to a Drop Zone Image Control and if they dropped the correct Drag Image, the Drop Zone will accept this as the Image.Source and another Drop Zone will be stacked in front of it to work with the next Desktop Computer component. Otherwise, it would get rejected and the Image.Source will be set to empty or null.
However, i'm encountering a weird issue that, on the first incorrect image dragged, the Image.Source gets reset to empty or null by property but on the actual UI, it doesn't. On the succeeding EventHandler execution, it does get reset. I have a TapGestureRecognizer on the Drop Zone to check if there is an Image.Source set or not.
I'm sorry if my explanation is a bit confusing because English is not my native language and i'm having a hard time explaining it. So please refer to below example in GIF and my code:
View (.xaml):
<ContentPage.Content>
<Grid RowDefinitions="Auto,60,*"
ColumnDefinitions="*">
<RelativeLayout Grid.Row="0" Grid.Column="0" HorizontalOptions="Center">
<Image x:Name="imgCaseDropZone"
RelativeLayout.XConstraint="0"
RelativeLayout.YConstraint="0"
HeightRequest="{Binding ScreenWidth}"
WidthRequest="{Binding ScreenWidth}"
PropertyChanged="CaseDropZone_PropertyChanged"
BackgroundColor="LightGray">
<Image.GestureRecognizers>
<DropGestureRecognizer AllowDrop="True" />
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped" />
</Image.GestureRecognizers>
</Image>
</RelativeLayout>
<Label Grid.Row="1" Grid.Column="0"
x:Name="lblDirections"
Text="Directions: Drag the components below in its proper order to the drop zone above."
TextColor="Black"
Padding="10"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
<ScrollView Grid.Row="2"
Orientation="Horizontal"
Margin="5">
<StackLayout Orientation="Horizontal">
<!--#region Case -->
<Grid RowDefinitions="*,Auto"
IsVisible="{Binding CaseIsVisible}">
<Image Grid.Row="0"
x:Name="imgCaseDragObject"
Source="{Binding CaseImgSource}">
<Image.GestureRecognizers>
<DragGestureRecognizer CanDrag="True" />
</Image.GestureRecognizers>
</Image>
<Label Grid.Row="1"
Text="{Binding CaseLabel}"
TextColor="White"
FontSize="Medium"
BackgroundColor="Gray"
HorizontalTextAlignment="Center" />
</Grid>
<!--#endregion-->
<!--#region Case Cover -->
<Grid RowDefinitions="*,Auto"
IsVisible="{Binding CaseCoverIsVisible}">
<Image Grid.Row="0"
Source="{Binding CaseCoverImgSource}">
<Image.GestureRecognizers>
<DragGestureRecognizer CanDrag="True" />
</Image.GestureRecognizers>
</Image>
<Label Grid.Row="1"
Text="{Binding CaseCoverLabel}"
TextColor="White"
FontSize="Medium"
BackgroundColor="Gray"
HorizontalTextAlignment="Center" />
</Grid>
<!--#endregion-->
<!--#region Case Screw -->
<Grid RowDefinitions="*,Auto"
IsVisible="{Binding CaseScrewIsVisible}">
<Image Grid.Row="0"
Source="{Binding CaseScrewImgSource}">
<Image.GestureRecognizers>
<DragGestureRecognizer CanDrag="True" />
</Image.GestureRecognizers>
</Image>
<Label Grid.Row="1"
Text="{Binding CaseScrewLabel}"
TextColor="White"
FontSize="Medium"
BackgroundColor="Gray"
HorizontalTextAlignment="Center" />
</Grid>
<!--#endregion-->
<!--#region Hard Disk Drive -->
<Grid RowDefinitions="*,Auto"
IsVisible="{Binding HardDiskDriveIsVisible}">
<Image Grid.Row="0"
Source="{Binding HardDiskDriveImgSource}">
<Image.GestureRecognizers>
<DragGestureRecognizer CanDrag="True" />
</Image.GestureRecognizers>
</Image>
<Label Grid.Row="1"
Text="{Binding HardDiskDriveLabel}"
TextColor="White"
FontSize="Medium"
BackgroundColor="Gray"
HorizontalTextAlignment="Center" />
</Grid>
<!--#endregion-->
<!--#region Heatsink -->
<Grid RowDefinitions="*,Auto"
IsVisible="{Binding HeatsinkIsVisible}">
<Image Grid.Row="0"
Source="{Binding HeatsinkImgSource}">
<Image.GestureRecognizers>
<DragGestureRecognizer CanDrag="True" />
</Image.GestureRecognizers>
</Image>
<Label Grid.Row="1"
Text="{Binding HeatsinkLabel}"
TextColor="White"
FontSize="Medium"
BackgroundColor="Gray"
HorizontalTextAlignment="Center" />
</Grid>
<!--#endregion-->
<!--#region Memory Module -->
<Grid RowDefinitions="*,Auto"
IsVisible="{Binding MemoryModuleIsVisible}">
<Image Grid.Row="0"
Source="{Binding MemoryModuleImgSource}">
<Image.GestureRecognizers>
<DragGestureRecognizer CanDrag="True" />
</Image.GestureRecognizers>
</Image>
<Label Grid.Row="1"
Text="{Binding MemoryModuleLabel}"
TextColor="White"
FontSize="Medium"
BackgroundColor="Gray"
HorizontalTextAlignment="Center" />
</Grid>
<!--#endregion-->
<!--#region Motherboard -->
<Grid RowDefinitions="*,Auto"
IsVisible="{Binding MotherboardIsVisible}">
<Image Grid.Row="0"
Source="{Binding MotherboardImgSource}">
<Image.GestureRecognizers>
<DragGestureRecognizer CanDrag="True" />
</Image.GestureRecognizers>
</Image>
<Label Grid.Row="1"
Text="{Binding MotherboardLabel}"
TextColor="White"
FontSize="Medium"
BackgroundColor="Gray"
HorizontalTextAlignment="Center" />
</Grid>
<!--#endregion-->
<!--#region Motherboard Screw -->
<Grid RowDefinitions="*,Auto"
IsVisible="{Binding MotherboardScrewIsVisible}">
<Image Grid.Row="0"
Source="{Binding MotherboardScrewImgSource}">
<Image.GestureRecognizers>
<DragGestureRecognizer CanDrag="True" />
</Image.GestureRecognizers>
</Image>
<Label Grid.Row="1"
Text="{Binding MotherboardScrewLabel}"
TextColor="White"
FontSize="Medium"
BackgroundColor="Gray"
HorizontalTextAlignment="Center" />
</Grid>
<!--#endregion-->
<!--#region Power Supply -->
<Grid RowDefinitions="*,Auto"
IsVisible="{Binding PowerSupplyIsVisible}">
<Image Grid.Row="0"
Source="{Binding PowerSupplyImgSource}">
<Image.GestureRecognizers>
<DragGestureRecognizer CanDrag="True" />
</Image.GestureRecognizers>
</Image>
<Label Grid.Row="1"
Text="{Binding PowerSupplyLabel}"
TextColor="White"
FontSize="Medium"
BackgroundColor="Gray"
HorizontalTextAlignment="Center" />
</Grid>
<!--#endregion-->
<!--#region Processor -->
<Grid RowDefinitions="*,Auto"
IsVisible="{Binding ProcessorIsVisible}">
<Image Grid.Row="0"
Source="{Binding ProcessorImgSource}">
<Image.GestureRecognizers>
<DragGestureRecognizer CanDrag="True" />
</Image.GestureRecognizers>
</Image>
<Label Grid.Row="1"
Text="{Binding ProcessorLabel}"
TextColor="White"
FontSize="Medium"
BackgroundColor="Gray"
HorizontalTextAlignment="Center" />
</Grid>
<!--#endregion-->
</StackLayout>
</ScrollView>
</Grid>
</ContentPage.Content>
View Code (.xaml.cs):
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class AssemblyPage : ContentPage
{
string caseSource;
public AssemblyPage()
{
InitializeComponent();
}
int dragCount = 0;
private void CaseDropZone_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Source")
{
caseSource = imgCaseDropZone.Source.ToString().Split(':').Last().Trim();
if (string.IsNullOrEmpty(caseSource))
{
return;
}
if (caseSource != "img_assembly_case.png")
{
imgCaseDropZone.Source = string.Empty;
lblDirections.Text = $"Drag Count: {++dragCount}";
}
}
}
private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
Application.Current.MainPage.DisplayAlert("", $"{imgCaseDropZone.Source}", "OK");
}
}
ViewModel (.cs):
public class AssemblyViewModel : BaseVM
{
public double ScreenWidth => Xamarin.Forms.Application.Current.MainPage.Width;
#region Case
bool caseIsVisible;
public bool CaseIsVisible
{
get => caseIsVisible;
set => SetProperty(ref caseIsVisible, value);
}
public string CaseImgSource { get; }
public string CaseLabel { get; }
#endregion
#region CaseCover
bool caseCoverIsVisible;
public bool CaseCoverIsVisible
{
get => caseCoverIsVisible;
set => SetProperty(ref caseCoverIsVisible, value);
}
public string CaseCoverImgSource { get; }
public string CaseCoverLabel { get; }
#endregion
#region HardDiskDrive
bool hardDiskDriveIsVisible;
public bool HardDiskDriveIsVisible
{
get => hardDiskDriveIsVisible;
set => SetProperty(ref hardDiskDriveIsVisible, value);
}
public string HardDiskDriveImgSource { get; }
public string HardDiskDriveLabel { get; }
#endregion
#region CaseScrew
bool caseScrewIsVisible;
public bool CaseScrewIsVisible
{
get => caseScrewIsVisible;
set => SetProperty(ref caseScrewIsVisible, value);
}
public string CaseScrewImgSource { get; }
public string CaseScrewLabel { get; }
#endregion
#region Heatsink
bool heatsinkIsVisible;
public bool HeatsinkIsVisible
{
get => heatsinkIsVisible;
set => SetProperty(ref heatsinkIsVisible, value);
}
public string HeatsinkImgSource { get; }
public string HeatsinkLabel { get; }
#endregion
#region MemoryModule
bool memoryModuleIsVisible;
public bool MemoryModuleIsVisible
{
get => memoryModuleIsVisible;
set => SetProperty(ref memoryModuleIsVisible, value);
}
public string MemoryModuleImgSource { get; }
public string MemoryModuleLabel { get; }
#endregion
#region Motherboard
bool motherboardIsVisible;
public bool MotherboardIsVisible
{
get => motherboardIsVisible;
set => SetProperty(ref motherboardIsVisible, value);
}
public string MotherboardImgSource { get; }
public string MotherboardLabel { get; }
#endregion
#region MotherboardScrew
bool motherboardScrewIsVisible;
public bool MotherboardScrewIsVisible
{
get => motherboardScrewIsVisible;
set => SetProperty(ref motherboardScrewIsVisible, value);
}
public string MotherboardScrewImgSource { get; }
public string MotherboardScrewLabel { get; }
#endregion
#region PowerSupply
bool powerSupplyIsVisible;
public bool PowerSupplyIsVisible
{
get => powerSupplyIsVisible;
set => SetProperty(ref powerSupplyIsVisible, value);
}
public string PowerSupplyImgSource { get; }
public string PowerSupplyLabel { get; }
#endregion
#region Processor
bool processorIsVisible;
public bool ProcessorIsVisible
{
get => processorIsVisible;
set => SetProperty(ref processorIsVisible, value);
}
public string ProcessorImgSource { get; }
public string ProcessorLabel { get; }
#endregion
public AssemblyViewModel()
{
CaseIsVisible = true;
CaseImgSource = "img_assembly_case.png";
CaseLabel = "Case";
CaseCoverIsVisible = true;
CaseCoverImgSource = "img_assembly_case_cover.png";
CaseCoverLabel = "Case Cover";
CaseScrewIsVisible = true;
CaseScrewImgSource = "img_assembly_case_screw.png";
CaseScrewLabel = "Case Screw";
HardDiskDriveIsVisible = true;
HardDiskDriveImgSource = "img_assembly_hard_disk_drive.png";
HardDiskDriveLabel = "Hard Disk Drive";
HeatsinkIsVisible = true;
HeatsinkImgSource = "img_assembly_heat_sink.png";
HeatsinkLabel = "Heatsink";
MemoryModuleIsVisible = true;
MemoryModuleImgSource = "img_assembly_memory_module.png";
MemoryModuleLabel = "Memory Module";
MotherboardIsVisible = true;
MotherboardImgSource = "img_assembly_motherboard.png";
MotherboardLabel = "Motherboard";
MotherboardScrewIsVisible = true;
MotherboardScrewImgSource = "img_assembly_motherboard_screw.png";
MotherboardScrewLabel = "Motherboard Screw";
PowerSupplyIsVisible = true;
PowerSupplyImgSource = "img_assembly_power_supply.png";
PowerSupplyLabel = "Power Supply";
ProcessorIsVisible = true;
ProcessorImgSource = "img_assembly_processor.png";
ProcessorLabel = "Processor";
}
}
Current Build demo in GIF: https://imgur.com/a/oLeM9DV (i can't directly link the GIF because its too big)
Because when you trigger the PropertyChanged event, the drag and drop action has already been completed and the image has been automatically set to the target.
We could make that judgment as soon as we drag it over the target with DragOver event.Then make an action when the drag and drop is complete with Drop event.
For example,change the codes of the Image (imgCaseDropZone):
<Image x:Name="imgCaseDropZone"
RelativeLayout.XConstraint="0"
RelativeLayout.YConstraint="0"
RelativeLayout.WidthConstraint="400"
RelativeLayout.HeightConstraint="400"
BackgroundColor="LightGray"
>
<Image.GestureRecognizers>
<DropGestureRecognizer AllowDrop="{Binding CaseIsVisible}" DragOver="CaseDropZone_DragOver" Drop="CaseDropZone_Drop"/>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped" />
</Image.GestureRecognizers>
</Image>
in the code behind:
private void CaseDropZone_DragOver(object sender, DragEventArgs e)
{
FileImageSource file = (FileImageSource)e.Data.Image;
string name = file.File;
if (name != "img_assembly_case.png")
{
e.Data.Image = string.Empty; // set the value string.Empty when drag it over the target,then it will fill the empty source to the image.
}
}
private async void CaseDropZone_Drop(object sender, DropEventArgs e)
{
var ctx = (BindingContext as AssemblyViewModel);
FileImageSource source = (FileImageSource)await e.Data.GetImageAsync();
string name = source.File;
if (name == "img_assembly_case.png")
{
ctx.CaseIsVisible = false;
imgMotherboardDropZone.IsVisible = true;
}
else
{
Global.Score.PCAssembly -= 5;
DisplayAlert($"Score: {Global.Score.PCAssembly}", "The pre-requisite component for this part has not yet been placed.", "OK");
}
}
Other Images are modified like this.
Currently i have a clicked function that delete an items :
async void DeleteButtonClicked(object sender, EventArgs e)
{
ImageButton button = sender as ImageButton;
var agenda = button.BindingContext as Agenda;
await App.Database.DeleteAgendaAsync(agenda);
await Navigation.PopAsync();
}
I want to implement this MVVM style with a command, the basic of my app is that i have an AgendaPage that loads items in a collection view, right now i want to be able to call the command on this icon and so it delete the item.
Thanks for your help.
Currently in my PageViewModel i only have this
I have to pass the current agenda to the DeleteAgendaAsync() method but it's unclear to me where to get it.
public Command DeleteAgendaCommand { get; set; }
public AgendaPageViewModel()
{
DeleteAgendaCommand = new Command(async () => await DeleteAgenda());
}
async Task DeleteAgenda()
{
await App.Database.DeleteAgendaAsync();
}
AgendaDatabase.cs in the Database folder
using System;
using System.Collections.Generic;
using System.Text;
using SQLite;
using Calculette.Models;
using System.Threading.Tasks;
using Calculette.ViewModel;
namespace Calculette.Database
{
public class AgendaDatabase
{
readonly SQLiteAsyncConnection database;
public AgendaDatabase(string dbPath)
{
database = new SQLiteAsyncConnection(dbPath);
database.CreateTableAsync<Agenda>().Wait();
}
// Get all agenda
public Task<List<Agenda>> GetAgendasAsync()
{
return database.Table<Agenda>().ToListAsync();
}
// Get specific agenda
public Task<Agenda> GetAgendaAsync(int id)
{
return database.Table<Agenda>()
.Where(i => i.ID == id)
.FirstOrDefaultAsync();
}
// Insert new agenda (save)
public Task<int> SaveAgendaAsync(Agenda agenda)
{
if (agenda.ID != 0)
{
return database.UpdateAsync(agenda);
}
else
{
return database.InsertAsync(agenda);
}
}
//Delete specific agenda
public Task<int> DeleteAgendaAsync(Agenda agenda)
{
return database.DeleteAsync(agenda);
}
public Task<int> AddAgendaAsync(Agenda agenda)
{
return database.InsertAsync(agenda);
}
}
}
Agenda.cs in the Models folder
[Table("Agenda")]
public class Agenda
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public string Topic { get; set; }
public string Duration { get; set; }
public DateTime Date { get; set; }
This is the NewFormViewModel.cs which is used to create new agenda items to the collectionview, it feels like i would need to access all the items added there, but im unsure on how to do that for the DeleteCommand
using Calculette.Database;
using Calculette.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
namespace Calculette.ViewModel
{
class NewFormViewModel : BaseViewModel
{
public Command AgendaSaveFormCommand { get; set; }
public NewFormViewModel()
{
// Initialise la valeur du DatePicker a celle d'aujourd'hui
this.Date = DateTime.Now;
// Commande pour la sauvegarde sur la page NewFormPage ( voir SaveForm() plus bas)
AgendaSaveFormCommand = new Command(async () => await SaveForm(), () => !IsBusy);
}
// Création des propriétés d'un agenda
private string topic;
public string Topic
{
get => topic;
set
{
topic = value;
NotifyPropertyChanged();
}
}
private string duration;
public string Duration
{
get => duration;
set
{
duration = value;
NotifyPropertyChanged();
}
}
private DateTime date;
public DateTime Date
{
get => date;
set
{
date = value;
NotifyPropertyChanged();
}
}
bool isBusy = false;
public bool IsBusy
{
get { return isBusy; }
set
{
isBusy = value;
NotifyPropertyChanged();
AgendaSaveFormCommand.ChangeCanExecute();
}
}
public int ID { get; }
// Methode qui enregistre un agenda et l'ajoute a la collection d'agenda de AgendaPage
async Task SaveForm()
{
IsBusy = true;
await Task.Delay(4000);
IsBusy = false;
// Agenda agenda = new Agenda();
//ObservableCollection<Agenda> agenda = new ObservableCollection<Agenda>();
Agenda agenda = new Agenda();
agenda.Topic = Topic;
agenda.Date = Date;
agenda.Duration = Duration;
await App.Database.SaveAgendaAsync(agenda);
await Application.Current.MainPage.DisplayAlert("Save", "La tâche a été enregistrée", "OK");
await Application.Current.MainPage.Navigation.PopAsync();
}
}
AgendaPage.xaml
<CollectionView Grid.Row="2" Margin="25" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
SelectionMode="Single" x:Name="AgendaCollection" ItemsSource="{Binding Agendas}"> <!--ItemsSource="{Binding AngedaCollection}" -->
<CollectionView.Header>
<StackLayout Orientation="Horizontal" Spacing="220">
<Label Text="Agenda" TextColor="Black" FontSize="18"/>
<StackLayout Orientation="Horizontal">
<ImageButton Source="iconplus.png" HeightRequest="30" WidthRequest="30" Clicked="GoToNewFormPage"></ImageButton>
<ImageButton Source="iconmoins.png" HeightRequest="30" WidthRequest="30" Clicked="DeleteButtonClicked"></ImageButton>
</StackLayout>
</StackLayout>
</CollectionView.Header>
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" ItemSpacing="20"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate >
<DataTemplate>
<pv:PancakeView HasShadow="True" BackgroundColor="White" VerticalOptions="StartAndExpand "
HorizontalOptions="FillAndExpand">
<Grid VerticalOptions="StartAndExpand" HorizontalOptions="FillAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<BoxView BackgroundColor="{Binding Color}" WidthRequest="3" HorizontalOptions="Start"
VerticalOptions="FillAndExpand"/>
<Expander Grid.Column="1" >
<Expander.Header>
<Grid HorizontalOptions="FillAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="3.5*"/>
</Grid.ColumnDefinitions>
<StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<Label Text="{Binding Date, StringFormat='{0:dd}'}" TextColor="#008A00" FontSize="27"
HorizontalOptions="Center"/>
<Label Text="{Binding Date, StringFormat='{0:MMMM}'}" TextColor="Black" FontSize="10"
HorizontalOptions="Center" Margin="0,-10,0,0" FontAttributes="Bold"/>
<ImageButton Source="iconplus.png" HorizontalOptions="Center" HeightRequest="30" WidthRequest="30" Clicked="GoToFormPage"></ImageButton>
</StackLayout>
<BoxView Grid.Column="1" BackgroundColor="#F2F4F8" WidthRequest="1" HorizontalOptions="Start"
VerticalOptions="FillAndExpand"/>
<StackLayout x:Name="topicLayout" Grid.Column="2" HorizontalOptions="Start" VerticalOptions="Center" Margin="20">
<Label Text="{Binding Topic}" TextColor="#008A00" FontSize="15" FontAttributes="Bold"/>
<Label Text="{Binding Duration}" TextColor="#2F3246" FontSize="12" Margin="0,-10,0,0"/>
<ImageButton Source="iconmoins.png" HeightRequest="30" WidthRequest="30" Command="{Binding Source={x:Reference AgendaCollection}, Path=AgendaPageViewModel.DeleteAgendaCommand}"
CommandParameter="{Binding .}"></ImageButton>
</StackLayout>
</Grid>
</Expander.Header>
<Grid HorizontalOptions="FillAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="3.5*"/>
</Grid.ColumnDefinitions>
<BoxView Grid.Column="1" BackgroundColor="#F2F4F8" WidthRequest="1" HorizontalOptions="Start"
VerticalOptions="FillAndExpand"/>
<StackLayout Grid.Column="2" Spacing="10">
<Label Text="Tâches" TextColor="Black" FontSize="15" Margin="20,0"/>
<StackLayout BindableLayout.ItemsSource="{Binding Speakers}" HorizontalOptions="Start" VerticalOptions="Center" Margin="20,0,0,20">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label TextColor="#2F3246" FontSize="12">
<Label.FormattedText>
<FormattedString>
<FormattedString.Spans>
<Span Text="{Binding Time}"/>
<Span Text=" - "/>
<Span Text="{Binding Name}" FontAttributes="Bold"/>
</FormattedString.Spans>
</FormattedString>
</Label.FormattedText>
</Label>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</StackLayout>
</Grid>
</Expander>
</Grid>
</pv:PancakeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Im not sure where you have your delete button, so here are two possible ways of doing this:
1) You select an Agenda from the CollectionView and then use one Button to delete this agenda:
Add the SelectedItem as a Property to the ViewModel like that:
ObservableCollection<Agenda> agendas = new ObservableCollection<Agenda>();
public ObservableCollection<Agenda> Agendas { get => agendas; set => agendas = value; }
public Agenda SelectedItem { get; set; }
public Command Delete { get; set; }
public ViewModel()
{
Agendas.Add(new Agenda() { Name = "test" });
Agendas.Add(new Agenda() { Name = "test2" });
Agendas.Add(new Agenda() { Name = "test3" });
Delete = new Command(new Action<object>((obj) =>
{
DeleteAsync(SelectedItem);
}));
}
private void DeleteAsync(Agenda selectedItem)
{
//delete Agenda here
}
To use this simply bind the SelectedItem property of the CollectionView to your ViewModel:
<StackLayout>
<StackLayout.BindingContext>
<local:ViewModel/>
</StackLayout.BindingContext>
<CollectionView ItemsSource="{Binding Agendas}"
SelectionMode="Single"
SelectedItem="{Binding SelectedItem}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame>
<StackLayout>
<Label Text="{Binding Name}" FontSize="120" Margin="12"/>
</StackLayout>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Button Text="Delete" Command="{Binding Delete}" />
</StackLayout>
2) You have a button to delete an Agenda for each Agenda:
You can setup your ViewModel similar to this:
ObservableCollection<Agenda> agendas = new ObservableCollection<Agenda>();
public ObservableCollection<Agenda> Agendas { get => agendas; set => agendas = value; }
public Command Delete { get; set; }
public ViewModel()
{
Delete = new Command(new Action<object>((obj) =>
{
DeleteAsync((Agenda) obj);
}));
}
private void DeleteAsync(Agenda selectedItem)
{
//delete Agenda here
}
Then you can set this up in xaml like this:
<StackLayout>
<StackLayout.BindingContext>
<local:ViewModel/>
</StackLayout.BindingContext>
<CollectionView x:Name="AgendaCollection"
ItemsSource="{Binding Agendas}" >
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame>
<StackLayout>
<Label Text="{Binding Topic}" FontSize="120" Margin="12"/>
<Button Text="Delete"
Command="{Binding Source={x:Reference AgendaCollection}, Path=BindingContext.Delete}"
CommandParameter="{Binding .}"/>
</StackLayout>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
Note you can directly Bind to a Command, you dont have to use ButtonClicked and than pass it on.
In this case you have to change the Binding Source to the CollectionView (give that a x:Name and Bind to that) because the Binding Context inside the DataTemplate will be set to the Children of the ItemSource (the individual Agendas). That way the command is automatically executed without the Clicked event.
To pass on the Agenda on which delete was clicked to the Command you can set the CommandParameter. Here it Binds to itself (As i said CollectionView will set the DataContext of its elements to the corresponding individual element.)
Here is my Listview
Inside Listview button IsEnabled Property not working,IsEnabled False not working.
I followed This step but still its not working
https://forums.xamarin.com/discussion/47857/setting-buttons-isenabled-to-false-does-not-disable-button
Inside My ViewModel
OrderItems=PopuldateOrders();// getting List Items
<ListView x:Name="OrderItems" VerticalOptions="Fill"
BackgroundColor="White" HasUnevenRows="True"
SeparatorVisibility="None" ItemsSource="{Binding OrderItems}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView BackgroundColor="White">
<Grid BackgroundColor="Transparent" Margin="0" VerticalOptions="FillAndExpand" x:Name="Item">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10*"/>
<ColumnDefinition Width="18*"/>
<ColumnDefinition Width="18*"/>
<ColumnDefinition Width="17*"/>
<ColumnDefinition Width="20*"/>
<ColumnDefinition Width="17*"/>
</Grid.ColumnDefinitions>
<Label Text="{Binding PullSheetId}" Grid.Row="0" IsVisible="False"/>
<controls:CheckBox Checked="{Binding IsChecked}" Grid.Row="0" Grid.Column="0" IsVisible="{Binding IsEnableShipBtn}" Scale=".8"/>
<Label Text="{Binding KitSKU}" Grid.Row="0" Grid.Column="1"
HorizontalTextAlignment="Center" VerticalOptions="Center" FontSize="Small" TextColor="Black"/>
<Label Text="{Binding SKU}" Grid.Row="0" Grid.Column="2"
HorizontalTextAlignment="Center" VerticalOptions="Center" FontSize="Small" TextColor="{Binding ItemColor}"/>
<Label Text="{Binding ReqPackQty}" Grid.Row="0" Grid.Column="3"
HorizontalTextAlignment="Center" VerticalOptions="Center" FontSize="Small" TextColor="Black"/>
<local:EntryStyle Scale=".6" Text="{Binding ScanQuantity}" Grid.Row="0" Keyboard="Numeric"
Grid.Column="4" HorizontalTextAlignment="Center"
VerticalOptions="Center" Placeholder="Qty" IsEnabled="True" x:Name="QtyEntry"
>
<local:EntryStyle.Behaviors>
<eventToCommand:EventToCommandBehavior EventName="TextChanged"
Command="{Binding Source={x:Reference OrderItems}, Path=BindingContext.ChangeItemQty}"
CommandParameter="{Binding Source={x:Reference Item}, Path=BindingContext}"
/>
</local:EntryStyle.Behaviors>
</local:EntryStyle>
<Button Text="Ship" Scale=".6" Grid.Row="0" Grid.Column="5"
VerticalOptions="Center" BackgroundColor="#6eb43a" TextColor="White"
BorderRadius="20" CornerRadius="20" BorderColor="{Binding isError}" BorderWidth="3"
MinimumWidthRequest="60"
x:Name="ShipBtn"
Command="{Binding Source={x:Reference OrderItems}, Path=BindingContext.SubmitSingleItem}"
IsEnabled="{Binding IsEnableShipBtn}" IsVisible="{Binding IsEnableShipBtn}"
CommandParameter="{Binding .}"
/>
</Grid>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Behaviors>
<eventToCommand:EventToCommandBehavior EventName="ItemTapped" Command="{Binding PackerItemsItemTapped}"/>
</ListView.Behaviors>
</ListView>
How to solve this?
You could achieve CanExecute method in ICommand to replace the IsEnabled property of Button. You could refer to my demo.
This is a GIF of my demo.
Firstly, You could see MainPage.xaml. Binding the model view and set command for Buttons.
<?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:TestDemo"
x:Class="TestDemo.MainPage">
<!--BindingContext from ButtonExecuteViewModel -->
<StackLayout>
<StackLayout.BindingContext>
<local:ButtonExecuteViewModel/>
</StackLayout.BindingContext>
<Button
Text="click me to enable following button"
Command="{Binding NewCommand}"/>
<Button
Text="Cancel"
Command="{Binding CancelCommand}"/>
</StackLayout>
Here is View Model ButtonExecuteViewModel.cs. You could see the construction method of ButtonExecuteViewModel, it set the execute and canExecute to acheve isEnable of Button.
public class ButtonExecuteViewModel : INotifyPropertyChanged
{
bool isEditing;
public event PropertyChangedEventHandler PropertyChanged;
public ButtonExecuteViewModel()
{
NewCommand = new Command(
execute: () =>
{
IsEditing = true;
RefreshCanExecutes();
},
canExecute: () =>
{
return !IsEditing;
});
CancelCommand = new Command(
execute: () =>
{
IsEditing = false;
RefreshCanExecutes();
},
canExecute: () =>
{
return IsEditing;
});
}
void RefreshCanExecutes()
{
(NewCommand as Command).ChangeCanExecute();
(CancelCommand as Command).ChangeCanExecute();
}
public ICommand NewCommand { private set; get; }
public ICommand CancelCommand { private set; get; }
public bool IsEditing
{
private set { SetProperty(ref isEditing, value); }
get { return isEditing; }
}
//Determine if it can be executed
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));
}
}
I tried all the answers here and nothing worked, I only solved creating a custom button component:
CustomButtonView.xaml
<Button xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CustomComponents.CustomButton.CustomButtonView"
FontAttributes="Bold"
TextColor="White"
FontSize="20"
TextTransform="Uppercase"
CornerRadius="20"
WidthRequest="200"
HorizontalOptions="Center" />
CustomButtonView.xaml.cs: Here lives the magic of the custom IsEnabled
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace CustomComponents.CustomButton
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CustomButtonView
{
#region constructors
public CustomButtonView()
{
InitializeComponent();
BackgroundColor = Color.Blue;
Clicked += (_, args) =>
{
if (IsEnabled) Command?.Execute(args);
};
}
#endregion
#region Command
public new static readonly BindableProperty CommandProperty = BindableProperty.Create(
nameof(Command),
typeof(ICommand),
typeof(CustomButtonView));
public new ICommand? Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
#endregion
#region IsEnabled
public new static readonly BindableProperty IsEnabledProperty = BindableProperty.Create(
nameof(IsEnabled),
typeof(bool),
typeof(CustomButtonView),
true,
propertyChanged: (bindable, _, newValue) =>
{
var component = (CustomButtonView)bindable;
if ((bool)newValue)
{
component.BackgroundColor = Color.Blue;
}
else
{
component.BackgroundColor = Color.Red;
}
});
public new bool IsEnabled
{
get => (bool)GetValue(IsEnabledProperty);
set => SetValue(IsEnabledProperty, value);
}
#endregion
}
}
Now you can use it like any regular button in your XAML:
<customButton:CustomButtonView Text="Button enabled"
Command="{Binding SomeCommand}"
IsEnabled="True"/>
<customButton:CustomButtonView Text="Button disabled"
Command="{Binding SomeCommand}"
IsEnabled="False"/>
I have a Listview that is creating dynamically data and a switch in every row, what i want is when i toggle a switch the others untoggle.
Is this possible to do?
Example:
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="6*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Text="{Binding article_description}"
FontAttributes="Bold" FontSize="13" Margin="10,5,0,-6" Grid.Row="0" LineBreakMode="NoWrap"/>
<Label Text="{Binding dish_name}"
FontSize="13" Margin="10,0,0,2" Grid.Row="1" Grid.Column="0"/>
<Label Grid.Row="0" Grid.Column="0" x:Name="LabelReserved" Text="{Binding reserved}" IsVisible="false" LineBreakMode="NoWrap"/>
<Switch Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" HorizontalOptions="Start" VerticalOptions="Center" IsEnabled="False" Toggled="SwitchMenu_OnToggled" >
<Switch.Triggers>
<DataTrigger TargetType="Switch" Binding="{Binding Source={x:Reference LabelReserved},
Path=Text.Length}" Value="7">
<Setter Property="IsToggled" Value="true" />
</DataTrigger>
</Switch.Triggers>
</Switch>
</Grid>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
Note: This solution was a bit of an experiment on my part - so I would recommend that, if you decide to implement this, use it with caution.
The intent here is to extend Switch to be able to act as a grouped radio button.
First step would be to create a IsToggled or IsChecked or similar property in the item that acts as BindingContext to each list-item. You can implement an interface like:
public interface IToggableItem
{
string GroupName { get; } //not mandatory, only added to support grouped lists
bool IsChecked { get; set; }
}
Second step would be extend Switch to be aware of items-list. We can do that by adding a GroupContext bindable property - which basically represents the parent list-view's ItemsSource.
When a switch is toggled, it iterates through the items-list to set the property as false on other items.
For example:
public class GroupedSwitch : CustomSwitch
{
public static readonly BindableProperty IsGroupingEnabledProperty =
BindableProperty.Create(
"IsGroupingEnabled", typeof(bool), typeof(GroupedSwitch),
defaultValue: default(bool));
public bool IsGroupingEnabled
{
get { return (bool)GetValue(IsGroupingEnabledProperty); }
set { SetValue(IsGroupingEnabledProperty, value); }
}
public static readonly BindableProperty GroupContextProperty =
BindableProperty.Create(
"GroupContext", typeof(IEnumerable), typeof(GroupedSwitch),
defaultValue: default(IEnumerable));
public IEnumerable GroupContext
{
get { return (IEnumerable)GetValue(GroupContextProperty); }
set { SetValue(GroupContextProperty, value); }
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName != nameof(IsToggled))
return;
if (IsToggled != true || GroupContext == null)
return;
var currentItem = BindingContext as IToggableItem;
if (currentItem == null)
return;
if (IsGroupingEnabled)
{
var groupList = GroupContext as IEnumerable<IGrouping<string, IToggableItem>>;
var currentGroup = groupList.FirstOrDefault(x => x.Key == currentItem.GroupName);
if (currentGroup != null)
foreach (var item in currentGroup)
{
if (item != currentItem)
item.IsChecked = false;
}
}
else
{
var simpleList = GroupContext as IEnumerable<IToggableItem>;
if (simpleList != null)
foreach (var item in simpleList)
{
if (item != currentItem)
item.IsChecked = false;
}
}
}
}
Third step would be bind GroupContext property to parent ListView's items source. For example:
<ListView x:Name="ParentListView" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" VerticalOptions="Center">
<Label Text="{Binding Name}" />
<local:GroupedSwitch
IsToggled="{Binding IsChecked}"
GroupContext="{Binding ItemsSource, Source={x:Reference ParentListView}}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Or,
<ListView x:Name="_parentList" IsGroupingEnabled="true" >
<ListView.GroupHeaderTemplate>
<DataTemplate>
<TextCell Text="{Binding Key}" />
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Name}" />
<local:GroupedSwitch
ToggledStateFromCode="{Binding IsSwitchOn}"
IsGroupingEnabled="true"
GroupContext="{Binding ItemsSource, Source={x:Reference _parentList}}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
EDIT 1: Updated code to add support for grouped list.
You should use triggers on each switch. Check this out:
https://www.tutorialspoint.com/xaml/xaml_triggers.htm