Xamarin Forms Databinding "." separator - data-binding

I'm struggling with databinding in Xamarin Forms. Here's why, what I expect to happen from the following XAML statement:
IsVisible="{Binding Path=UserContext.IsLoggedOut}"
Is - That the property is bound to a child object of the View Model. Now either this isn't supported in Xamarin Forms or I am missing a trick.
If this is not supported in Xamarin, where it is supported in WPF, then what are we supposed to do in order to propagate nested objects? Flattening the View Model is making me write reams and reams of inelegant code.

Nested properties are supported, like other pretty complex expression as well:
You can test it:
Xaml
<StackLayout Spacing="20">
<StackLayout Orientation="Horizontal">
<Label Text="Subproperties: " HorizontalOptions="FillAndExpand" FontSize="15"></Label>
<Label Text="{Binding Item.SubItem.Text}" HorizontalOptions="FillAndExpand" FontSize="15"></Label>
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Indexer: " HorizontalOptions="FillAndExpand" FontSize="15"></Label>
<Label Text="{Binding Item.Dictionary[key].Text}" HorizontalOptions="FillAndExpand" FontSize="15"></Label>
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Array Indexer: " HorizontalOptions="FillAndExpand" FontSize="15"></Label>
<Label Text="{Binding Item.Array[1].Text}" HorizontalOptions="FillAndExpand" FontSize="15"></Label>
</StackLayout>
</StackLayout>
Page
public partial class Page2 : ContentPage
{
public ItemModel Item { get; }
public Page2()
{
InitializeComponent();
Item = new ItemModel();
BindingContext = this;
}
}
public class ItemModel
{
public ItemSubModel SubItem { get; set; }
public Dictionary<string, ItemSubModel> Dictionary { get; set; }
public ItemSubModel[] Array { get; set; }
public ItemModel()
{
SubItem = new ItemSubModel();
Dictionary = new Dictionary<string, ItemSubModel>
{
{"key", new ItemSubModel()}
};
Array = new [] {new ItemSubModel(), new ItemSubModel() };
}
}
public class ItemSubModel
{
public string Text { get; set; } = "Supported";
}
Result

I'm assuming you're trying in Xaml. Try removing 'Path'.
IsVisible="{Binding UserContext.IsLoggedOut}"
However more importantly, what is your BindingContext? For the above code to work you would need the BindingContext to be set to class Foo, which has a property called UserContext, which itself has a property IsLoggedOut.
Have a look here as well

Related

Member not found in data context

I am trying to bind the ImageSource property of the NewGames class to the Source property of an Image control in a CarouselView but i keep getting the same error. Here is my code.
New Game Class
namespace FYP.ViewModels
{
public class NewGames
{
public int Id { get; set; }
public string GameTitle { get; set; }
public double Rating { get; set; }
public string ImageSource { set; get; }
}
}
This is my view model
using System.ComponentModel;
using System.Text;
namespace FYP.ViewModels
{
public class NewReleasesViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<NewGames> NewGames;
public ObservableCollection<NewGames> Games
{
get { return NewGames; }
set { NewGames = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Games"));
}
}
public NewReleasesViewModel()
{
Games = new ObservableCollection<NewGames>();
AddData();
}
private void AddData()
{
Games.Add(new NewGames
{
Id = 0,
GameTitle = "The Elder Scrolls Online",
Rating = 4.9,
ImageSource= "https://assets-prd.ignimgs.com/2022/01/05/elderscrollsonline- 1641413357689.jpg"
});
Games.Add(new NewGames
{
Id = 1,
GameTitle = "World Of Warcraft",
Rating = 4.9,
ImageSource = "https://assets-prd.ignimgs.com/2021/12/10/wow-1639126324635.jpg"
});
Games.Add(new NewGames
{
Id = 2,
GameTitle = "Star Wars: The Old Republic",
Rating = 4.9,
ImageSource = "https://assets-prd.ignimgs.com/2022/01/27/swotor-sq1-1643302998212.jpg"
});
}
}
}
And this is where i am trying to bind it to
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Frame HeightRequest="300"
WidthRequest="180"
BackgroundColor="white"
Padding="0"
CornerRadius="10"
HasShadow="True"
Margin="15"
HorizontalOptions="CenterAndExpand">
<Grid>
<StackLayout BackgroundColor="DimGray">
<Image Source="{Binding ImageSource}" Aspect="AspectFill"></Image>
</StackLayout>
<StackLayout Margin="-5">
<Label Text="{Binding GameTitle}"
TextColor="PaleGoldenrod"
FontSize="18"
FontAttributes="Bold"
Margin="15"
VerticalOptions="EndAndExpand"/>
</StackLayout>
</Grid>
</Frame>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
It seems i am binding the NewGames class correcly because the carouselView is getting populated but for some reason none of the properties of the class go through and i don't know why. Hopefully this helps you guys understand was i am trying to do.
You're using compiled bindings when you're using the x:DataType property.
In order to make your bindings work like you expect, you need to explicitly set the x:DataType whenever the context changes. This is usually the case when you're using a DataTemplate.
In your case this would be something like this:
<CarouselView ItemsSource={Binding Games}>
<CarouselView.ItemTemplate>
<DataTemplate
x:DataType="local:NewGames">
<StackLayout>
<!-- ... -->
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
Where it says "local:NewGames" you need to put the correct namespace and class name.

