Xamarin Forms Refresh Layout after Observable Collection changes - c#

I'm having a though time trying to "refresh" one of the views where I'm using a WrapLayout. Even though I change the items inside the ObservableCollection the page does not show the changes made.
Code below (some obfuscation needed due to confidentiality issues but I think the most important part is all there). Any help would be greatly appreciated.
Thanks.
ItemCardsViewModel.cs
// INotifyPropertyChanged implemented on BaseViewModel
public class ItemCardsViewModel : BaseViewModel
{
public ObservableCollection<ItemViewModel> Items { get; set; }
public ICommand RefreshCardsCommand { get; private set; }
public Action OnItemsChanged { get; internal set; }
public ItemCardsViewModel()
{
(...)
this.RefreshCardsCommand = new Command(RefreshCards);
}
private void RefreshCards(object x)
{
this.Items = new ObservableCollection<ItemViewModel>(
this.Items.Select(x =>
{
x.IsVisible = false;
return x;
}));
OnPropertyChanged(nameof(this.Items));
if (this.OnItemsChanged != null)
OnItemsChanged();
}
(...)
}
ItemCards.xaml.cs
public partial class ItemCards : ContentPage
{
ItemCardsViewModel ViewModel => ((ItemCardsViewModel)this.BindingContext);
public ItemCards()
{
InitializeComponent();
foreach (var item in ViewModel.Items)
{
var cell = new ItemView { BindingContext = item };
CardsLayout.Children.Add(cell);
}
ViewModel.OnItemsChanged += CardsLayout.ForceLayout;
}
}
ItemCards.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:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True" (...)>
<ContentPage.Content>
<Grid>
(...)
<ScrollView Orientation="Vertical" Padding="0,5,0,5" Grid.Column="0" Grid.Row="2">
<ScrollView.Content>
<local:WrapLayout x:Name="CardsLayout" Spacing="5" HorizontalOptions="Start" VerticalOptions="Start" />
</ScrollView.Content>
</ScrollView>
</Grid>
</ContentPage.Content>
</ContentPage>
EDIT: Forgot to mention but I'm using Prism so the ViewModel is automatically wired up to the view.
EDIT 2: Just a quick update on this one... The issue persists even if I don't create a new Instance of the ObservableCollection on the RefreshCards method but rather loop through the records and set the IsVisible property one by one. Also tried to add a new ItemViewModel to the collection. Always the same result, no changes are shown on the page.

Related

Xamarin Forms Binding Page Title from Model

Does anyone know how to bind a Page Title from a model? I wanted to make the Name property as Title Page below is my code
Xaml
<ContentPage BackgroundColor="White"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="app.MainMenuPage"
NavigationPage.HasBackButton="False">
<NavigationPage.TitleView>
<StackLayout>
<Label Text="{Binding Name}"/>
</StackLayout>
</NavigationPage.TitleView>
My Model
public class EmployeeDetails
{
public string PersonnelNumber { get; set; }
public string PrimaryContactEmail { get; set; }
public string Name { get; set; }
}
You can do it through your viewmodel, assigning your viewmodel to your view with the bindingContext, put this in the constructor of your view BindingContext = new TEstViewModel();
TEstViewModel should be the name of your viewModel.
In your viewmodel you have to have your model as a public property:
public EmployeeDetails Detail { get; set; }
Then in your XAML view you can put Detail.Name
<ContentPage BackgroundColor="White"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="app.MainMenuPage"
Title="{Binding Detail.Name}"
NavigationPage.HasBackButton="False">
If we want to bind a model and display the data of our model to our page, we need to set the model to the BindingContext of our page. And in general, we usually create a ViewModel for our page.
From document ViewModel , we know that:
The view model implements properties and commands to which the view
can data bind to, and notifies the view of any state changes through
change notification events. The properties and commands that the view
model provides define the functionality to be offered by the UI.
I created a simple demo, you can refer to the following code:
1.create a ViewModel (TestViewModel.cs) :
In this ViewModel, we can init our data(employee ) .
public class TestViewModel
{
public EmployeeDetails employee { get; set; }
public TestViewModel() {
employee = new EmployeeDetails { PersonnelNumber = "01", Name = "Jack", PrimaryContactEmail = "test123#gmail.com" };
}
}
2.In OurPage.xaml.cs
we can set the BindingContext
public partial class MainPage : ContentPage
{
TestViewModel viewModel;
public MainPage()
{
InitializeComponent();
viewModel = new TestViewModel();
this.BindingContext = viewModel;
}
}
3.In OurPage.xaml
we can display our data like this(<Label Text="{Binding employee.Name}"/>):
<?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="FormApp1214.MainPage">
<NavigationPage.TitleView>
<StackLayout>
<Label Text="{Binding employee.Name}"/>
</StackLayout>
</NavigationPage.TitleView>
<StackLayout>
</StackLayout>
</ContentPage>

