Multiselect checkbox in xamarin forms cross platform - c#

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

Related

Xamarin Select All in ListView

I'm looking to have a Select All checkbox that will update all the other checkboxes in the listview when it's selected or deselected but I can't find a way to make them update. I've tried a foreach statement, as well as a for statement in the ViewModel and to run the task when the Select All checkbox is changed, but they don't seem to update the UI. Any help is appreaciated!
view model:
public class SyncViewModel : BaseViewModel
{
public ObservableCollection<Company> CompaniesCollection { get; set; }
public ObservableCollection<WellGroup> WellGroupCollection { get; set; }
public ObservableCollection<Well> WellsCollection { get; set; }
public SyncViewModel()
{
Title = "Sync";
CompaniesCollection = new ObservableCollection<Company>();
WellGroupCollection = new ObservableCollection<WellGroup>();
WellsCollection = new ObservableCollection<Well>();
}
public async Task InitializeData()
{
var wellDataStore = new WellDataStore();
var companies = await wellDataStore.GetAllGroups();
if (companies != null)
{
CompaniesCollection.Clear();
foreach (var company in companies)
{
CompaniesCollection.Add(company);
}
}
}
public async Task SyncData()
{
IsBusy = true;
// load and process data
IsBusy = false;
}
}
}
xaml:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:viewModel="clr-namespace:SiteVisits.ViewModels" xmlns:model="clr-namespace:SiteVisits.Models"
x:DataType="viewModel:SyncViewModel"
x:Class="SiteVisits.Views.Sync">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Sync" Clicked="Sync_Clicked" />
</ContentPage.ToolbarItems>
<StackLayout>
<ActivityIndicator
IsVisible="{Binding IsBusy}"
IsRunning="{Binding IsBusy}" />
<CheckBox x:Name="SelectAll" Color="Blue" CheckedChanged="CheckAll" />
<Label Text="Select All" FontSize="Large" VerticalOptions="Center"/>
<ListView x:Name="Companies"
ItemsSource="{Binding CompaniesCollection}"
SelectionMode="Single"
HasUnevenRows="True"
ItemTapped="Companies_Selection">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="10" x:DataType="model:Company">
<CheckBox x:Name="Name" Color="Blue" IsChecked="{Binding IsChecked}" />
<Label Text="{Binding Name}"
FontSize="Large"
VerticalOptions="Center"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
xaml.cs:
SyncViewModel viewModel;
public Sync(SyncViewModel viewModel)
{
InitializeComponent();
this.viewModel = viewModel;
BindingContext = this.viewModel;
}
protected override async void OnAppearing()
{
await viewModel.InitializeData();
}
async private void Sync_Clicked(object sender, EventArgs e)
{
await viewModel.SyncData();
}
private void Companies_Selection(object sender, ItemTappedEventArgs e)
{
if (e.Item != null)
{
var company = e.Item as Company;
company.IsChecked = !company.IsChecked;
}
}
Since you are using MVVM, I would use a binding for the checkbox
<CheckBox x:Name="SelectAll" Color="Blue" IsChecked="{Binding AllChecked}" />
So then you will need this bool property, something like this. So when the value changes, it updates all the collection
private bool _allChecked;
public bool AllChecked { get => _allChecked;
set
{
if (value != _allChecked)
{
UpdateCompaniesCollection(value);
OnPropertyChanged(nameof(AllChecked));
}
}
}
And then you will need the method to update the collection. One way would be
void UpdateCompaniesCollection(bool newValue)
{
for(int i = 0; i < CompaniesCollection.Count; i++)
{
var tempCompany = CompaniesCollection[i];
tempCompany.IsChecked = newValue;
CompaniesCollection[i] = tempCompany;
}
}
That should do the trick. This only will trigger the change of the elements inside the collection when the Checkbox is checked. But if you want to also the the other way round (if a item in unchecked, then deselect the allCheck), that would be more complicated.

Highlighting days in a XamForms Calendar

