How to create select all check box in xamarin form - xamarin.forms

I have create a list view and inside listview template I have multiple check box.
I have a select all check box on before the list view.
Select all and deselect all works fine.
How can uncheck “SelectAll” check box if I uncheck any of the check box present inside list view.

You can try to add new field and bind it to the property IsChecked of CheckBox.
And we can add event handler to the individual CheckBoxes of your ListView, then we can update the binded property of the CheckBox(IsCheckAll).
I created a demo and achieved this function.
You can refer to the following code:
MyViewModel.cs
public class MyViewModel: INotifyPropertyChanged
{
public ObservableCollection<Item> Items { get; set; }
public bool IsUnCheckedOneItem { get; set; } = false;
// add property `IsCheckAll` for the `Select all` checkbox
private bool _isCheckAll;
public bool IsCheckAll
{
get => _isCheckAll;
set
{
SetProperty(ref _isCheckAll, value);
//update all the items
if (!IsUnCheckedOneItem)
{
updateAllItems(IsCheckAll);
IsUnCheckedOneItem = false;
}
}
}
private void updateAllItems(bool ischecked)
{
foreach (var item in Items)
{
item.IsChecked = ischecked;
}
}
public ICommand CheckAllCommand { get; set; }
public ICommand UpdateThisItemCommand { get; set; }
public MyViewModel() {
Items = new ObservableCollection<Item>();
Items.Add( new Item { NumType = "S" , LocationCode = "0001"});
Items.Add(new Item { NumType = "M", LocationCode = "0002" });
Items.Add(new Item { NumType = "L", LocationCode = "0003" });
Items.Add(new Item { NumType = "S", LocationCode = "0001" });
Items.Add(new Item { NumType = "M", LocationCode = "0002" });
Items.Add(new Item { NumType = "L", LocationCode = "0003" });
UpdateThisItemCommand = new Command<Item>(checkboxcommand);
}
private void checkboxcommand(Item obj)
{
if (obj!= null)
{
if (!obj.IsChecked )
{
// you also need to add some other logic codes here
if (IsCheckAll && !CheckAllMethod()) {
IsUnCheckedOneItem = true;
IsCheckAll = false;
}
}
else if (obj.IsChecked && CheckAllMethod()) {
IsCheckAll = true;
}
}
// here
IsUnCheckedOneItem = false;
}
public bool CheckAllMethod()
{
bool ischeckedAll = true;
foreach (var item in Items)
{
if (!item.IsChecked)
{
ischeckedAll = false;
}
}
return ischeckedAll;
}
bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Item.cs
public class Item: INotifyPropertyChanged
{ // IsChecked for the checkbox of Item in listview
private bool _isChecked;
public bool IsChecked
{
get => _isChecked;
set
{
SetProperty(ref _isChecked, value);
}
}
public string NumType { get; set; }
public string LocationCode { get; set; }
public string Barcode { get; set; }
public string UserName { get; set; }
public string PickingAdjustementDate { get; set; }
bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
MainPage.xaml
<ContentPage.BindingContext>
<xamlistviewapp:MyViewModel></xamlistviewapp:MyViewModel>
</ContentPage.BindingContext>
<StackLayout Orientation="Vertical"
Spacing="25"
Padding="30,0"
VerticalOptions="Center"
>
<Label Text="select all items"/>
<CheckBox IsChecked="{Binding IsCheckAll}" >
</CheckBox>
<ListView Grid.Row="1" x:Name="listview" ItemsSource="{Binding Items}" >
<ListView.Header>TheHeader</ListView.Header>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Text="{Binding NumType}" Margin="0,0,0,0" />
<Label Grid.Row="0" Grid.Column="1" Text="{Binding LocationCode}" Margin="0,0,0,0" />
<CheckBox Grid.Row="0" Grid.Column="2" IsChecked="{Binding IsChecked}">
<CheckBox.Behaviors>
<xct:EventToCommandBehavior
Command="{Binding BindingContext.UpdateThisItemCommand, Source={x:Reference listview}}"
CommandParameter="{Binding .}"
EventName="CheckedChanged" />
</CheckBox.Behaviors>
</CheckBox>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>

Related

ListView is not updating

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

How a user select to display 1 out of two (or more) fields on a single Label in Xamarin

I'm trying to use a single Label to display one of the two data fields alternately in Xamarin Forms. Only Label 1 Displaying the binding field (Contact_Name), while second Label which I am trying to use a variable "DisplayField" is not displaying either 'Contact_Address' or 'Contact_eMail' .
Question posted before and Another user tried to help but it didn't work!
Model Class
public class Contacts
{
[PrimaryKey][Autoincrement]
public int Contact_ID { get; set; }
public string Contact_Name { get; set; }
public string Contact_Address { get; set; }
public string Contact_eMail { get; set; }
}
XAML Page
<StackLayout>
<Button Text="Display Address" FontSize="Large" HorizontalOptions="Center" VerticalOptions="Fill" Clicked="Display_Address" />
<Button Text="Display Email" FontSize="Large" HorizontalOptions="Center" VerticalOptions="Fill" Clicked="Display_eMail" />
<Entry HorizontalOptions="FillAndExpand" Text="{Binding DisplayField}" />
<ListView x:Name="listView" HasUnevenRows="True" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell >
<StackLayout Orientation="Vertical" VerticalOptions="CenterAndExpand" >
<Frame >
<StackLayout Orientation="Vertical" VerticalOptions="Center">
<Label Text="{Binding Contact_Name}" FontSize="Medium" LineBreakMode="WordWrap" />
<Label Text="{Binding DisplayField}" LineBreakMode="WordWrap" />
</StackLayout>
</Frame>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Code Behind
public partial class FieldSwap : ContentPage
{
readonly FieldViewModel _fieldViewModel;
readonly SQLiteAsyncConnection _connection = DependencyService.Get<ISQLite>().GetConnection();
public ObservableCollection<Contacts> CList { get; set; }
public static string DisplayField { get; private set; }
public static int caseSwitch { get; private set; }
public FieldSwap()
{
InitializeComponent();
_fieldViewModel = new FieldViewModel();
_fieldViewModel.Field = "Contact_Address";
this.BindingContext = _fieldViewModel;
}
public static void SelectField()
{
switch (caseSwitch)
{
case 1:
DisplayField = "Contact_Address";
break;
case 2:
DisplayField = "Contact_eMail";
break;
default:
DisplayField = ("Contact_Address");
break;
}
}
private void Display_Address(object sender, EventArgs e)
{
caseSwitch = 1;
SelectField();
ReadData();
}
private void Display_eMail(object sender, EventArgs e)
{
caseSwitch = 2;
SelectField();
ReadData();
}
public void ReadData()
{
var list = _connection.Table<Contacts>().ToListAsync().Result;
CList = new ObservableCollection<Contacts>(list);
listView.ItemsSource = CList;
}
}
View Model Class
public class FieldViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
String _field;
public string Field
{
set
{
if (!value.Equals(_field, StringComparison.Ordinal))
{
_field = value;
OnPropertyChanged("DisplayField");
}
}
get
{
return _field;
}
}
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(propertyName));
}
}
You could use IsVisible property to achieve that, not need to bind only one lable.
Therefore, binding Contact_Address and Contact_eMail with two lables in StackLayout as follows:
<StackLayout Orientation="Vertical" VerticalOptions="Center">
<Label Text="{Binding Contact_Name}" FontSize="Medium" LineBreakMode="WordWrap" />
<Label Text="{Binding Contact_Address}" IsVisible="{Binding AddressVisible}" LineBreakMode="WordWrap" />
<Label Text="{Binding Contact_eMail}" IsVisible="{Binding EMailVisible}" LineBreakMode="WordWrap" />
</StackLayout>
Then in Contacts add two visiable proerty:
public class Contacts: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
...
private bool addressVisible;
public bool AddressVisible
{
set
{
if (addressVisible != value)
{
addressVisible = value;
OnPropertyChanged("AddressVisible");
}
}
get
{
return addressVisible;
}
}
private bool eMailVisible;
public bool EMailVisible
{
set
{
if (eMailVisible != value)
{
eMailVisible = value;
OnPropertyChanged("EMailVisible");
}
}
get
{
return eMailVisible;
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now in Contentpage, you could modify the visiable propery when button be clicked:
private void Display_Address(object sender, EventArgs e)
{
foreach(var item in CList )
{
item.AddressVisible = true;
item.EMailVisible = false;
}
}
private void Display_eMail(object sender, EventArgs e)
{
foreach (var item in CList )
{
item.AddressVisible = false;
item.EMailVisible = true;
}
}
Here is the effect:

Focus on Next Entry after max length of 1

I have created dynamic 20 entries and want to focus on next entry after user enter a digit and max length of entry is 1. The focus should be automatically move on next entry.I am sharing my code.Thanks in advance for help.
//model
public class CrossingUIModel
{
public int Id { get; set; }
public string FieldValue { get; set; }
}
//on change property
private ObservableCollection<CrossingUIModel> bindCrossingUIModel;
public ObservableCollection<CrossingUIModel> BindCrossingUIModel
{
get { return bindCrossingUIModel; }
set
{
bindCrossingUIModel = value;
OnPropertyChanged(nameof(BindCrossingUIModel));
}
}
//creating ui
public void CreateUI()
{
UserDialogs.Instance.ShowLoading();
BindCrossingUIModel = new ObservableCollection<CrossingUIModel>();
for (int i = 1; i < 21; i++)
{
CrossingUIModel model = new CrossingUIModel();
model.Id = i;
BindCrossingUIModel.Add(model);
}
UserDialogs.Instance.HideLoading();
}
//xml file
<CollectionView x:Name="CrossingView" ItemsSource="{Binding BindCrossingUIModel, Mode=TwoWay}" SelectionMode="Multiple">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" Span="10" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout HorizontalOptions="FillAndExpand">
<Entry x:Name="Fields" Text="{Binding FieldValue, Mode=TwoWay}"
ReturnType="Next" MaxLength="1" Keyboard="Numeric"
TextChanged="Fields_TextChanged" ></Entry>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Since you had used Data-Binding , it would be better to handle all the logic in ViewModel .
in code behind
Define a custom Entry
public class MyEntry:Entry
{
public static readonly BindableProperty IsFocusProperty =BindableProperty.Create("IsFocus", typeof(bool), typeof(MyEntry), false,propertyChanged: OnChanged);
static void OnChanged(BindableObject bindable, object oldValue, object newValue)
{
var entry = bindable as MyEntry;
var focus = (bool)newValue;
if(focus)
{
entry.Focus();
}
else
{
entry.Unfocus();
}
}
public bool IsFocus
{
get { return (bool)GetValue(IsFocusProperty); }
set {
SetValue(IsFocusProperty, value);
}
}
public MyEntry()
{
this.Focused += MyEntry_Focused;
this.Unfocused += MyEntry_Unfocused;
}
private void MyEntry_Unfocused(object sender, FocusEventArgs e)
{
this.IsFocus = false;
}
private void MyEntry_Focused(object sender, FocusEventArgs e)
{
this.IsFocus = true;
}
}
in Model
public class CrossingUIModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int Id { get; set; }
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
string fieldValue;
public string FieldValue
{
get
{
return fieldValue;
}
set
{
if (fieldValue != value)
{
fieldValue = value;
OnPropertyChanged("FieldValue");
}
}
}
bool isFocus = false;
public bool IsFocus
{
get
{
return isFocus;
}
set
{
if (isFocus != value)
{
isFocus = value;
OnPropertyChanged("IsFocus");
}
}
}
}
in ViewModel
public class MyViewModel
{
public ObservableCollection<CrossingUIModel> BindCrossingUIModel { get; set; }
public MyViewModel()
{
BindCrossingUIModel = new ObservableCollection<CrossingUIModel>();
for (int i = 1; i < 21; i++)
{
CrossingUIModel model = new CrossingUIModel();
model.Id = i;
BindCrossingUIModel.Add(model);
}
foreach (CrossingUIModel model in BindCrossingUIModel)
{
model.PropertyChanged += Model_PropertyChanged;
}
}
private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName== "FieldValue")
{
var model = sender as CrossingUIModel;
if(model.FieldValue.Length==1)
{
model.FieldValue = model.FieldValue.Substring(0, 1);
model.IsFocus = false;
int id = model.Id;
BindCrossingUIModel[id].IsFocus = true;
}
}
}
}
in xaml
Now we don't need to set MaxLength and TextChanged any more .
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<CollectionView x:Name="CrossingView" ItemsSource="{Binding BindCrossingUIModel, Mode=TwoWay}" SelectionMode="Multiple">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" Span="10" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout WidthRequest="100" HeightRequest="30" HorizontalOptions="FillAndExpand">
<local:MyEntry WidthRequest="80" BackgroundColor="LightBlue" HeightRequest="30" x:Name="Fields" Text="{Binding FieldValue, Mode=TwoWay}"
IsFocus="{Binding IsFocus, Mode=TwoWay}"
ReturnType="Next" Keyboard="Numeric"
></local:MyEntry>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
By the way , you could use Grid instead of StackLayout as the Parent Layout of the Entry.