How to fix the issue with binding generic list to listview in xamarin forms?

I'm new to Xamarin forms. I tried to make a card view using ListView in Xamarin forms. Problem is that I am having issue with binding data from generic list.
The number of rows shown in listview are the same as the number of rows in the list but the property values don't bind to the XAML tags
I've tried it using both approaches by binding the data from code behind and directly to the item source in 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="MedicalCenter.Pages.MenuPage">
<ListView x:Name="MyListView"
ItemsSource="{Binding Items}"
ItemTapped="Handle_ItemTapped"
CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding lstHomeMenu}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
public partial class HomePage : ContentPage
{
public HomePage ()
{
InitializeComponent ();
this.BindingContext = new HomeMenuViewModel();
}
}
public class HomeMenuViewModel
{
public IList<HomeMenu> lstHomeMenu { get; set; }
public HomeMenuViewModel()
{
lstHomeMenu = new List<HomeMenu>();
GenerateCardModel();
}
private void GenerateCardModel()
{
lstHomeMenu.Add(new HomeMenu()
{
Title = "Request Appointment",
Icon = "icon_appointment.png",
BackgroundColor = "#479589"
});
lstHomeMenu.Add(new HomeMenu()
{
Title = "Order Prescription",
Icon = "icon_prescription.png",
BackgroundColor ="#4383D9"
});
}
}
public class HomeMenu
{
public string Title { get; set; }
public string Icon { get; set; }
public string BackgroundColor { get; set; }
}
}
When I bind a List to the ListView the data binds properly.
public class HomeMenuViewModel
{
public IList<string> lstHomeMenu { get; set; }
public HomeMenuViewModel()
{
lstHomeMenu = new List<string>();
GenerateCardModel();
}
private void GenerateCardModel()
{
lstHomeMenu = new ObservableCollection<string>
{
"1",
"2",
"3",
"4",
"5",
"6"
};
}
}
}
<?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="MedicalCenter.Pages.MenuPage">
<ListView x:Name="MyListView"
ItemsSource="{Binding Items}"
ItemTapped="Handle_ItemTapped"
CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding .}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
but when I bind the List the binding doesn't work but I get the same number of rows in the ListView as the number of rows in the List.
Edited Question:
List View Result
This is the View I am getting. Seems like the list is binding but the properties inside the object are not binding
UPD:
I still think that your XAML is a problem.
I've recreated a project from the provided code. Please look at the lines 10 and 16 here.
==========
Original answer:
I think that problem is here:
ItemsSource="{Binding Items}"
Your HomeMenuViewModel contains public IList<string> lstHomeMenu { get; set; }, not Items.
Ok. I found the problem. During building the release version when I select
"Sdk and User Assemblies" for "Linking" menu in Android Options properties. after that the list view doesn't bind the data. When I select "Sdk assemblies only" it works fine. Don't know why this is happening.

Databinding is not working between two tabbedpage children

