Binding Listview Items to multiple Models - xamarin

I have a List view with a label and an entry. I set the item source of ListView to SQL table in codebehind. My label will have its text from this SQL, but I want to save my Entry.Text to a new model (binding to another model that is not related to listview itemsource)...
HOW TO DO IT??
The SQL stores names.
Here is my xaml:
<ListView x:Name="Mlist" Grid.Row="3" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="5" BackgroundColor="White" RowSpacing="40">
<Label x:Name="label" HorizontalOptions="Center" Text="{Binding name}" />
<Entry x:Name="money" Placeholder="type the income" Text={ *** Binding to another model that is not related to listview itemsource}
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code behind:
Mlist.itemsource = conn.Table<peaople>().ToList();

Move the code which manipulate data into the viewmodel ,make ViewModel implement INotifyPropertyChanged , and set the viewmodel as the BindingContext in code behind.
ViewModel
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
List<People> list;
public List<People> List {
get
{
return list;
}
set
{
list = value;
NotifyPropertyChanged();
}
}
private string money;
public string Money //add Money property directly in viewmodel
{
get { return money; }
set
{
money = value;
NotifyPropertyChanged();
}
}
public ViewModel()
{
List = conn.Table<People>().ToList();
}
}
Page(code behind)
viewModel = new ViewModel();
this.BindingContext = viewModel;
Xaml
Name current page as Self , and redirect the binding path on Entry.Text.
x:Class="FormsApp.Views.Page1"
x:Name="Self"
>
<ListView x:Name="Mlist" ItemsSource="{Binding List}" RowHeight="100" HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout >
<Label x:Name="label" TextColor="Red" HorizontalOptions="Center" Text="{Binding Name}" />
<Entry x:Name="money" Placeholder="type the income" Text="{Binding Source={x:Reference Self}, Path=BindingContext.Money}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Screen shot
All the Entry share the data Money .

Related

Color converter binding not being called