Xamarin forms tabbed page not retrieving data from in OnAppearing

I retrieve data from the Azure database to show one of the tabbed pages. when calling the method from ViewModel in OnAppearing not retrieve data, but when click the button it retrieves and shows on the page.
Please advice If I have constructed ViewModel and view correctly? if so why it doesn't work. ?
Connection manager:
public partial class DatabaseManager
{
static DatabaseManager defaultInstance = new DatabaseManager();
MobileServiceClient client;
IMobileServiceTable<Person> personTable;
private DatabaseManager()
{
this.client = new MobileServiceClient(Constants.AzureMobileAppURL);
this.personTable = client.GetTable<Person>();
}
public static DatabaseManager DefaultManager
{
get
{
return defaultInstance;
}
private set
{
defaultInstance = value;
}
}
public MobileServiceClient CurrentClient
{
get { return client; }
}
}
Model:
public class Person
{
[JsonProperty(PropertyName = "FirstName")]
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
[JsonProperty(PropertyName = "DisplayName")]
public string DisplayName
{
get { return displayName; }
set { displayName = value; }
}
[JsonProperty(PropertyName = "LastName")]
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
}
ViewModel:
public class ProfilePageViewModel : ViewModelBase
{
DatabaseManager manager;
string firstName = "";
string lastName = "";
string displayName = "";;
IMobileServiceTable<Person> personTable;
public ProfilePageViewModel()
{
manager = DatabaseManager.DefaultManager;
this.personTable = manager.CurrentClient.GetTable<Person>();
RefreshCommand = new Command(
execute: async () =>
{
try
{
await GetProfileAsync();
}
catch
{
}
});
}
public async Task GetProfileAsync()
{
try
{
IEnumerable<Person> items = await personTable
.Where(pserson => pserson.Active)
.ToEnumerableAsync();
foreach (var item in items)
{
FirstName = item.FirstName;
LastName = item.LastName;
DisplayName = item.DisplayName;
}
}
catch (Exception e)
{
}
}
public string FirstName
{
private set { SetProperty(ref firstName, value); }
get { return firstName; }
}
public string LastName
{
private set { SetProperty(ref lastName, value); }
get { return lastName; }
}
public string DisplayName
{
private set { SetProperty(ref displayName, value); }
get { return displayName; }
}
public ICommand RefreshCommand { private set; get; }
}
View:
ProfilePage.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="SLSNZ.Views.ProfilePage"
xmlns:controls="clr-
namespace:ImageCircle.Forms.Plugin.Abstractions;
assembly=ImageCircle.Forms.Plugin"
xmlns:local="clr-namespace:SLSNZ.ViewModels"
Title="Profile">
<ContentPage.Resources>
<ResourceDictionary>
<local:ProfilePageViewModel x:Key="viewModel">
</local:ProfilePageViewModel>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Icon>
<OnPlatform x:TypeArguments="FileImageSource">
<On Platform="iOS" Value="icon-profile.png" />
</OnPlatform>
</ContentPage.Icon>
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="0, 20, 0, 0" />
</ContentPage.Padding>
<StackLayout BindingContext="{StaticResource viewModel}">
<Label Text="Display Name"
TextColor="Gray"
FontSize="Small"
HorizontalOptions="Start" />
<Label Text="{Binding DisplayName}"
VerticalOptions="Center"
HorizontalOptions="Start"
VerticalOptions="Start/>
<Label Text="First Name"
TextColor="Gray"
FontSize="Small"
HorizontalOptions="Start" />
<Label Text="{Binding FirstName}"
FontSize="Large"
HorizontalOptions="Start"
VerticalOptions="Start" />
<Label Text="Last Name"
TextColor="Gray"
FontSize="Small"
HorizontalOptions="Start" />
<Label Text="{Binding LastName}"
FontSize="Large"
HorizontalOptions="Start"
VerticalOptions="Start" />
<Button Text="Refresh"
Command="{Binding RefreshCommand}"
Grid.Row="0" Grid.Column="1"/>
</StackLayout>
</ContentPage>
View:
ProfilePage.cs
public partial class ProfilePage : ContentPage
{
ProfilePageViewModel viewModel;
public ProfilePage()
{
InitializeComponent();
viewModel = new ProfilePageViewModel();
}
protected override async void OnAppearing()
{
base.OnAppearing();
await viewModel.GetProfileAsync();
}
}
ViewModelBase:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected 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));
}
}
In your view by the time you await viewModel.GetProfileAsync(); The view will already render.
Your GetProfileAsync in the View Model does an await so will get the data then update it.
I suggest changing the IMobileServiceTable personTable to a property and implement a on Property change to notify the view that the data has changes.
So your viewmodel should implement INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
Then when the Data Changes you can notify it in the view model like:
OnPropertyChanged("personTable");
Also in your view change your code to:
pre-initialize the viewmodel:
public ProfilePage()
{
InitializeComponent();
SetViewModel();
}
protected async void SetViewModel()
{
viewmodel = await viewModel.GetProfileAsync();
}
This way you wont block the UI thread and when you call the OnPropertyChnage it will notify your view to update.
UPDATE:
I have created a small sample Xamarin project for you to demonstrate how you can bind and notify the view of changes.
You had a few issues in your view as well where your DisplayName label was not closed properly and you had duplicate properties for HorizontalOptions in some labels.
Download this Xamarin sample. It had hard coded data but will show you the flow of setting the data and the Binding Context of the View without locking the UI thread.
https://github.com/loanburger/54430503