I'm new to Xamarin.Forms development and I'm trying to build a tiny Xamarin app to start.
My app currently has one main TabbedPage that has two ContentPages children. On the ListePage, I have a ListView with an ObservableCollection with OlonaModel is an object with an int Numero and a Text string. I would like the Details page to show details of the selected OlonaModel from the listview of ListePage, but the Details page doesn't seem to update to changes when I select an item from the listview.
Both of the content pages are bound to the same ListPageViewModel. The view model updates when I select an item from the listview, but the changes aren't reflected on the Details page and I'm really confused.
How can I make the Details refresh itself when the SelectedItem of the view model gets set ?
The MainPage :
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage 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"
xmlns:local="clr-namespace:App.Views"
xmlns:viewmodels="clr-namespace:App.ViewModels"
mc:Ignorable="d"
x:Class="App.MainPage">
<TabbedPage.Children>
<local:ListePage/>
<local:Details/>
</TabbedPage.Children>
</TabbedPage>
The ListePage.xaml (ContentPage1 in the post) :
<?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="App.Views.ListePage"
Title="Liste">
<ContentPage.Content>
<StackLayout>
<ListView x:Name="ListeMipoitra" ItemSelected="ListeMipoitra_ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Text="{Binding Numero}" Grid.Column="0" FontSize="Medium" FontAttributes="Bold"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
The ListePage.xaml.cs :
namespace App.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ListePage : ContentPage
{
public static ListePageViewModel viewModel;
public ListePage()
{
InitializeComponent();
viewModel = new ListePageViewModel();
this.BindingContext = viewModel;
ListeMipoitra.ItemsSource = viewModel.listeOlonaVM;
}
private void ListeMipoitra_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
viewModel.setSelected((OlonaModel)e.SelectedItem);
}
}
}
The Details.xaml (ContentPage2 in the post) :
<?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="App.Views.Details"
Title="Détails">
<ContentPage.Content>
<StackLayout>
<Label Text="{Binding Text}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
The Details.xaml.cs :
namespace App.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Details : ContentPage
{
public Details()
{
InitializeComponent();
this.BindingContext = ListePage.viewModel.selected;
}
}
}
The ListePageViewModel.cs :
Note: I'm using Fody and PropertyChanged.Fody weaver, so the RaisePropertyChanged() event (should be) is called automatically when a property changes
namespace App.ViewModels
{
public class ListePageViewModel : INotifyPropertyChanged
{
public ObservableCollection<OlonaModel> listeOlonaVM;
public OlonaModel selected { get; set; }
public ListePageViewModel()
{
listeOlonaVM = new ObservableCollection<OlonaModel>();
listeOlonaVM = ListeOlona.liste;
}
public event PropertyChangedEventHandler PropertyChanged;
public void setSelected(OlonaModel olona)
{
selected = olona;
}
}
}
The model of the Olona object :
namespace App.Models
{
public class OlonaModel
{
public int Numero { get; set; }
public string Text { get; set; }
public OlonaModel(int num, string text)
{
this.Numero = num;
this.Text= text;
}
}
}
The ListeOlona.cs where the model of the list is stored:
The InitializeList() method is called at App Startup.
namespace App.ViewModels
{
public static class ListeOlona
{
public static ObservableCollection<OlonaModel> liste = new ObservableCollection<OlonaModel>();
public static void InitializeList()
{
liste.Add(new OlonaModel(1,
"FirstItem"));
liste.Add(new OlonaModel(2,
"Second Item"));
}
}
}
According to your description, there is tabbedpage, two pages, one is Listpage, another is detailpage, you want to select listview item in listpage, then other detail info will display detailpage, am I right?
If yes, I suggest you can do this like this:
TabbedPage:
<TabbedPage
x:Class="App4.TabbedPage2"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App4"
x:Name="tabbedpage2">
<!-- Pages can be added as references or inline -->
<TabbedPage.Children>
<local:ListPage Title="Mian page" BindingContext="{Binding}" />
<local:DetailPage Title="Detail page" BindingContext="{Binding select}" />
</TabbedPage.Children>
public partial class TabbedPage2 : TabbedPage
{
public TabbedPage2 ()
{
InitializeComponent();
this.BindingContext = new ListePageViewModel();
}
}
public class OlonaModel
{
public int Numero { get; set; }
public string Text { get; set; }
}
public class ListePageViewModel : ViewModelBase
{
public ObservableCollection<OlonaModel> listeOlonaVM { get; set; }
private OlonaModel _select;
public OlonaModel select
{
get { return _select; }
set
{
_select = value;
RaisePropertyChanged("select");
}
}
public ListePageViewModel()
{
listeOlonaVM = new ObservableCollection<OlonaModel>()
{
new OlonaModel(){Numero=1,Text="first item"},
new OlonaModel(){Numero=2,Text="second item"},
new OlonaModel(){Numero=3,Text="third item"},
new OlonaModel(){Numero=4,Text="fouth item"}
};
select = listeOlonaVM[0];
}
}
ListPage:
<StackLayout>
<ListView ItemsSource="{Binding listeOlonaVM}" SelectedItem="{Binding select}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Numero}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
DetailPage:
<StackLayout>
<Label
HorizontalOptions="CenterAndExpand"
Text="{Binding Text}"
VerticalOptions="CenterAndExpand" />
</StackLayout>
ViewModelBase implement INotifyPropertyChanged interface.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Xamarin Cross Platform Picker not binding correctly

