XAML ListSource binding from multiple objects - xamarin

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.

Related

Binding Listview Items to multiple Models

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 .

Any idea how to add CarouselView indicators inside the CarouselView it self?

I am using a CarouselView to display a few views
each one of them is basically 2 StackLayouts
like so
what I have done so far is create a CarouselView with a template for both of these Stacklayouts
<CarouselView
x:Name="MyCarouselView"
IsVisible="{Binding ShouldShow}"
ItemsSource="{Binding MyItemSource}"
Scrolled="CarouselView_Scrolled">
<CarouselView.ItemTemplate>
<DataTemplate>
<ContentView>
<FlexLayout Direction="Column" JustifyContent="SpaceBetween">
<StackLayout FlexLayout.Basis="25%">
<elements:TopView Data="{Binding}"/>
</StackLayout>
<StackLayout FlexLayout.Basis="75%">
<elements:BottomView Data="{Binding}"/>
</StackLayout>
</FlexLayout>
</ContentView>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
Note: the views bind to different values but for simplicity sake, I wrote a simpler DataTemplate here.
it works fine and there is no problem in binding or anything
what I am trying to do is add indicators to show the user that there are more items and which one he is seeing now. (small circles or something like that).
the thing is I want to display them between the two views like so
Any idea of how to do it?
Any other way to achieve the wanted result is welcome as well
Please and thank you.
The easy way to implement that is using Grid to do that :
<Grid>
<!-- Place new controls here -->
<CarouselView x:Name="CustomCarouselView"
IndicatorView="indicatorView"
VerticalOptions="FillAndExpand"
>
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<ContentView BackgroundColor="LightBlue" >
<Label Text="{Binding TopViewTitle}" FontSize="Header" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" HeightRequest="250"/>
</ContentView>
<ContentView BackgroundColor="LightGray" Margin="0,80,0,0">
<Label Text="{Binding BottomViewTitle}" FontSize="Header" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" HeightRequest="250" />
</ContentView>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
<IndicatorView x:Name="indicatorView"
IndicatorsShape="Square"
IndicatorColor="LightGray"
SelectedIndicatorColor="DarkGray"
VerticalOptions="Center"
Margin="0,-150,0,0" />
</Grid>
Above is my sample xaml code ,only need to custom the Margin property of IndicatorView and bottom ContentView after binding ItemSource for CarouselView :
List<CarouselModel> carouselModels = new List<CarouselModel>();
carouselModels.Add(new CarouselModel { TopViewTitle = "first top", BottomViewTitle = "first bottom" });
carouselModels.Add(new CarouselModel { TopViewTitle = "second top", BottomViewTitle = "second bottom" });
carouselModels.Add(new CarouselModel { TopViewTitle = "third top", BottomViewTitle = "third bottom" });
CustomCarouselView.ItemsSource = carouselModels;
The effect shows as expected :
The another way you can use RelativeLayout to implement that :
<RelativeLayout>
<!-- Place new controls here -->
<CarouselView x:Name="CustomCarouselView"
IndicatorView="indicatorView"
>
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<ContentView BackgroundColor="LightBlue">
<Label Text="{Binding TopViewTitle}"
FontSize="Header"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
HeightRequest="250" />
</ContentView>
<ContentView BackgroundColor="LightGray"
Margin="0,80,0,0">
<Label Text="{Binding BottomViewTitle}"
FontSize="Header"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
HeightRequest="250" />
</ContentView>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
<IndicatorView x:Name="indicatorView"
IndicatorsShape="Square"
IndicatorColor="LightGray"
SelectedIndicatorColor="DarkGray"
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent,Property=Width,Factor=0.5,Constant=-25}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent,Property=Height,Factor=0.5,Constant=-75}"
/>
</RelativeLayout>
Here you can modify RelativeLayout.XConstraint and RelativeLayout.YConstraint of IndicatorView to set the space between two ContentView . The Margin property
of Bottom ContentView also need to set , it's effect is to show the space between two ConentView .
You can achieve this in two methods :-
Grid, where you can place both the controls in the same Grid.Row.
Assign VerticalOptions="FillAndExpand" to the CarouselView and VerticalOptions="Center" to the IndicatorView.
By Using AbsoluteLayout.
Let me know if you have more queries.
We can achieve this scenario by placing a carousel view inside the parent carousel view and providing the Indicator view for the carousel placed inside.
Also we need to bind the "Position" property of inside carousel view properly, so that indicator view points to the proper selected index. Please find the code below for reference,
Inside Xaml:
<StackLayout Padding="30">
<CarouselView x:Name="MyCarouselView"
ItemsSource="{Binding MyItemSource}"
FlowDirection="MatchParent">
<CarouselView.ItemTemplate>
<DataTemplate>
<ContentView>
<FlexLayout Direction="Column"
JustifyContent="SpaceBetween"
BackgroundColor="Pink">
<StackLayout FlexLayout.Basis="5%">
<BoxView HeightRequest="100"
WidthRequest="100"
BackgroundColor="{Binding Boxview1Color}" />
</StackLayout>
<CarouselView FlexLayout.Basis="0%"
IndicatorView="indicatorView"
IsSwipeEnabled="False"
ItemsSource="{Binding Items}"
Position="{Binding Position}">
<CarouselView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding ItemText}"/>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
<IndicatorView x:Name="indicatorView"
FlexLayout.Basis="10%"
HeightRequest="10"
IndicatorsShape="Circle"
IndicatorColor="White"
SelectedIndicatorColor="DarkBlue">
</IndicatorView>
<StackLayout FlexLayout.Basis="60%">
<BoxView HeightRequest="100"
WidthRequest="100"
BackgroundColor="{Binding Boxview2Color}"/>
</StackLayout>
</FlexLayout>
</ContentView>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
</StackLayout>
Inside ViewModel:
public class MainPageViewModel
{
private ObservableCollection<MyItemSourceViewModel> _myItemSource;
public ObservableCollection<MyItemSourceViewModel> MyItemSource { get; set; }
public MainPageViewModel()
{
MyItemSource = new ObservableCollection<MyItemSourceViewModel>();
MyItemSource.Add(new MyItemSourceViewModel { Boxview1Color = Color.Red, Boxview2Color = Color.Blue, Position = 0 });
MyItemSource.Add(new MyItemSourceViewModel { Boxview1Color = Color.Green, Boxview2Color = Color.Purple, Position = 1});
MyItemSource.Add(new MyItemSourceViewModel { Boxview1Color = Color.PeachPuff, Boxview2Color = Color.Brown, Position = 2 });
var count = MyItemSource.Count;
foreach(var item in MyItemSource)
{
var itemCount = count;
var i = 0;
item.Items = new List<ItemsModel>();
while(itemCount > 0)
{
var itemModel = new ItemsModel { ItemText = i.ToString()};
item.Items.Add(itemModel);
i++;
itemCount--;
}
}
}
}
public class MyItemSourceViewModel
{
public Color Boxview1Color { get; set; }
public Color Boxview2Color { get; set; }
public int Position { get; set; }
public List<ItemsModel> Items { get; set; }
}
public class ItemsModel
{
public string ItemText { get; set; }
}
Please refer below screenshots as well:
Sample screenshot 1
Sample screenshot 2

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.