I have a label with black text color inside a frame with white background color, the thing is, I want to assign the background color and text color from the viewmodel, I have created the converter, and did the bindings, but for some reason it isn't working
this is my view code:
?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="ComanderoMovil.Views.PlatillosView"
xmlns:ios="clr -namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
ios:Page.UseSafeArea="true"
xmlns:behaviorsPack="clr- namespace:Xamarin.Forms.BehaviorsPack;assembly=Xamarin.Forms.Behaviors Pack"
xmlns:converterPack="clr-namespace:ComanderoMovil.Converters">
<ContentPage.Resources>
<ResourceDictionary>
<converterPack:ColorConverter x:Key="ColorConverter"/>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout>
<SearchBar> </SearchBar>
<CollectionView ItemsSource="{Binding Grupos}"
HeightRequest="50"
ItemsLayout="HorizontalList"
SelectionMode="Single"
SelectionChangedCommand="{Binding
SelectedGrupoCommand, Mode=TwoWay}">
<CollectionView.ItemTemplate>
<DataTemplate>
<ContentView Padding="2">
<Frame BorderColor="Black"
HasShadow="False"
Padding="2"
BackgroundColor="{Binding
ButtonBackColor, Converter={StaticResource ColorConverter}}">
<StackLayout>
<Label Margin="10"
Text="{Binding nombre}"
TextColor="{Binding
TextColor, Converter = {StaticResource ColorConverter}}"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Center"
FontSize="Small"
VerticalOptions="CenterAndExpand"></Label>
</StackLayout>
</Frame>
</ContentView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage.Content>
</ContentPage>
Here is my ViewModel:
public class PlatillosViewModel : INotifyPropertyChanged
{
private INavigation Navigation;
private ObservableCollection<PlatilloModel> _platillos;
private string _textColor;
private string _backColor;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<GrupoModel> Grupos { get; set; }
public ObservableCollection<PlatilloModel> Platillos
{
get => _platillos;
set
{
_platillos = value;
OnPropertyChanged();
}
}
public string TextColor
{
get => _textColor;
set
{
_textColor = value;
OnPropertyChanged();
}
}
public string ButtonBackColor
{
get => _backColor;
set
{
_backColor = value;
OnPropertyChanged();
}
}
public PlatillosViewModel(INavigation navigation)
{
Navigation = navigation;
TextColor = "Black";
ButtonBackColor = "White";
PlatillosRepository repository = new PlatillosRepository();
Platillos = repository.GetAll();
GrupoRepository grupoRepository = new GrupoRepository();
Grupos = grupoRepository.GetAll();
}
public ICommand SelectedPlatilloCommand => new Command<PlatilloModel>(async platillo =>
{
await Navigation.PushAsync(new PlatilloView());
});
public ICommand SelectedGrupoCommand => new Command<GrupoModel>(async grupo =>
{
ButtonBackColor = "Black";
TextColor = "White";
PlatillosRepository platillosRepository = new PlatillosRepository();
Platillos = platillosRepository.GetFilteredByGroup(grupo);
});
protected virtual void OnPropertyChanged([CallerMemberName] string property = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
and Here is my converter:
public class ColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var valor = value.ToString();
switch(valor)
{
case "White":
return Color.White;
case "Black":
return Color.Black;
default:
return Color.Red;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
Your issue is not with the ValueConverter but with your Bindings.
<CollectionView ItemsSource="{Binding Grupos}"
HeightRequest="50"
ItemsLayout="HorizontalList"
SelectionMode="Single"
SelectionChangedCommand="{Binding SelectedGrupoCommand, Mode=TwoWay}">
<CollectionView.ItemTemplate>
<DataTemplate>
<ContentView Padding="2">
<Frame BorderColor="Black"
HasShadow="False"
Padding="2"
BackgroundColor="{Binding ButtonBackColor, Converter={StaticResource ColorConverter}}">
<StackLayout>
<Label Margin="10"
Text="{Binding nombre}"
TextColor="{Binding TextColor, Converter = {StaticResource ColorConverter}}"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Center"
FontSize="Small"
VerticalOptions="CenterAndExpand">
</Label>
</StackLayout>
</Frame>
</ContentView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
You are using a CollectionView and when you set the ItemSource
<CollectionView ItemsSource="{Binding Grupos}"
All the bindings you do inside will assume this as the BindingContext.
<Label Margin="10"
Text="{Binding nombre}"
TextColor="{Binding TextColor, Converter = {StaticResource ColorConverter}}"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Center"
FontSize="Small"
VerticalOptions="CenterAndExpand" />
The same way the nombre property you are binding to the Label Text Property, is part of the GroupModel class, that way the TextColor and ButtonBackColor properties are expected to be part of the same class you did bind as the ItemSource.
If you want to make it work: either add these two properties (TextColor and ButtonBackColor) to the GroupModel class or change the binding so that these two properties are accessed from the parent Binding.
The first one will give you more flexibility but at the same time might add repeated code (if all the items will share the same color for example).
The second option can be accomplished this way:
Add a name to the CollectionView
<CollectionView ItemsSource="{Binding Grupos}"
HeightRequest="50"
x:Name="GruposList"
....
Then we are gonna change a bit the binding of those items that are not part of your GrupoModel but are part of the ViewModel
<DataTemplate>
<ContentView Padding="2">
<Frame BorderColor="Black"
HasShadow="False"
Padding="2"
BackgroundColor="{Binding BindingContext.ButtonBackColor,
Source={x:Reference GruposList},
Converter={StaticResource ColorConverter}}">
<StackLayout>
<Label Margin="10"
Text="{Binding nombre}"
TextColor="{Binding BindingContext.TextColor,
Source={x:Reference GruposList},
Converter={StaticResource ColorConverter}}"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Center"
FontSize="Small"
VerticalOptions="CenterAndExpand">
</Label>
</StackLayout>
</Frame>
</ContentView>
</DataTemplate>
As you can see we are now accessing them through the CollectionView Binding, we do this when we specify the Source and use a Reference. More about bindings here
Hope this helps.-
Side Note:
In your converter watch for nulls.
var valor = value.ToString();
The above can make your application crash if value is null.
Use this instead:
var valor = value?.ToString();

XAML ListSource binding from multiple objects

I have a XAML view in which I am binding to a ViewModel and an ObservableCollection (Games) of type GAME_TBL
<ListView x:Name="GameListView"
ItemsSource="{Binding Games}"
ItemTapped="Handle_ItemTapped"
CachingStrategy="RecycleElement"
RowHeight="120">
I am referencing properties of that GAME_TBL object like so
<Label Text="{Binding GAME_NAME}"
Style="{StaticResource GameListTitle}" />
However, I want to style the list rows and tried to bind to an object that is not a property of GAME_TBL
<BoxView Grid.Column="0"
Grid.Row="0"
Grid.ColumnSpan="5"
Grid.RowSpan="5"
BackgroundColor="{Binding BoxViewStyle}"/>
Code behind from same ViewModel
public Color BoxViewStyle
{
get { return _boxViewStyle; }
set
{
_boxViewStyle = value;
OnPropertyChanged(nameof(BoxViewStyle));
}
}
When the ViewModel is called I then set it like this
BoxViewStyle = Color.FromHex("#000000");
However it hasn't worked
I think it's something to do with me declaring the entire ListView to have an ItemSource which is the OS, but then trying to use an object outside of that without explicitly referencing it? Might be wrong about that.
The BindingContext for your list view is whatever data type Games is. Since the BoxViewStyle property lives in your ViewModel you can't bind to it from inside your ListView.ItemTemplate. You need to specify the source for your Binding.
Name your main ContentPage element. x:Name="mainElement"
When you set your BoxViewStyle binding specify the source:
<BoxView Grid.Column="0"
Grid.Row="0"
Grid.ColumnSpan="5"
Grid.RowSpan="5"
BackgroundColor="{Binding BoxViewStyle, Source={x:Reference mainElement}"/>
no that was just to get things working stage by stage - I wanted to know I could bind the color first, then I was going to write a method that would alternate the colours every row
If you want to have the same color for ListView row, you can create BoxViewStyle color property in ViewModel, as ottermatic said that BoxViewStyle property is in ViewModel, so you can not bind it for ListView datetemplate, so you name your listview as list firstly, find list's BindingContext.BoxViewStyle.
<ListView x:Name="list" ItemsSource="{Binding games}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding GAME_NAME}" />
<BoxView BackgroundColor="{Binding BindingContext.BoxViewStyle, Source={x:Reference list}}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
If you want to alternate the colors every ListView row, I suggest you can create BoxViewStyle in model, according to ListView row index to change color.
<ListView x:Name="list" ItemsSource="{Binding games}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding GAME_NAME}" />
<BoxView BackgroundColor="{Binding BoxViewStyle}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Model.cs:
public class Game
{
public int Index { get; set; }
public string GAME_NAME { get; set; }
public Color BoxViewStyle
{
get
{
if (Index % 2 == 0)
{
return Color.Red;
}
else
{
return Color.Blue;
}
}
}
}
ViewModel.cs:
public class GameViewModel
{
public ObservableCollection<Game> games { get; set; }
public GameViewModel()
{
games = new ObservableCollection<Game>()
{
new Game(){Index=0,GAME_NAME="game 1"},
new Game(){Index=1,GAME_NAME="game 2"},
new Game(){Index=2,GAME_NAME="game 3"},
new Game(){Index=3,GAME_NAME="game 4"},
new Game(){Index=4,GAME_NAME="game 5"}
};
}
}
If my reply solved your issue, please remember to mark my reply as answer, thanks.

how to set name for checkbox in ListView

i want set name for check box and use in code for post method for api
<ListView ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout BackgroundColor="#eee" Orientation="Vertical">
<StackLayout Orientation="Horizontal">
<controls:CheckBox DefaultText="{Binding Name}" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Since you had used MVVM . I suggest that you should handle all logic in your ViewModel .You can get the value and index of CheckBox in ViewModel.
I used the CheckBox plugin from https://github.com/enisn/Xamarin.Forms.InputKit .
in your xaml
<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"
xmlns:local="clr-namespace:App12"
xmlns:input="clr-namespace:Plugin.InputKit.Shared.Controls;assembly=Plugin.InputKit"
mc:Ignorable="d"
x:Name="contentPage" // set the name of content page
x:Class="xxx.MainPage">
<ListView x:Name="listview" ItemsSource="{Binding MyItems}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout BackgroundColor="#eee" Orientation="Vertical">
<StackLayout Orientation="Horizontal">
<input:CheckBox Text="{Binding Name}" Type="Check" IsChecked="{Binding IsCheck,Mode=TwoWay}" CheckChangedCommand="{Binding Source={x:Reference contentPage}, Path=BindingContext.CheckCommand}" CommandParameter="{Binding }"/>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
in your model
public class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string Name { get; set; }
private bool isCheck;
public bool IsCheck
{
get
{
return isCheck;
}
set
{
if (isCheck != value)
{
isCheck = value;
NotifyPropertyChanged();
}
}
}
}
in Viewmodel or Code behind
public ObservableCollection<Model> MyItems { get; set; }
public ICommand CheckCommand { get; private set; }
public YourViewModel()
{
MyItems = new ObservableCollection<Model>() {
new Model(){Name="xxx",IsCheck=true },
//...
};
CheckCommand = new Command((arg)=> {
var model = arg as Model;
for(int i=0;i<MyItems.Count;i++)
{
if (model == MyItems[i])
{
// i is the index that you checked
bool ischeck = MyItems[i].IsCheck;
// do some thing you want
}
}
});
}
I would suggest adding a Binding for the CheckBox State:
<controls:CheckBox x:Name="chechBox" DefaultText="{Binding Name}" IsChecked="{Binding IsChecked}" />
And then, in the ListView ItemTapped event:
void OnSelection (object sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem == null) {
return; //ItemSelected is called on deselection, which results in SelectedItem being set to null
}
var item = (YourModel)e.SelectedItem;
if(item != null)
{
var checkBoxState = item.IsChecked;
}
}

