any suggestions for how to implement expandable list view with different child views in Xamarin forms. Can anyone please help me for this?
To use different template for different cells you want to use DataTemplateSelector, it's documented here: Creating a Xamarin.Forms DataTemplateSelector
First define it in separate class:
public class PersonDataTemplateSelector : DataTemplateSelector
{
public DataTemplate ValidTemplate { get; set; }
public DataTemplate InvalidTemplate { get; set; }
protected override DataTemplate OnSelectTemplate (object item, BindableObject container)
{
return ((Person)item).DateOfBirth.Year >= 1980 ? ValidTemplate : InvalidTemplate;
}
}
Then add it to your page's resources:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:Selector;assembly=Selector" x:Class="Selector.HomePage">
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="validPersonTemplate">
<ViewCell>
...
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="invalidPersonTemplate">
<ViewCell>
...
</ViewCell>
</DataTemplate>
<local:PersonDataTemplateSelector x:Key="personDataTemplateSelector"
ValidTemplate="{StaticResource validPersonTemplate}"
InvalidTemplate="{StaticResource invalidPersonTemplate}" />
</ResourceDictionary>
</ContentPage.Resources>
...
</ContentPage>
And then just use it in your list:
<ListView x:Name="listView" ItemTemplate="{StaticResource personDataTemplateSelector}" />
To have a possibility to expand/hide the cells, you need to:
add a property IsExpanded to the ViewModel of specific list item
change it to true/false on the ItemSelected event of your list
bind the visibility of the view you want to hide/expand to the value of IsExpanded
XAML
<ListView ItemsSource="{Binding YOUR_SOURCE}" SeparatorVisibility="Default"
HasUnevenRows="True" ItemSelected="MyList_ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame>
<StackLayout>
<Label Text="My Heading"/>
</StackLayout>
<StackLayout x:Name="moreItemStack" Orientation="Horizontal"
IsVisible="false">
<Label Text="child 1"/>
<Label Text="child 2"/>
</StackLayout>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView>
C#
private void MyList_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var myItem = e.SelectedItem;
moreItemStack.IsVisible = true;
}
#user3932639 There you go
NOTE: This was written for clarification, it has been tested.
Related
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.
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;
}
To customize the appearance of an item in the CollectionView, I'm using a DataTemplateSelector, but unlike what happens in the ListView data binding doesn't seem to work for ItemTemplate in the CollectionView.
I have already tested my solution with a ListView in place of the CollectionView and it works.
Everything works fine also if I replace the resource ItemTemplate binding with the inline ItemTemplate.
I'm trying to customize different objects which inherit from ISensor. It looks like
public interface ISensor
{
string Name { get; set; }
// ...
}
This is the view model:
public class TestViewModel : MvxNavigationViewModel
{
private static ObservableCollection<ISensor> _SensorList;
public TestViewModel(IMvxLogProvider logProvider, IMvxNavigationService navigation)
: base(logProvider, navigation)
{
_SensorList = new ObservableCollection<ISensor> { new Sensor("Thermostat"), null };
}
public ObservableCollection<ISensor> SensorsSource { get => _SensorList; }
}
And this is the page containing the CollectionView:
<ContentView>
<StackLayout>
<CollectionView ItemsSource="{Binding SensorsSource}" ItemTemplate="{StaticResource SensorsTemplate}" Margin="10">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" Span="2"/>
</CollectionView.ItemsLayout>
<CollectionView.EmptyView>
<StackLayout>
<Label Text="Add a new sensor"/>
</StackLayout>
</CollectionView.EmptyView>
</CollectionView>
</StackLayout>
</ContentView>
The DataTemplateSelector:
public class SensorSelector : DataTemplateSelector
{
public DataTemplate EmptyTemplate { get; set; }
public DataTemplate SensorTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
if (item is Sensor)
return SensorTemplate;
return EmptyTemplate;
}
}
The DataTemplate present in the App.xaml file:
<DataTemplate x:Key="EmptyDataTemplate">
<ViewCell>
<StackLayout BackgroundColor="Yellow">
<Label Text="EMPTY"/>
</StackLayout>
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="SensorDataTemplate">
<ViewCell>
<StackLayout BackgroundColor="Red">
<Label Text="{Binding Name}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
<Selectors:SensorSelector x:Key="SensorsTemplate"
EmptyTemplate="{StaticResource EmptyDataTemplate}"
SensorTemplate="{StaticResource SensorDataTemplate}"/>
Every time I enter in the TestViewModel the app crash. Is it a binding bug or am I doing something wrong?
With ListView, the child of the DataTemplate used as the ItemTemplate must be a ViewCell (or derive from it). With CollectionView, you cannot have the ViewCell in place. In other words, you can't have a DataTemplate that works with both ListView and CollectionView simultaneously. Removing the ViewCell layer in your DataTemplates should fix the crash when using with CollectionView:
<DataTemplate x:Key="EmptyDataTemplate">
<StackLayout BackgroundColor="Yellow">
<Label Text="EMPTY"/>
</StackLayout>
</DataTemplate>
And the equivalent change for the SensorDataTemplate.
I use the same XAML many times in my application with two minor differences which are the value of the Text and Selected that I pass in:
<ViewCell Tapped="selectValue" >
<Grid VerticalOptions="CenterAndExpand" Padding="20,0" >
<local:StyledLabel Text="{Binding [1].Name}" HorizontalOptions="StartAndExpand" />
<local:StyledLabel IsVisible="{Binding [1].IsSelected}" TextColor="Gray" HorizontalOptions="End" Text="✓" />
</Grid>
</ViewCell>
Does Xamarin forms have any template feature where I could for example shorten this to something like:
<local:SwitchViewCell Text="{Binding [1].Name}" Selected="{Binding [1].IsSelected}" />
Here's what I have so far:
<?xml version="1.0" encoding="utf-8" ?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
mlns:local="clr-namespace:Japanese;assembly=Japanese"
x:Class="Japanese.SwitchViewCell""
Tapped="selectValue" >
<Grid VerticalOptions="CenterAndExpand" Padding="20,0" >
<local:StyledLabel Text="{Binding Text, Source={x:Reference this}}" HorizontalOptions="StartAndExpand" />
<local:StyledLabel IsVisible="{Binding IsVisible, Source={x:Reference this}}" TextColor="Gray" HorizontalOptions="End" Text="✓" />
</Grid>
</ViewCell>
With this code behind right now:
namespace Japanese.Templates
{
public partial class SwitchViewCell : ViewCell
{
public SwitchViewCell()
{
InitializeComponent();
}
public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(SwitchViewCell));
public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(SwitchViewCell));
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
public bool IsVisible
{
get
{
return (bool)GetValue(IsVisibleProperty);
}
set
{
SetValue(IsVisibleProperty, value);
}
}
}
}
I'm not sure if this is 100% the way to go but when I try to implement this I get the message:
EventHandler "selectValue" not found in type "Japanese.Templates.SwitchViewCell" (Japanese)
ListView Cell:
For the ListView cell, you can define the ViewCell layout of ListView item.
Ex. PersonCell.xaml
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataTemplates.PersonCell">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*" />
<ColumnDefinition Width="0.3*" />
<ColumnDefinition Width="0.4*" />
</Grid.ColumnDefinitions>
<Label Text="{Binding FirstName}" />
<Label Grid.Column="1" Text="{Binding LastName}" />
<Label Grid.Column="2" Text="{Binding Email}" />
</Grid>
</ViewCell>
Then you can use this into ListView's DataTemplate as:
<ListView ItemSource="{Binding PersonList}">
<ListView.ItemTemplate>
<DataTemplate>
<local:PersonCell />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This way ListView resuses the cell design for each item.
Reusable View:
You can also make a reusable view which can be simply included in your page.
Ex. MyCustomView.xaml:
<Grid xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Xam.Control.MyCustomView">
<StackLayout>
<Label Text="Hello"/>
<Label Text="How Are You?"/>
</StackLayout>
</Grid>
Page:
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:customView="clr-namespace:Xam.Control;assembly=Xam"
x:Class="Xam.View.HomePage">
Here, notice that you have to include the namespace and assembly where your custom view resides.
And then in this page, simply add it like:
<customView:MyCustomView />
Sure, just put it in a separate XAML file and define a the bindable properties that you need and map the values onto the controls in your custom control. In fact, the latter is not absolutely needed but is nicer to make it completely reusable.
If you just want to reuse it in your project and always data bind it to the same fields, you can leave it as-is, as the BindingContext will be inherited.
To get you started, you might want to have a look at a blog post of mine about this: https://blog.verslu.is/xamarin/xamarin-forms-xamarin/reusable-custom-usercontrols-with-bindableproperty/
I've got a user control that I'm including in a ListView and I want to add dynamic content when the user control initialises based on the listitem binding. I'm not sure how to do this. See the "HOW DO I BIND THIS???"... I presume I should binding my PropertyBinding to the listitem somehow?
Here's my original view with my listview
<?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:local="clr-namespace:EngineerApp" x:Class="EngineerApp.GigsPage" NavigationPage.HasNavigationBar="false" xmlns:controls="clr-namespace:XLabs.Forms.Controls;assembly=XLabs.Forms">
<ContentPage Title="Gigs" Icon="icon.png">
<StackLayout Orientation="Vertical">
<local:HeaderBar LeftButtonText="Back" RightButtonText="Leave" LeftButtonClickEvent="Back" RightButtonClickEvent="Back"></local:HeaderBar>
<local:ButtonBar LeftButtonText="Add Gig" RightButtonText="Month View" LeftButtonClickEvent="AddGig" RightButtonClickEvent="Back"></local:ButtonBar>
<ActivityIndicator IsRunning="{Binding IsBusy}" IsVisible="{Binding IsBusy}"></ActivityIndicator>
<ListView HasUnevenRows="true" SeparatorVisibility="Default" ItemsSource="{Binding gigs}" ItemSelected="Handle_ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame Padding="20,20,20,20">
<Frame.Content>
<Frame Padding="15,15,15,15" OutlineColor="Gray" BackgroundColor="White">
<Frame.Content>
<StackLayout Padding="20,0,0,0" Orientation="Vertical" HorizontalOptions="CenterAndExpand">
<Label Text="{Binding venue}"
HorizontalTextAlignment="Center"
TextColor="#69add1"/>
<Label Text="{Binding date}"
HorizontalTextAlignment="Center"
FontFamily="OpenSans-Light"
FontSize="9"
TextColor="#69add1"/>
<local:AuthorisationBar SelectedGig="{Binding .}"></local:AuthorisationBar>
</StackLayout>
</Frame.Content>
</Frame>
</Frame.Content>
</Frame>
</ViewCell>
<local:GigCard />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
</TabbedPage>
And then here's the user control xaml.
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="EngineerApp.AuthorisationBar">
<ContentView.Content>
<StackLayout BackgroundColor="Red" HeightRequest="50" x:Name="barcontent" Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<Label HorizontalOptions="FillAndExpand" VerticalOptions="Center" Text="I want to show some data here once the bindings are working"></Label>
</StackLayout>
</ContentView.Content>
</ContentView>
And here's the code behind for the user control
using System;
using System.Collections.Generic;
using EngineerApp.ViewModels;
using Xamarin.Forms;
namespace EngineerApp
{
public partial class AuthorisationBar : ContentView
{
public static readonly BindableProperty GigProperty = BindableProperty.Create("SelectedGig", typeof(GigViewModel), typeof(AuthorisationBar), new GigViewModel());
public GigViewModel SelectedGig
{
get
{
return (GigViewModel)GetValue(GigProperty);
}
set
{
SetValue(GigProperty, value);
}
}
public AuthorisationBar()
{
InitializeComponent();
BindingContext = this;
}
}
}
UPDATE 1 - Updated all pages to reflect most recent suggestion. With the {Binding .} I now get the error stated below:
"Cannot assign property "SelectedGig": Property does not exists, or is not assignable, or mismatching type between value and property"
Try this:
<local:AuthorisationBar SelectedGig="{Binding .}"></local:AuthorisationBar>
where "." will be the "current" item of the list.
Notice I used SelectedGid in my code as this is the name of the property you defined your custom control, not SelectedGigId.
Also you need to remove this line from the constructor of your custom control:
BindingContext = SelectedGig;
The BindingContext will already be of type GigViewModel
Hope this helps!
UPDATE
Your Custom Control code behind should look:
public partial class AuthorisationBar : ContentView
{
public AuthorisationBar()
{
InitializeComponent();
}
public static readonly BindableProperty SelectedGigProperty = BindableProperty.Create(nameof(SelectedGig), typeof(GigViewModel), typeof(AuthorisationBar), null);
public GigViewModel SelectedGig
{
get
{
return (GigViewModel)GetValue(SelectedGigProperty);
}
set
{
SetValue(SelectedGigProperty, value);
}
}
}
As previously stated you don't need to set anything to the BindingContext
UPDATE 2
In response to your question. The error was that the BindableProperty of your custom control was not complying with the naming conventions requirements.
From Xamarin BindableProperty documentation:
The naming convention for bindable properties is that the bindable property identifier must match the property name specified in the Create method, with "Property" appended to it.
This is why updating your BindableProperty from GigProperty to SelectedGigProperty fixed your issue.
I've done something similar for my current project - passing the current item's ID to my ViewModel (from the code you've posted, it seems you're not using proper separation of concerns under the MVVM paradigm - the VM shouldn't be initialising anything in your view):
<Button Text="Reconnect" Command="{Binding Path=BindingContext.ReconnectCommand, Source={x:Reference Name=ServicesList}}" CommandParameter="{Binding}" VerticalOptions="Center"/>
For reference, the button which is passing the command is embedded in a ListView.DataTemplate, and is passing the ID of the list item it's part of