FindViewById on Xamarin Forms?

I need some way to find View/object by its ID. I heared about FindViewById function, but it's not present in my ContentPage class. Where can I find it?
Context: I have ListView with buttons inside. I don't know how many buttons are there. When user clicks on one of those buttons, I get its ID and store globally. What I want to accomplish is to find this specific button, which id was stored in variable.
<StackLayout x:Name="chooseObjectButtons">
<ListView x:Name="SlotsList" ItemsSource="{Binding .}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout>
<Button Text="{Binding Text}" BackgroundColor="Gray" Clicked="SlotChosen" />
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Change the XAML to:
<ListView ItemsSource="{Binding Slots}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout>
<Button Text="{Binding Title}" BackgroundColor="Gray" Clicked="Handle_Clicked" Command="{Binding Select}" />
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Handle click:
private Button LastButtonClicked;
void Handle_Clicked(object sender, System.EventArgs e)
{
if (LastButtonClicked != null)
{
// Change background here
}
LastButtonClicked = (Button)sender;
// Do stuff here.
}
To process the specific command for each button use:
public List<SlotsButtons> Slots
{
get
{
return new List<SlotsButtons>
{
new SlotsButtons
{
Title = "T1",
Select = new Command(()=>{
// do stuff here when clicked.
})
},
new SlotsButtons
{
Title = "T2",
Select = new Command(()=>{
// do stuff here when clicked.
})
}
};
}
}
NOTE: Initial question answer.
In Xamarin Forms the class ContentPage is a Partial class.
One part is automatically generated from XAML and the other represents the code behind.
The XAML generated Partial class has the code to find the views by name.
The correct name is FindByName and you should't need to use this in your partial class because it its already made in the generated partial class.
If you want to access a view in your code behind just give it a name in XAML.
There is an XAML example:
<Button x:Name="button" ></Button>
And in your code behind you could do something like:
button.BorderWidth = 3;
If you still need to find a view for some reason, do this:
var button = this.FindByName<Button>("button");

Resources