xamarin.forms listview populate by BindingContext and viewmodel

I'm new with Xamarin, but I'm coming from C# background.
I'm trying to set the items source of listview by passing viewmodel to the bindingcontext property. I know I can set the itemssoruce programatically in the code behind but I think setting it through the bindingcontext is the right way to do it, correct me if I'm wrong.
Let me start with what I have currently.
This is the viewmodel I have:
public class AirportSelectVM
{
public int AirportID { get; set; }
public string AirportICAO { get; set; }
public int VolumeGallons { get; set; }
}
In the code behind I'm doing this:
private void SetInitialListView()
{
ObservableCollection<AirportSelectVM> listAirport = new ObservableCollection<AirportSelectVM>();
AirportSelectVM firstAirport = new AirportSelectVM();
listAirport.Add(firstAirport);
BindingContext = listAirport;
}
And in the XAML I have:
<ContentPage.Content>
<StackLayout>
<Picker x:Name="pickerAircraft" ItemDisplayBinding="{Binding TailNumber}" SelectedItem="{Binding Id}" SelectedIndexChanged="PickerAircraft_SelectedIndexChanged" Title="Aircraft Selector"></Picker>
<ListView ItemsSource="{Binding listAirport}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="10,10,10,10">
<Label Text="Leg 1 Arrival" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
Just for comparison the picker items source is set in the code behind but eventually I would like to move that in the bindingcontext as well.
So, my main question will be how to setup the items source of a listview through bindingcontext?
you are setting the BindingContext of the Page to listAirport. So the ItemsSource will be the same as the page binding
<ListView ItemsSource="{Binding .}">
If you want to do it "the right way", you should learn more about the MVVM pattern.
For each page you are binding a page view model, which will be a bridge between your Models (data) and your UI.
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/data-bindings-to-mvvm
Now if you just want to have a working code, you need to set your ItemsSource directly like this:
<ContentPage.Content>
<StackLayout>
<Picker x:Name="pickerAircraft" ItemDisplayBinding="{Binding TailNumber}" SelectedItem="{Binding Id}" SelectedIndexChanged="PickerAircraft_SelectedIndexChanged" Title="Aircraft Selector"></Picker>
<ListView x:Name="AirportListView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="10,10,10,10">
<Label Text="Leg 1 Arrival" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
code-behind:
private void SetInitialListView()
{
ObservableCollection<AirportSelectVM> listAirport = new ObservableCollection<AirportSelectVM>();
AirportSelectVM firstAirport = new AirportSelectVM();
listAirport.Add(firstAirport);
AirportListView.ItemsSource = listAirport;
}