Binding StackLayout to ViewModel doens't work

I would like to have a property in my ViewModel that is linked to my StackLayout. I tried this by Binding my StackLyout to the ViewModel.
When I click on a button, this layout should be made invisible.
When I do this with the code below, my program crashes with a NulReferenceObject: Object Reference not set to an instance of an object. The StackLayout that i am talking about is the first one in the code below.
<FlexLayout>
<StackLayout BindableLayout.ItemTemplate="{Binding CreateQuizPageQuizNameSL}"> // This StackLayout should be bind to the ViewModel
<Label Text="Create New Quiz" />
<StackLayout >
<Entry Text="{Binding QuizNameInput}" Placeholder="Enter quiz name"/>
</StackLayout>
</StackLayout>
<Button Command="{Binding SubmitCreateQuizCommand}" Text="Create my quiz now!"></Button>
</FlexLayout>
ViewModel
internal class CreateQuizPageViewModel
{
// Quiz Name Input
public String QuizNameInput { get; set; }
// Command submit creating a quiz
public Command SubmitCreateQuizCommand { get; set; }
public StackLayout CreateQuizPageQuizNameSL { get; set; } = new StackLayout();
public CreateQuizPageViewModel()
{
// Declaring a new command, giving the OnSubmitCreateNewQuizClick to the delegate
SubmitCreateQuizCommand = new Command(OnSubmitCreateNewQuizClick);
}
// When a user submit the creation of new quiz
public void OnSubmitCreateNewQuizClick()
{
CreateQuizPageQuizNameSL.IsVisible = false;
}
}
Here is how to switch two layouts using IsVisible binding.
FIRST Add Nuget Xamarin.CommunityToolkit to your Xamarin Forms project. (The one that is "MyProjectName", without ".iOS" or ".Android" at end.)
TwoLayoutPage.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:xct="http://xamarin.com/schemas/2020/toolkit"
xmlns:local="clr-namespace:TestBugs"
x:Class="TestBugs.TwoLayoutPage">
<ContentPage.BindingContext>
<local:TwoLayoutViewModel/>
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<xct:InvertedBoolConverter x:Key="InvertedBoolConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout>
<StackLayout
IsVisible="{Binding UseSecondLayout, Converter={StaticResource InvertedBoolConverter}}"
VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
<Label Text="First Layout" FontSize="20" />
<Button Text="To Second" Command="{Binding SwitchToSecondLayoutCommand}" />
</StackLayout>
<StackLayout IsVisible="{Binding UseSecondLayout}"
VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
<Label Text="Second Layout!" FontSize="32" />
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
TwoLayoutViewModel.cs:
using Xamarin.Forms;
namespace TestBugs
{
public class TwoLayoutViewModel : BindableObject
{
private bool _usesecondLayout = false;
public bool UseSecondLayout {
get => _usesecondLayout;
set {
_usesecondLayout = value;
OnPropertyChanged();
}
}
public TwoLayoutViewModel()
{
SwitchToSecondLayoutCommand = new Command(SwitchToSecondLayout);
}
public Command SwitchToSecondLayoutCommand { get; set; }
private void SwitchToSecondLayout()
{
UseSecondLayout = true;
}
}
}

Adding data to CarouselView item source results in error