I am new to Xamarin Cross Platform apps
I am trying to bind a Picker ItemSource to a List and display one property, with out success! My reference has been from here
Please can some one advise where my error is, my View or Xmal please (or probably both)
The List is a List of StdGrades defined as
namespace FitRestults_Dev1
{
class StdGrade
{
public string Gradelbl
{ get; set; }
public string Grade
{ get; set; }
public static List<StdGrade> Grades()
{
List<StdGrade> GradesList = new List<StdGrade>(){
new StdGrade(){ Gradelbl="10th Gup (White belt)", Grade="G10"},
new StdGrade(){ Gradelbl="9th Gup (Organge belt)", Grade="G9"},
new StdGrade(){ Gradelbl="8th Gup (Organge belt 1 tag)", Grade="G8"},
... };
return GradesList;
}
public List<StdGrade> GradesList => Grades();
public static string GetGrade(string Input)
{
List<StdGrade> GradesList = Grades();
var result = (from r in GradesList where r.Gradelbl == Input select r).First();
return result.Grade;
}
}
For the Content page I have defined a simple view as
namespace FitRestults_Dev1
{
class AddStudentView
{
List<StdGrade> _GradeList;
public List<StdGrade> GradeList
{ get => _GradeList;
set
{
_GradeList = StdGrade.Grades();
}
}
}
}
My content page xmal is
<?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="FitRestults_Dev1.AddStudent"
xmlns:src="clr-namespace:FitRestults_Dev1"
>
<ContentPage.BindingContext>
<src:AddStudentView/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout Padding="10" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Grid>
…
<Picker x:Name="GradePicker" Title="Select a Grade" Grid.Row="2" Grid.Column="1" MinimumWidthRequest="100" FontSize="12"
ItemsSource="GradeList" SelectedIndex="0" ItemDisplayBinding="{Binding Gradelbl}">
</Picker>
</StackLayout>
</ContentPage.Content>
</ContentPage>
You are providing the itemsSource the incorrect way it should be a binding
ItemsSource={Binding GradeList}
Also Stop using Generic.List for binding, MVVM with Xamarin Forms should have ObservableCollections as it inherits from INotifyPropertyChanged and INotifyCollectionChanged

Xamarin.Forms transfer data between screens with MVVM

I have two screens.
The 1st one have two Entries and Save button, the 2nd one - two Labels.
Both have corresponding binded ViewModels.
e.g. 1st XAML:
<Entry x:Name="Entry1" Text="{Binding Entry1}"/>
<Button Command="{Binding SaveCommand}" Text="Save"/>
1st ViewModel:
class Screen1ViewModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string entry1;
public string Entry1;
{
get { return entry1; }
set
{
entry1= value;
PropertyChanged(this, new PropertyChangedEventArgs("Entry1"));
}
}
//similar code for Entry2
public ICommand SaveCommand { protected set; get; }
public Screen1ViewModel()
{
SaveCommand = new Command(OnSubmit);
}
public void OnSubmit()
{
//I guess here I supposed to transfer data from 1st screen to 2nd
}
}
Is there any easy way to get strings from 1st screen entries and pass them to 2nd screen labels using ViewModels?
If you don't want these View Models to be coupled or have relationships with each other, you would want some sort of event aggregator or messaging (pub-sub) mechanism. Xamarin Forms comes with a Messaging service out of the box called the Messaging Center to accomplish this.
I've implemented very simplified sample for you.
Of course it's not the best implementation to do the following:
((testApp.App)App.Current).MainPage.Navigation
The best way to implement navigation is to have navigation service like in the following article:
https://mallibone.com/post/a-simple-navigation-service-for-xamarinforms
It's better since in this case your viewModel does not know anything about pages, it knows only string page key. It's also easier to understand code and debug it, since there is a central calling point.
There is also MVVM light toolkit available. The following article demonstrates how to leverage its features and to implement navigation:
https://mobileprogrammerblog.wordpress.com/2017/01/21/xamarin-forms-with-mvvm-light/
Messaging service is the worst thing I can recommend regarding navigation since it's hard to understand code and debugging is a real mess. By decoupling code you make dependent things independent and new people can't get a head or tail of it how the code works. Messaging is good when you pass events from inner viewModels to the root page view model or from view model to view or to page, but it does not suit for navigation task.
My simple sample can be found below:
App code:
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new Views.Page1());
}
Page1.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="testApp.Views.Page1"
xmlns:local="clr-namespace:testApp.Views;assemply=testApp">
<ContentPage.BindingContext>
<local:Page1ViewModel/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<Entry Text="{Binding TextPropertyValue}" />
<Button Command="{Binding SaveCommand}" Text="Save"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Page1ViewModel:
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace testApp.Views
{
public class Page1ViewModel:INotifyPropertyChanged
{
public Page1ViewModel()
{
SaveCommand = new Xamarin.Forms.Command(HandleAction);
}
async void HandleAction(object obj)
{
await ((testApp.App)App.Current).MainPage.Navigation.PushAsync(
new Page2()
{
BindingContext = new Page2ViewModel(TextPropertyValue)
});
}
string entry1;
public string TextPropertyValue
{
get
{
return entry1;
}
set
{
if (value!=entry1)
{
entry1 = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TextPropertyValue)));
}
}
}
public ICommand SaveCommand
{
get;
set;
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Page2.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="testApp.Views.Page2">
<ContentPage.Content>
<StackLayout>
<Label Text="{Binding EntryValue}"/>
</StackLayout>
</ContentPage.Content>
Page2.xaml.cs
public partial class Page2 : ContentPage
{
public Page2()
{
InitializeComponent();
}
}
Page2ViewModel
using System;
namespace testApp.Views
{
public class Page2ViewModel
{
public Page2ViewModel(string entry)
{
EntryValue = entry;
}
public string EntryValue
{
get;
set;
}
}
}

Categories