Adding a drop down area option on my list view

I’m wondering if you are able to add a drop down area option to a list view. For example when the user clicks on the specific row a drop down area appears below it showing various information and when they click it again it goes away.
Is this possible using xamarin forms ?
You cannot add a drop-down control inside a list view in Xamarin Forms. However, you could create a custom List View with custom Item Templates that contains a Stacklayout "hidden" from the user until the user taps on the row to re-enable the visibility of the "hidden" Stacklayout pushing down other rows. You may need to add an extra property to the ViewModel in order to control individual rows visibility.
If you check this C# corner guide it shows you how to come up with an Expandable ListView in Xamarin Forms.
You will have to make changes as per your requirement but your XAML would look something like this:
<ListView x:Name="HotelsList" BackgroundColor="White" IsGroupingEnabled="True" IsPullToRefreshEnabled="true" IsRefreshing="{Binding IsBusy, Mode=OneWay}" ItemsSource="{Binding Items}" RefreshCommand="{Binding LoadHotelsCommand}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" VerticalOptions="Center">
<Label VerticalOptions="Center" FontAttributes="Bold" FontSize="Medium" Text="{Binding .RoomName}" TextColor="Black" VerticalTextAlignment="Center" /> </StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Label FontAttributes="Bold" FontSize="Small" Text="{Binding Name}" TextColor="Gray" VerticalTextAlignment="Center" />
<Image x:Name="ImgA" Source="{Binding StateIcon}" Margin="0,0,5,0" HeightRequest="20" WidthRequest="20" HorizontalOptions="End" />
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={x:Reference currentPage}, Path=BindingContext.RefreshItemsCommand}" NumberOfTapsRequired="1" CommandParameter="{Binding .}" />
</Grid.GestureRecognizers>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
</ListView>
And your ViewModel as below:
public class HotelViewModel: ObservableRangeCollection < RoomViewModel > , INotifyPropertyChanged {
// It's a backup variable for storing CountryViewModel objects
private ObservableRangeCollection < RoomViewModel > hotelRooms = new ObservableRangeCollection < RoomViewModel > ();
public HotelViewModel(Hotel hotel, bool expanded = false) {
Hotel = hotel;
_expanded = expanded;
foreach(Room room in hotel.Rooms) {
Add(new RoomViewModel(room));
}
if (expanded) AddRange(hotelRooms);
}
public HotelViewModel() {}
private bool _expanded;
public bool Expanded {
get {
return _expanded;
}
set {
if (_expanded != value) {
_expanded = value;
OnPropertyChanged(new PropertyChangedEventArgs("Expanded"));
OnPropertyChanged(new PropertyChangedEventArgs("StateIcon"));
if (_expanded) {
AddRange(hotelRooms);
} else {
Clear();
}
}
}
}
public string StateIcon {
get {
if (Expanded) {
return "arrow_a.png";
} else {
return "arrow_b.png";
}
}
}
public string Name {
get {
return Hotel.Name;
}
}
public Hotel Hotel {
get;
set;
}
}
Check the above guide where the blogger and flawlessly explained all the aspects related to the use of this
Goodluck,
Revert in case of queries.

Resources