I have a Xamarin CarouselView initialized with an empty ObservableCollection. The Carousel view correctly shows the EmptyView on page load.
However, when I add an item to the Observable Collection iOS throws the following exception:
attempt to insert item 0 into section 0, but there are only 0 items in section 0 after the update
The way I'm populating the item source is after the page loads I've hooked a button to an event handler to add items to the Observable Collection.
If I initialize the Observable Collection with initial data then the CarouselView works fine, it's just when I add items later that it breaks.
Page:
...
<ContentPage.BindingContext>
<viewModel:MatchesPageViewModel></viewModel:MatchesPageViewModel>
</ContentPage.BindingContext>
<CarouselView Margin="-10,15,-10,0"
ItemsSource="{Binding PendingMatches}"
HorizontalOptions="FillAndExpand"
HorizontalScrollBarVisibility="Never"
IsSwipeEnabled="True"
VerticalOptions="StartAndExpand">
<CarouselView.EmptyView>
<Frame>
<Label Text="It's empty"></Label>
</Frame>
</CarouselView.EmptyView>
<CarouselView.ItemTemplate>
<DataTemplate>
<Label Text="Some Content"></Label>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
View Model:
public class MatchesPageViewModel : BaseViewModel
{
public ObservableCollection<MatchResponse> PendingMatches { get; set; } =
new ObservableCollection<MatchResponse>();
//pretend it's invoked from a button in the page
public void SomeEventHandler()
{
//throws exception: attempt to insert item 0 into section 0...
PendingMatches.Add(new MatchResponse());
}
}
I guess that you may have some problem about adding item in observablecollection for Button click method. I do one demo using Button command binding that you can take a look.
<CarouselView
HorizontalOptions="FillAndExpand"
HorizontalScrollBarVisibility="Never"
IsSwipeEnabled="True"
ItemsSource="{Binding PendingMatches}"
VerticalOptions="StartAndExpand">
<CarouselView.EmptyView>
<Frame>
<Label Text="It's empty" />
</Frame>
</CarouselView.EmptyView>
<CarouselView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding str}" />
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
<Button
x:Name="btn1"
Command="{Binding command1}"
Text="add data" />
public partial class Page2 : ContentPage
{
public Page2()
{
InitializeComponent();
this.BindingContext = new MatchesPageViewModel();
}
}
public class MatchesPageViewModel
{
public ObservableCollection<MatchResponse> PendingMatches { get; set; } = new ObservableCollection<MatchResponse>();
public ICommand command1 { get; set; }
public MatchesPageViewModel()
{
command1 = new Command(SomeEventHandler);
}
public void SomeEventHandler()
{
//throws exception: attempt to insert item 0 into section 0...
PendingMatches.Add(new MatchResponse() { str = "test" });
}
}
public class MatchResponse
{
public string str { get; set; }
}

Xamarin Forms CollectionView Command not working