Xamarin ListView Grouping results in blackscreen

I'm developing an app with Xamarin.Forms (xaml), but now i have a strange behavior with the grouping of the ListView
I have a ListView with a CustomCell if i display it without grouping everything works as expected, but if I set IsGroupingEnabled to true the screen is getting black.
Before Grouping:
With grouping
I have no idea what i'm missing or what i did wrong.
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:cell="clr-namespace:BrindaApp.Cells"
x:Class="BrindaApp.Tabs.MainTab" Title="Main">
<StackLayout>
<StackLayout Orientation="Horizontal">
<Entry Placeholder="Search" HorizontalOptions="StartAndExpand"></Entry>
<Image x:Name="image_Group" HorizontalOptions="End">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="Group_Tapped" />
</Image.GestureRecognizers>
</Image>
</StackLayout>
<StackLayout VerticalOptions="FillAndExpand">
<ListView ItemsSource="{Binding ProductSource}" HasUnevenRows="True" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" x:Name="mainListView"
RelativeLayout.HeightConstraint= "{ConstraintExpression Type=RelativeToParent, Property=Height, Factor=1,Constant=0}" IsPullToRefreshEnabled="True" BackgroundColor="Black"
GroupDisplayBinding="{Binding Category}" GroupShortNameBinding="{Binding Category}" IsGroupingEnabled="True">
<ListView.Resources>
</ListView.Resources>
<ListView.ItemTemplate>
<DataTemplate>
<cell:ProductCell ImageUrl="{Binding ProductImageUrl}" Difficult="{Binding Difficult}" Titel="{Binding Titel}" IsFavorit="{Binding IsFavorit}" ProductId="{Binding ProductId}"
RelativeLayout.HeightConstraint= "{ConstraintExpression Type=RelativeToParent, Property=Height, Factor=.2,Constant=0}" Height="200" Tapped="ProductCell_Tapped"
></cell:ProductCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!--</RelativeLayout>-->
</StackLayout>
</StackLayout>
<!--<Label Text="Some Text"/>-->
</ContentPage>
MainPage.xaml.cs
public partial class MainTab : ContentPage
{
ProductsViewModel viewModel;
public bool IsGrouped { get; set; }
public MainTab()
{
viewModel = new ProductsViewModel();
BindingContext = viewModel;
InitializeComponent();
viewModel.mainListView = mainListView;
image_Group.Source = ImageSource.FromResource("BrindaApp.Imgs.group.png");
}
private void ProductCell_Tapped(object sender, EventArgs e)
{
Navigation.PushAsync(new ProductDetails());
}
private void Group_Tapped(object sender, EventArgs e)
{
if(IsGrouped)
{
mainListView.IsGroupingEnabled = false;
}
else
{
mainListView.IsGroupingEnabled = true;
}
IsGrouped = !IsGrouped;
}
}
Model:
public class ProductModel
{
public string ProductImageUrl { get; set; }
public string Titel { get; set; }
public int Difficult { get; set; }
public bool IsFavorit { get; set; }
public string ProductId { get; set; }
public string Category { get; set; }
}
ViewModel
public class ProductsViewModel:BaseViewModel
{
public ListView mainListView;
ObservableCollection<ProductModel> productSource;
public ObservableCollection<ProductModel> ProductSource
{
get
{
return productSource;
}
set
{
productSource = value;
FirePropertyChanged("ProductSource");
}
}
public ICommand RefreshListView { get; set; }
public ProductsViewModel()
{
ProductSource = new ObservableCollection<ProductModel>();
ProductSource.Add(new ProductModel() { ProductImageUrl = "https://media-cdn.tripadvisor.com/media/photo-s/02/d7/5a/1c/essen-trinken.jpg", IsFavorit = true, Category = "Test" });
ProductSource.Add(new ProductModel() { ProductImageUrl = "https://www.burgerking.at/003_at/website/slider/17_028_pop_cheesemas16_at/17_028_pop_cheesemas16_at_startseitenslider_01_product_angusclaus.png", Category = "Test" });
ProductSource.Add(new ProductModel() { ProductImageUrl = "https://media-cdn.tripadvisor.com/media/photo-s/02/d7/5a/1c/essen-trinken.jpg", IsFavorit = true });
ProductSource.Add(new ProductModel() { ProductImageUrl = "https://www.burgerking.at/003_at/website/slider/17_028_pop_cheesemas16_at/17_028_pop_cheesemas16_at_startseitenslider_01_product_angusclaus.png", Category = "Test" });
FirePropertyChanged("ProductSource");
RefreshListView = new Command(() =>
{
//TODO refresh list
mainListView.IsRefreshing = false;
},
() =>
{
return true;
});
}
}
I'm struggeling here for days and cannot find an answer, hopefuly someone can help me.
As guid i used: https://developer.xamarin.com/guides/xamarin-forms/user-interface/listview/customizing-list-appearance/#Grouping
When reading the link you also referred to yourself, you are required to create a list of lists:
Create a list of lists (a list of groups, each group being a list of elements).
Right now, you just have a flat list which is most likely why you experience your issue.
An example, also taken from the same link, is as follows:
static PageTypeGroup()
{
List<PageTypeGroup> Groups = new List<PageTypeGroup> {
new PageTypeGroup ("Alfa", "A"){
new PageModel("Amelia", "Cedar", new switchCellPage(),""),
new PageModel("Alfie", "Spruce", new switchCellPage(), "grapefruit.jpg"),
new PageModel("Ava", "Pine", new switchCellPage(), "grapefruit.jpg"),
new PageModel("Archie", "Maple", new switchCellPage(), "grapefruit.jpg")
},
new PageTypeGroup ("Bravo", "B"){
new PageModel("Brooke", "Lumia", new switchCellPage(),""),
new PageModel("Bobby", "Xperia", new switchCellPage(), "grapefruit.jpg"),
new PageModel("Bella", "Desire", new switchCellPage(), "grapefruit.jpg"),
new PageModel("Ben", "Chocolate", new switchCellPage(), "grapefruit.jpg")
}
}
All = Groups; //set the publicly accessible list
}

Resources