I have made a Xamarin app that allows me to choose a date in a DatePicker and and save it to a Database using SQLite. I then get those dates and show them in a ListView, and I was wondering if its possible to highlight the days that are in the database onto the Calendar?
This is what is used to save the data
void SaveButton_OnClicked(object sender, EventArgs e)
{
Birthdays brithday = new Birthdays()
{
FirstName = fNameEntry.Text,
LastName = lNameEntry.Text,
Date = datePicker.Date
};
using (SQLiteConnection conn = new SQLiteConnection(App.FilePath))
{
conn.CreateTable<Birthdays>();
int rowsAdded = conn.Insert(brithday);
}
DisplayAlert("Alert", "Birthday has been saved to database", "OK");
}
And this to load it into the ListView
private async void NavigateButton_OnClicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new Page1());
}
protected override void OnAppearing()
{
base.OnAppearing();
using(SQLiteConnection conn = new SQLiteConnection(App.FilePath))
{
conn.CreateTable<Birthdays>();
var birthday = conn.Table<Birthdays>().ToList();
birthdayListView.ItemsSource = birthday;
}
}
And this is my XML im using to load the Calendar and the ListView on the home page.
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" BackgroundColor="{AppThemeBinding Dark=Black, Light=White}" xmlns:controls="clr-namespace:XamForms.Controls;assembly=XamForms.Controls.Calendar" xmlns:d="http://xamarin.com/schemas/2014/forms/design" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" mc:Ignorable="d" x:Class="Mobile_App.MainPage">
<ContentPage.ToolbarItems>
<ToolbarItem
Text="Settings"
Clicked="ToolbarItem_Clicked"
></ToolbarItem>
</ContentPage.ToolbarItems>
<StackLayout>
<!-- Place new controls here -->
<controls:Calendar DatesBackgroundColor="{AppThemeBinding Dark=Black, Light=White}" DatesTextColor="{AppThemeBinding Dark=White, Light=Black}" BackgroundColor="{AppThemeBinding Dark=Black, Light=White}" Padding="10,0,10,0" SelectedBorderWidth="4" DisabledBorderColor="Black" ShowNumberOfWeek="false" StartDay="Sunday" TitleLabelTextColor="Purple" TitleLeftArrowTextColor="MediumVioletRed" TitleRightArrowTextColor="MediumVioletRed" SpecialDates="{Binding Date}" DateCommand="{Binding DateChosen}" />
<Label Text="Welcome to the Birthday App!" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" />
<Button Text="Add a birthday" AnchorX="1" AnchorY="1" Clicked="NavigateButton_OnClicked" />
<Switch AnchorX="1" AnchorY="1" IsToggled="False" Toggled="Switch_Toggled" />
<ListView HeightRequest="250" WidthRequest="50" BackgroundColor="{AppThemeBinding Dark=Black, Light=#444343}" x:Name="birthdayListView">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell
Text="{Binding FullName}"
Detail="{Binding Date}"
DetailColor="YellowGreen">
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
As Jason's reply, you can get list data from Sqlite database, then create ObservableCollection<XamForms.Controls.SpecialDate> attendances to add current Date data.
ObservableCollection Class represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
I do one simple that you can take a look:
<StackLayout>
<controls:Calendar
Padding="10,0,10,0"
BackgroundColor="{AppThemeBinding Dark=Black,
Light=White}"
DateCommand="{Binding DateChosen}"
DatesBackgroundColor="{AppThemeBinding Dark=Black,
Light=White}"
DatesTextColor="{AppThemeBinding Dark=White,
Light=Black}"
DisabledBorderColor="Black"
SelectedBorderWidth="4"
SelectedDate="{Binding Date}"
ShowNumberOfWeek="false"
SpecialDates="{Binding attendances}"
StartDay="Sunday"
TitleLabelTextColor="Purple"
TitleLeftArrowTextColor="MediumVioletRed"
TitleRightArrowTextColor="MediumVioletRed" />
<ListView
x:Name="birthdayListView"
BackgroundColor="{AppThemeBinding Dark=Black,
Light=#444343}"
HeightRequest="250"
ItemsSource="{Binding birthdays}"
WidthRequest="50">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell
Detail="{Binding Date}"
DetailColor="YellowGreen"
Text="{Binding FullName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
public partial class Page38 : ContentPage, INotifyPropertyChanged
{
private List<Birthday> _birthdays;
public List<Birthday> birthdays
{
get { return _birthdays; }
set
{
_birthdays = value;
RaisePropertyChanged("birthdays");
}
}
private DateTime? _date;
public DateTime? Date
{
get
{
return _date;
}
set
{
_date = value;
RaisePropertyChanged(nameof(Date));
}
}
public ObservableCollection<XamForms.Controls.SpecialDate> attendances { get; set; }
public ICommand DateChosen
{
get
{
return new Command((obj) => {
System.Diagnostics.Debug.WriteLine(obj as DateTime?);
});
}
}
public Page38()
{
InitializeComponent();
attendances = new ObservableCollection<SpecialDate>();
Date = DateTime.Now;
this.BindingContext = this;
}
protected override void OnAppearing()
{
base.OnAppearing();
//get data from sqlite database.
birthdays = new List<Birthday>()
{
new Birthday(){FirstName="cherry", Date=new DateTime(2021,5,25), },
new Birthday(){FirstName="cherry", Date=new DateTime(2021,5,26), },
new Birthday(){FirstName="cherry", Date=new DateTime(2021,5,27), },
new Birthday(){FirstName="cherry", Date=new DateTime(2021,5,28), },
new Birthday(){FirstName="cherry", Date=new DateTime(2021,5,30), },
new Birthday(){FirstName="cherry", Date=new DateTime(2021,5,31), },
};
//foreach birthdays data, add Date property to attendances
foreach (Birthday b in birthdays)
{
attendances.Add(new XamForms.Controls.SpecialDate(b.Date) { BackgroundColor = Color.Green, TextColor = Color.White, BorderColor = Color.Yellow, BorderWidth = 8, Selectable = true });
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You need to implement INotifyPropertyChanged to notify data changed.
The screenshot:

Passing parameter from view-model to page and display it

I'm stuck with passing parameter from view model to page. On view model page i have list of properties which i increase by button , after button click sum is displyed on same page below that i collected that many of smth, my goal is to send this sum collected on this view model page to new page which i want to be responsible for displaying this sum . I'm stuck with passing parameter, it just don't update the value, it looks like the binding is okey becouse app don't throw exception that object has no reference. I'm begginer in xamarin and for any explanation or just direction which i can follow to achive this I would be very appreciated. Thank you in advance :)
ListViewModel code:
public class PersonListViewModel : INotifyPropertyChanged
{
public ObservableCollection<PersonViewModel> Persons { get; set; }
PersonViewModel selectedPerson;
double _sumcollected;
public double SumCollected
{
get => _sumcollected;
set
{
if (_sumcollected != value)
{
_sumcollected = value;
OnPropertyChanged("SumCollected");
}
}
}
public INavigation Navigation { get; set; }
public PersonListViewModel()
{
Persons = new ObservableCollection<PersonViewModel>
{
new PersonViewModel()
{
Name="Test", Surname="Test", Description= "TEsT", Background = "bgtest6.jpg", ProgressCounter =0.1, SavedClicked=0,Weight=1
},
new PersonViewModel()
{
Name="Test", Surname="Test", Description= "TEsT",Background = "bgtest6.jpg", ProgressCounter =0.1, SavedClicked=0,Weight=30
},
new PersonViewModel()
{
Name="Test", Surname="Test", Description= "TEsT",Background = "bgtest6.jpg", ProgressCounter =0.2, SavedClicked=0,Weight=100
},
new PersonViewModel()
{
Name="Test", Surname="Test", Description= "TEsT",Background = "bgtest6.jpg", ProgressCounter =0.3, SavedClicked=0,Weight=27
},
};
NavigateCommand = new Command<PersonViewModel>(NavigatationSolved);
IncreaseProgressCommand = new Command<PersonViewModel>(IncreaseProgress);
GotoCounterCommand = new Command<PersonListViewModel>(GotoNumbersPage);
NavigateSumPageCommand = new Command<PersonListViewModel>(NavigateSumPage);
}
private void NavigateSumPage(object obj)
{
Debug.WriteLine("Navigate to sum page ");
PersonListViewModel personListModel = obj as PersonListViewModel;
Navigation.PushAsync(new SumPage(personListModel));
}
//Passing SumCollected not working
private void GotoNumbersPage(object numbers)
{
PersonListViewModel personList = numbers as PersonListViewModel;
Navigation.PushAsync(new CounterPage(personList));
Debug.WriteLine("Next Page ?");
}
private void IncreaseProgress(object sender)
{
PersonViewModel person = sender as PersonViewModel;
if(person.ProgressCounter >= 1)
{
person.ProgressCounter -= person.ProgressCounter;
Application.Current.MainPage.DisplayAlert("Alert!", "Message after one progress bar", "GO!");
}
else
{
person.ProgressCounter += .2;
}
//Navigation.PushPopupAsync(new GratulationAlertPage());
person.SavedClicked += 1;
Debug.WriteLine("Saved Clicked");
SumCollected += 1;
SumCollected += person.Weight;
Debug.WriteLine("New SumCollected value");
}
}
ListViewModelPage code:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CommandDemo.Views.PersonListPage"
>
<NavigationPage.TitleView>
<StackLayout Orientation="Horizontal">
<Button Text="Numbers"
Command="{Binding Path=BindingContext.GotoCounterCommand}"
CommandParameter="{Binding .}"/>
</StackLayout>
</NavigationPage.TitleView>
<ContentPage.Content>
<StackLayout Padding="10"
Margin="10">
<ListView x:Name="personList"
ItemsSource="{Binding Persons}"
HasUnevenRows="True"
>
<!--SelectedItem="{Binding SelectedPerson}"-->
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<StackLayout.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1"
Command="{Binding Source={x:Reference personList},Path=BindingContext.NavigateCommand}"
CommandParameter="{Binding .}"/>
</StackLayout.GestureRecognizers>
<Label Text="{Binding Name}"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
HorizontalOptions="Center"
VerticalOptions="Center"
Margin="5,5,5,5"/>
<ProgressBar Progress="{Binding ProgressCounter}"/>
<Button Text="Add Progress"
Command="{Binding Source={x:Reference personList},Path=BindingContext.IncreaseProgressCommand}"
CommandParameter="{Binding .}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Label Text="{Binding SumCollected}"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Center"
VerticalOptions="Center"
HorizontalOptions="Center"/>
<Button Text="Numbers"
Command="{Binding NavigateSumPageCommand}"
CommandParameter="{Binding .}"/>
</StackLayout>
</ContentPage.Content>
SumViewModel code:
public class CounterViewModel : INotifyPropertyChanged
{
private PersonListViewModel _personListView;
public PersonListViewModel PersonList
{
get => _personListView;
set
{
if (_personListView != value)
{
_personListView = value;
OnPropertyChanged("PersonList");
}
}
}
PersonViewModel _personView;
public PersonViewModel PersonView
{
get => _personView;
set
{
if (_personView != value)
{
_personView = value;
OnPropertyChanged("PersonView");
}
}
}
public double SumCollected
{
get => PersonList.SumCollected;
set
{
if (PersonList.SumCollected != value)
{
PersonList.SumCollected = value;
OnPropertyChanged("SumCollected");
}
}
}
private double _collected;
public double Collected
{
get => _collected;
set
{
if (_collected != value)
{
_collected = value;
OnPropertyChanged("Collected");
}
}
}
public CounterViewModel()
{
PersonList = new PersonListViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Page where i want to display sum collected from list page:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CommandDemo.Views.SumPage">
<ContentPage.Content>
<StackLayout>
<Label Text="{Binding PersonList.SumCollected}"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage.Content>
Sum page code behind:
public partial class SumPage : ContentPage
{
public SumPage (PersonListViewModel personListModel)
{
InitializeComponent ();
BindingContext = new CounterViewModel();
}
}
You need to receive the object that you are passing in your viewmodel.
public CounterViewModel(PersonListViewModel personList)
{
PersonList = personList;
}

Xamarin.Forms binding does not work

I try to rewrite my UWP C# app for Windows10 to Xamarin app using XAML. But Binding (for example here in ListView ItemSource=...) is not working for me and I don´t know why.
Visual Studio tells me, Cannot Resolve Symbol Recording due to unknown Data Context.
Here is my XAML (MainPage.xaml) for testing purpose:
<?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:XamarinTest;assembly=XamarinTest"
x:Class="XamarinTest.MainPage">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<ListView x:Name="listView" IsVisible="false" ItemsSource="{Binding Recording}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal">
<Image Source="Accept" WidthRequest="40" HeightRequest="40" />
<StackLayout Orientation="Vertical" HorizontalOptions="StartAndExpand">
<Label Text="TEST" HorizontalOptions="FillAndExpand" />
<Label Text="TEST" />
</StackLayout>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ContentPage>
Here is C# (MainPage.xaml.cs):
namespace XamarinTest
{
public partial class MainPage : ContentPage
{
public MainPage()
{
this.InitializeComponent();
this.AllTestViewModel = new RecordingViewModel();
this.BindingContext = AllTestViewModel;
}
public RecordingViewModel AllTestViewModel { get; set; }
}
}
And finally ViewModel (RecordingViewModel.cs):
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using XamarinTest.Model;
namespace XamarinTest.ViewModel
{
public class RecordingViewModel : INotifyPropertyChanged
{
public ObservableCollection<Recording> Recordings { get; } = new TrulyObservableCollection<Recording>();
public RecordingViewModel()
{
Recordings.Add(new RecordingTest2()
{
TestName = "Test 1",
TestNote = "Vytvoreni DB",
TestTime = new TimeSpan(0, 0, 0)
});
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public TrulyObservableCollection()
{
CollectionChanged += FullObservableCollectionCollectionChanged;
}
public TrulyObservableCollection(IEnumerable<T> pItems) : this()
{
foreach (var item in pItems)
{
this.Add(item);
}
}
private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
}
}
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));
OnCollectionChanged(args);
}
}
}
Everything (models and viewmodels) are working in native UWP Windows 10 app. Only the Binding and making same view is problem in Xamarin. Could someone please help with binding?
Thx.
EDIT
Recording.cs is here:
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace XamarinTest.Model
{
public abstract class Recording : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string TestName { get; set; }
private TimeSpan _testTime;
private string _testNote;
private string _actualIco = "Play";
private bool _isActive = false;
private bool _enabled = true;
public double IcoOpacity { get; private set; } = 1.0;
public string ActualIco
{
get => _actualIco;
set
{
if (_actualIco == null) _actualIco = "Admin";
_actualIco = value;
NotifyPropertyChanged("ActualIco");
}
}
public bool IsActive
{
get => _isActive;
set
{
if (_isActive == value) return;
_isActive = value;
IcoOpacity = !value ? 1.0 : 0.3;
NotifyPropertyChanged("IsActive");
NotifyPropertyChanged("IcoOpacity");
}
}
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
NotifyPropertyChanged("Enabled");
}
}
public string TestNote
{
get => _testNote;
set
{
if (_testNote == value) return;
_testNote = value;
NotifyPropertyChanged("TestNote");
}
}
public TimeSpan TestTime
{
get => _testTime;
set
{
if (_testTime == value) return;
_testTime = value;
NotifyPropertyChanged("TestTime");
}
}
protected Recording()
{
TestName = "Unkonwn";
TestNote = "";
_testTime = new TimeSpan(0, 0, 0);
}
protected Recording(string testName, string testNote, TimeSpan testTime)
{
TestName = testName;
TestNote = testNote;
_testTime = testTime;
}
public string OneLineSummary => $"{TestName}, finished: "
+ TestTime;
private void NotifyPropertyChanged(string propertyName = "")
{
var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public abstract bool playTest();
}
}
I tried add DataContext in XAML (postet in origin question), because of intellisence like this:
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:dvm="clr-namespace:XamarinTest.ViewModel"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
d:DataContext="{system:Type dvm:RecordingViewModel}"
and this to Grid:
<Label Text="{Binding Recordings[0].TestName}" Grid.Row="0" Grid.Column="2" />
IntelliSence is OK, but text doesn´t show in app.
Finally is working!
XAML should looks like code below.
Imporant is xmls:viewModel="..." and <ContentPage.BindingContext>...</>.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModel="clr-namespace:XamarinTest.ViewModel;assembly=XamarinTest"
x:Class="XamarinTest.MainPage"
>
<ContentPage.BindingContext>
<viewModel:RecordingViewModel/>
</ContentPage.BindingContext>
<ListView x:Name="listView" ItemsSource="{Binding Recordings}" Grid.Row="1" Grid.Column="1">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal">
<Image Source="Accept" WidthRequest="40" HeightRequest="40" />
<StackLayout Orientation="Vertical" HorizontalOptions="StartAndExpand">
<Label Text="{Binding TestName}" HorizontalOptions="FillAndExpand" />
<Label Text="{Binding TestNote}" />
</StackLayout>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ContentPage>
and MainPage.xaml.cs is okey
namespace XamarinTest
{
public partial class MainPage : ContentPage
{
public MainPage()
{
this.InitializeComponent();
this.AllTestViewModel = new RecordingViewModel();
this.BindingContext = AllTestViewModel;
}
public RecordingViewModel AllTestViewModel { get; set; }
}
}
looking at your ViewModel, it looks like there is no Recording member, but you do have a Recordings member.
EDIT
So you are adding your DataContext in the code behind so ignore the Xaml part.
Your View (MainPage.xaml) has a ViewModel(RecordingViewModel.cs). The ViewModel has a member called Recordings (a collection of type Recording). But in your Xaml, you are try to bind to Recording.
Change:
<ListView x:Name="listView" IsVisible="false" ItemsSource="{Binding Recording}">
to:
<ListView x:Name="listView" IsVisible="false" ItemsSource="{Binding Recordings}">
2nd EDIT
The only Labels in your example is the one inside of the ListView yes?
If so, you can access the Recordings children like TestNote by:

Xamarin - Bind view model in xaml

I'm binding grouped listview to view model data source via xaml.
But when the app is launched the list is empty even when I populate it from code behind in ctor. When I declare the ListView x:Name="myList" and populate it from code behind it works, but it's not "binded" to the view model directly.
<?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:viewModels="clr-namespace:Atc.Obedy.ViewModels;assembly=Atc.Obedy"
x:Class="Atc.Obedy.MainPage" Title="Jídelníček">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="20, 40, 20, 20"
Android="20, 20, 20, 20"
WinPhone="20, 20, 20, 20" />
</ContentPage.Padding>
<ContentPage.BindingContext>
<viewModels:MainPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
Orientation="Vertical"
Spacing="15">
<ListView BindingContext="{ Binding MealsGroups }" GroupDisplayBinding="{ Binding DisplayName }" IsGroupingEnabled="true">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical">
<StackLayout Orientation="Horizontal">
<Label Text="{Binding MealTitle}" />
<Button Image="icon.png" Command="{ Binding OrderCommand }" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Code behind:
public partial class MainPage : ContentPage
{
protected IMealsRepository MealsRepository { get; }
public MainPage()
{
MealsRepository = IoC.Container.Get<IMealsRepository>();
var mealGroups = new ObservableCollection<MealsGroup>();
foreach (var meals in MealsRepository.GetMeals(DateTime.MinValue, DateTime.MaxValue).Where(x => x.DayOfOrder != null).GroupBy(x=>x.DayOfOrder))
{
mealGroups.Add(ProvideMealsGroup(meals.Key.Value, meals));
}
InitializeComponent();
var viewModel = BindingContext as MainPageViewModel;
viewModel.MealsGroups = mealGroups;
}
This is View Model:
public class MainPageViewModel : INotifyPropertyChanged, IViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<MealsGroup> _mealGroups = null;
public ObservableCollection<MealsGroup> MealsGroups
{
get { return _mealGroups; }
set
{
_mealGroups = value;
OnPropertyChanged(nameof(MealsGroups));
}
}
public ICommand OrderCommand { get; set; }
public MainPageViewModel()
{
OrderCommand = new Command(() =>
{
Debug.WriteLine("MealsGroups");
});
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And meal group
public class MealsGroup : List<MealViewModel>
{
public string DisplayName { get; set; }
public string ShortName { get; set; }
public event MealGroupOrderSwitched MealGroupOrderSwitched;
public ICommand OrderCommand { get; set; }
public MealsGroup()
{
OrderCommand = new Command(() =>
{
Debug.WriteLine("MealGroup");
});
}
public void AddMeal(Meal meal)
{
var model = new MealViewModel
{
IsOrdered = meal.IsOrdered,
MealTitle = meal.MealTitle,
MealId = meal.MealId.Value,
DayOfOrder = meal.DayOfOrder.Value,
IsOrderable = meal.IsOrderable,
IsSoup = meal.IsSoup
};
model.MealOrdered += meaId =>
{
for (var i = 0; i < Count; i++)
{
var mealViewModel = this[i];
if (mealViewModel.MealId != meaId && mealViewModel.IsOrderable)
mealViewModel.IsOrdered = false;
}
MealGroupOrderSwitched?.Invoke(this);
};
Add(model);
}
}
but the android app when launched has empty list. Even when I added items in code behind in ctor.
Solved by changing in xaml ListView property BindingContext={binding MealsGroups} to ItemsSource={binding MealsGroups}

Categories

Resources