I have a collection view with the command binded, but for some reason when I select an item the action is never called in the viewmodel, heres my ViewModel code:
public class PlatillosViewModel : INotifyPropertyChanged
{
private INavigation Navigation;
public event PropertyChangedEventHandler PropertyChanged;
public List<PlatilloModel> Platillos { get; set; }
public List<GrupoModel> Grupos { get; set; }
public ICommand SelectedGroupCommand => new Command(SelectedGroup);
public PlatillosViewModel(INavigation navigation)
{
Navigation = navigation;
PlatillosRepository repository = new PlatillosRepository();
Platillos = repository.GetAll().ToList();
GrupoRepository grupoRepository = new GrupoRepository();
Grupos = grupoRepository.GetAll().ToList();
}
public ICommand SelectedPlatilloCommand => new Command<PlatilloModel>(async platillo =>
{
await Navigation.PushAsync(new PlatilloView());
});
void SelectedGroup()
{
PlatillosRepository platillosRepository = new PlatillosRepository();
//Platillos = platillosRepository.GetFilteredByGroup(grupoSeleccionado);
}
protected virtual void OnPropertyChanged(string property = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
And here is my Page:
<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.BehaviorsPack">
<ContentPage.Content>
<StackLayout>
<SearchBar> </SearchBar>
<StackLayout Orientation="Horizontal">
<CollectionView ItemsSource="{Binding Grupos}"
HeightRequest="50"
ItemsLayout="HorizontalList"
SelectionMode="Single"
SelectedItem="{Binding SelectedGroupCommand, Mode=TwoWay}">
<CollectionView.ItemTemplate>
<DataTemplate>
<ContentView>
<Label Margin="2"
BackgroundColor="Black"
Text="{Binding nombre}"
TextColor="White"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Center"
FontSize="Small"></Label>
</ContentView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
<ListView Grid.Column="2"
HasUnevenRows="True"
SeparatorVisibility="None"
ItemsSource="{Binding Platillos}">
<ListView.Behaviors>
<behaviorsPack:SelectedItemBehavior Command="{Binding SelectedPlatilloCommand}"/>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView Padding="2, 5, 5, 0">
<Frame OutlineColor="Black"
Padding="10"
HasShadow="False">
<StackLayout Orientation="Horizontal">
<Label Margin="10"
Text="{Binding clave_platillo}"
FontSize="Large"
HorizontalOptions="Start"></Label>
<Label Margin="10"
HorizontalTextAlignment="End"
Text="{Binding nombre}"></Label>
</StackLayout>
</Frame>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
I have tried adding the command to the items inside the collection view, replacing labels for buttons, but still doesn't work, I've also tried to use SelectionChangedCommand in the collection view, and still the same issue, the only way I can make it work is handling the item selection in the View, but I want to stay true to MVVM.
Here is my GrupoModel:
public class GrupoModel
{
public string clave_grupo { get; set; }
public int id_clasificacion { get; set; }
public int id_grupo { get; set; }
public string nombre { get; set; }
public bool pedirClave { get; set; }
public bool status { get; set; }
public int tipo { get; set; }
}
and here is an image of what im trying to do:
If you read the document:
When the SelectionMode property is set to Single, a single item in the
CollectionView can be selected. When an item is selected, the
SelectedItem property will be set to the value of the selected item.
When this property changes, the SelectionChangedCommand is executed
(with the value of the SelectionChangedCommandParameter being passed
to the ICommand), and the SelectionChanged event fires.
When you want to bind a Commond, you should bind to the SelectionChangedCommand instead of SelectedItem. Change your code like below and it will work:
<CollectionView
HeightRequest="50"
ItemsLayout="HorizontalList"
SelectionMode="Single"
SelectionChangedCommand="{Binding SelectedGroupCommand, Mode=TwoWay}"
>
The command should go in the class of GrupoModel instead of the PlatillosViewModel
public List<GrupoModel> Grupos { get; set; }
Should be "linked" to class GrupoModel that have properties and a commandwhich will listen, something like:
Class GrupoModel
{
public int Id { get; set; }
public string Foo { get; set; }
public ICommand SelectedGroupCommand => new Command(Completar);
private async void Completar()
{
await ViewModels.PlatillosViewModel.GetInstancia().SelectedGroup(this);
}
}
This way each element of Grupos will have a command to listen.
BTW: Shouldn't Grupos be an ObservableCollection?

Binding data to a listview and hiding a field

I have the code below in the constructor of my .cs file of a page. I am able to pull
data without a problem
void InitData()
{
ArticlesService objArtServ = new ArticlesService();
Task<List<ArticlesModel>> dataRetrieved = objArtServ.GetValues("News");
//Bind headlines and Id to listview but hide Id
// datalist.itemsource = ?
}
Here is the service that returns the data for me without any problem.
public class ArticlesService
{
HttpClient client;
public async Task<List<ArticlesModel>> GetValues(string category){
client = new HttpClient();
var response = await client.GetStringAsync("http://testing-dev.com/Api/Articles/DefaultArticles/" + category);
var articlesModel = JsonConvert.DeserializeObject<List<ArticlesModel>>(response);
return articlesModel;
}
}
I have a grid on this main page and a list view which will list the headlines from my model and Id. The id will be hidden
and not displayed on the list.
Here is the code below
<StackLayout Grid.Column="1" Orientation="Vertical" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" Padding="0" Spacing="0">
<SearchBar x:Name="searchBar" Placeholder="Search" SearchCommandParameter="{Binding Source={x:Reference searchBar}, Path=Text}"/>
<StackLayout VerticalOptions="FillAndExpand">
<StackLayout VerticalOptions="FillAndExpand" Padding="1" BackgroundColor="Black">
<ListView x:Name="dataList" BackgroundColor="White">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label FontSize="20" VerticalOptions="CenterAndExpand" TextColor="Black" Text="{Binding HeadLines}"></Label>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</StackLayout>
</StackLayout>
Here is my model I am using
public class ArticlesModel
{
public Int32 Id { get; set; }
public string HeadLines { get; set; }
public string Url { get; set; }
public string Category { get; set; }
public string Summary { get; set; }
}
Questions
How do I bind only the HeadLines and Id to my listview such that my Id will be hidden but the headlines will show in the listview
On the click of each row in my listview, I want to be able to pass the associated Id so I can use the Id to query my list for other things.
How do I achieve this?
You don't need to bind the id if you're not displaying it. The ItemSelected event will pass you the complete ArticlesModel object for the row that was tapped, and you can easily get the ID from the model.
dataList.ItemSelected += (s,e) {
if (e.SelectedItem == null) return;
// item will be a pointer to the selected ArticlesModel object
var item = (ArticlesModel)e.SelectedItem;
// then you can use item.Id or any other property on ArticlesModel
};

Resources