.NET MAUI - How to bind to a property of an ObservableCollection Item - c#

I have a CollectionView and I would like to bind the Text of the Label to the 'RecipeName'.
It is not working to do: Text="{Binding RecipeName}"
Now it looks like:
My View looks like:
<CollectionView ItemsSource="{Binding Recipes}"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="{x:Type x:String}">
<SwipeView>
<Grid>
<Frame>
<Frame.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:MainViewModel}}, Path=TapCommand}"
CommandParameter="{Binding .}"/>
</Frame.GestureRecognizers>
<Label Text="{Binding .}" <!-- Here -->
FontSize="24"/>
</Frame>
</Grid>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
My ViewModel looks like:
public MainViewModel()
{
LoadRecipes();
}
private void LoadRecipes()
{
Recipes = new ObservableCollection<RecipeModel>
{
new RecipeModel
{
RecipeName = "Pizza", // Here
Ingredients = "Teig, Belag",
Steps = "Belag auf Teig und danach in den Ofen"
},
new RecipeModel
{
RecipeName = "Burger", // Here
Ingredients = "Brötchen, Fleisch, Belag",
Steps = "Belag und Fleisch auf das Brötchen"
}
};
}
[ObservableProperty]
ObservableCollection<RecipeModel> recipes;

This binds the entire object
Text="{Binding .}"
To bind a specific property
Text="{Binding RecipeName}"
Note that you can only bind to public properties

You don't have to set the BindingContext of the Text. It seems a little complicated. I give some suggestions to make the use of bindings more clear for your case.
First, set the BindingContext of the ContentPage, set it MainViewModel. You could set it in .xaml file:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
.....
xmlns:viewmodel="clr-namespace:BindBindBind">
<ContentPage.BindingContext>
<viewmodel:MainViewModel/>
</ContentPage.BindingContext>
or in code behind .cs file:
public MainPage()
{
InitializeComponent();
this.BindingContext = new MainViewModel();
}
Then for the CollectionView, bind the ItemsSource to the Recipes in the MainViewModel. The default BindingContext is the same as its parent, that is MainViewModel which is set before.
<CollectionView ItemsSource="{Binding Recipes}"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate>
<SwipeView>
<Grid>
<Frame>
<Frame.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:MainViewModel}}, Path=TapCommand}"
CommandParameter="{Binding .}"/>
</Frame.GestureRecognizers>
<Label Text="{Binding RecipeName}" FontSize="24"/>
</Frame>
</Grid>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
x:DataType for DataTemplate is not necessary. Recipes is the Collection of RecipeModel instance and DataTemplate defines each instance how to display. So for each label, Text property should binds to the RecipeName property of each model instance.
Hope it works for you. If you still have any question, feel free to ask.
For more info, you could refer to Populate a CollectionView with data and Data binding.

Related

CollectionView inside CollectionView doesn't bind ItemsSource property correctly (XamarinForms)

I was trying to nest a collectionView inside another, but I am having troubles when it comes to binding the ItemsSource property of the nested one, even though the method by which I have created the ObservableCollection and binded it to the ItemsSource property is the same for both collectionViews. Any idea what am I doing wrong?
XAML code:
<ContentPage.Resources>
<ResourceDictionary>
<selectors:FirstSelector x:Key="First"/>
<selectors:SecondSelector x:Key="Second"/>
</ResourceDictionary>
</ContentPage.Resources>
<renderers:GradientLayout
ColorsList="#FFFFFF,#FFFFFF"
Mode="ToBottomLeft"
Padding="10,50,10,0">
<ScrollView>
<StackLayout>
<CollectionView
ItemsSource="{Binding FirstList}"
ItemTemplate="{StaticResource First}"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
Margin="0,0,0,15">
<CollectionView.ItemsLayout>
<LinearItemsLayout
Orientation="Vertical"
ItemSpacing="15"/>
</CollectionView.ItemsLayout>
<CollectionView.Resources>
<DataTemplate x:Key="Normal">
<Grid
HeightRequest="400"
HorizontalOptions="FillAndExpand"
ColumnDefinitions="20*,30*,25*,25*"
RowDefinitions="15*,70*,15*">
<Frame
Padding="0"
HasShadow="False"
BackgroundColor="Pink"
CornerRadius="30"
IsClippedToBounds="True"
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="4">
<CollectionView
ItemTemplate="{StaticResource Second}"
ItemsSource="{Binding SecondList}"
BackgroundColor="Blue"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<CollectionView.ItemsLayout>
<LinearItemsLayout
Orientation="Horizontal"
ItemSpacing="0">
</LinearItemsLayout>
</CollectionView.ItemsLayout>
<CollectionView.Resources>
<DataTemplate x:Key="NormalTwo">
<Grid
WidthRequest="392"
BackgroundColor="Orange"
ColumnDefinitions="100*"
RowDefinitions="100*"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
Padding="0">
<Frame
BackgroundColor="Purple"
HasShadow="False"
CornerRadius="20"
Padding="0"
Grid.Column="0"
Grid.Row="0">
<Image
Margin="-2,0,0,0"
Source="source.jpg"
Aspect="AspectFill"
HeightRequest="200">
</Image>
</Frame>
</Grid>
</DataTemplate>
</CollectionView.Resources>
</CollectionView>
</Frame>
</Grid>
</DataTemplate>
</CollectionView.Resources>
</CollectionView>
</StackLayout>
</ScrollView>
</renderers:GradientLayout>
ViewModel code:
public class HomeViewModel : FreshBasePageModel
{
public static ObservableCollection<OneForAllModel> firstlist;
public ObservableCollection<OneForAllModel> FirstList
{
get
{
return pictureList;
}
set
{
pictureList = value;
}
}
public ObservableCollection<PictureModel> secondlist;
public ObservableCollection<PictureModel> SecondList
{
get
{
return secondcollectionlist;
}
set
{
secondcollectionlist = value;
}
}
public HomeViewModel(InavigationService _navigation, IDatabaseApiHelper _DataBaseApi)
{
Navigation = _navigation;
DataBaseApi = _DataBaseApi;
//Creates a provisional list necessary to show the skeleton loading UI effect
OneForAllModel loadingPicture = new OneForAllModel()
{
//content
};
ObservableCollection<OneForAllModel> LoadingList = new ObservableCollection<OneForAllModel>()
{
loadingPicture,
loadingPicture,
loadingPicture,
loadingPicture,
loadingPicture,
loadingPicture,
loadingPicture,
loadingPicture,
loadingPicture,
loadingPicture,
};
//This list is binded to the first collection view item source and works perfectly
FirstList = LoadingList;
#region SecondCollectionViewBelongins
PictureModel a = new PictureModel()
{
//content
};
PictureModel b = new PictureModel()
{
//content
};
ObservableCollection<PictureModel> list = new ObservableCollection<PictureModel>()
{
a,
b
};
SecondList = list;
**//This second binding does not seem to work**
#endregion
}
That is all, if you need more information I will provide it as soon as I see your request, thank you all so much for your time, have a good day.
When binding inside a binding we have to give the binding context.
First give the name for content page.
<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"
x:Name="Root">
Then refer the binding context
ItemsSource="{Binding Source={x:Reference Root},Path=BindingContext.SecondList}"
Here is the documentation.
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/data-binding/basic-bindings
This looks to be a BindingContext issue. You are binding the first CollectionView correctly to FirstList. This means an Item will be created for each item in that list. Each item will have a BindingContext of type OneForAllModel.
Because you are then creating another CollectionView within that item it is trying to find a property called SecondList on the OneForAllModel instead of the HomeViewModel.
Try adding a a RelativeSource binding to your SecondList binding.
It should look something like this
<CollectionView ItemTemplate="{StaticResource Second}"
ItemsSource="{Binding Source={RelativeSource AncestorType={x:Type HomeViewModel}}, Path=SecondList}"
BackgroundColor="Blue"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
Here's a link to the documentation on Relative Bindings for reference: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/data-binding/relative-bindings
Maybe you should use StackLayout with a BindableLayout instead of the CollectionView.
Here is an example: https://stackoverflow.com/a/66591841/2472664

How to set the BindableLayout.ItemsSource to an ObservableCollection in an other file

Currently i have a TasksGroupPageViewModel.cs that contains my two ObservableCollection:
public ObservableCollection<TasksGroup> TasksGroups { get; set; } = new ObservableCollection<TasksGroup>();
public ObservableCollection<Tasks> Taches1 { get; set; } = new ObservableCollection<Tasks>();
The binding for my CollectionView which uses the TasksGroups data works, BUT i have a bindable stacklayout inside it but i am unable to bind it to his ObservableCollection data (taches1)
From the debuger i can see that it always try to access data from Models.TasksGroup.cs and not Models.Tasks.cs, it's like since my stacklayout is a children of the CollectionView, i can't bind to it
My question is : How can i directly bind to Taches1 ObservableCollection which is in TaskGroupPageViewModel with BindableLayout.ItemsSource="" or in .xaml.cs file but i didn't find a way to do it like i did for TasksGroup in the on appearing method
How i initialize my data in TaskGroupPage.xaml.cs (data1 is for TasksGroup and data2 is for Tasks :
protected override async void OnAppearing()
{
base.OnAppearing();
var data2 = await App.Database.GetAllTasks();
var data1 = await App.Database.GetTaskGroupsAsync();
var vm = this.BindingContext as TasksGroupPageViewModel;
vm.TasksGroups = new ObservableCollection<TasksGroup>(data1);
TasksGroupCollection.ItemsSource = vm.TasksGroups;
vm.Taches1 = new ObservableCollection<Tasks>(data2);
}
How i bind to my collection view with x:Name=TasksGroupCollection
<CollectionView Grid.Row="2" Margin="25" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
SelectionMode="Single" x:Name="TasksGroupCollection" >
Inside this CollectionView i have a bindable stack layout which i want to bind to Taches1 data
<StackLayout Grid.Column="2" Spacing="10" >
<Label Text="Tâches" TextColor="Black" FontSize="15" Margin="20,0"/>
<StackLayout BindableLayout.ItemsSource="{Binding }" HorizontalOptions="Start" VerticalOptions="Center" Margin="20,0,0,20" x:Name="Taches1">
<BindableLayout.ItemTemplate >
<DataTemplate>
<Label TextColor="#2F3246" FontSize="12">
<Label.FormattedText>
<FormattedString>
<FormattedString.Spans>
<Span Text="{Binding TaskDBA}"/>
<Span Text=" - "/>
<Span Text="{Binding TaskDescription}" FontAttributes="Bold"/>
</FormattedString.Spans>
</FormattedString>
</Label.FormattedText>
</Label>
</DataTemplate>
</BindableLayout.ItemTemplate >
</StackLayout>
</StackLayout>
From the debuger i can see that it always try to access data from
Models.TasksGroup.cs and not Models.Tasks.cs
When any element is included in a DataTemplate, the BindingContext will be the current item from the template's ItemsSource.
Your TasksGroupCollection.ItemsSource is vm.TasksGroups and there is no property called Taches1 in vm.TasksGroups, that's why you can't bind successfully.
Solution:
Taches1 is a property of vm and you should set the correct binding Source:
First, give a name to your ContentPage, let's say it MyPage:
<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:Name="MyPage"
x:Class="App266.MainPage">
Then the bindingContext of your page is vm, bind the BindableLayout.ItemsSource to vm.Taches1 :
<StackLayout BindableLayout.ItemsSource="{Binding BindingContext.Taches1 , Source={x:Reference MyPage}}" HorizontalOptions="Start" VerticalOptions="Center" Margin="20,0,0,20" x:Name="Taches1">

Xamarin.Forms - Binding on ContextMenu

I have a list with songs as items. A long press on the element should display a context menu.
AllSongsViewModel.xaml:
<DataTemplate x:Key="SongTemplate">
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Text="Edit" />
<MenuItem Text="Delete"/>
</ViewCell.ContextActions>
<StackLayout Padding="15,5" VerticalOptions="Center">
<Label Text="{Binding Title}"
FontSize="16"/>
<Label Text="{Binding Performer}"
FontSize="12"/>
</StackLayout>
</ViewCell>
</DataTemplate>
This works well, but I need to bind now so that the context menu opens depending on bool IsAdmin, which lies in AllSongsViewModel
AllSongsViewModel.cs:
public bool IsAdmin => _authService.LoggedUser.Role == "Admin";
But I don’t know how to bind this property to the context menu
Unfortunately you can't do this on your ViewModel. But you can set a BindingContextChange event on your View Cell and change it there like this:
XAML:
<DataTemplate x:Key="SongTemplate">
<ViewCell BindingContextChanged="OnBindingContextChanged">
<StackLayout Padding="15,5" VerticalOptions="Center">
<Label Text="{Binding Title}"
FontSize="16"/>
<Label Text="{Binding Performer}"
FontSize="12"/>
</StackLayout>
</ViewCell>
In your code behind:
private void OnBindingContextChanged(object sender, EventArgs e)
{
base.OnBindingContextChanged();
if (BindingContext == null)
return;
ViewCell theViewCell = ((ViewCell)sender);
var viewModel = this.BindingContext.DataContext as AllSongsViewModel;
theViewCell.ContextActions.Clear();
if (viewModel.IsAdmin)
{
theViewCell.ContextActions.Add(new MenuItem()
{
Text = "Delete",
});
theViewCell.ContextActions.Add(new MenuItem()
{
Text = "Edit",
});
}
}

Xamarin.Forms: add labels inside ListView?

I'm still learning, so please bear with me.
I'm trying to display a ListView in my Xamarin.Form. So far, this is what I have, and it works pretty well:
<?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:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"
xmlns:local="clr-namespace:GasStations"
x:Class="GasStations.MainPage">
<StackLayout>
<ListView x:Name="ListView_Pets">
</ListView>
</StackLayout>
</ContentPage>
This is the codebehind:
var obj = JsonConvert.DeserializeObject(coord);
List<String> storeList = new List<String>();
string store = string.Empty;
foreach (var item in ((JArray)obj))
{
store = item.Value<string>("name");
//desc = item.Value<string>("description"); /* This is the string I want to display */
storeList.Add(store);
}
ListView_Pets.ItemsSource = storeList;
It works and it shows the store names in the ListView. You can see the screenshot here.
Now, instead of just showing the store Name in the ListView I want to also display the description (and any other values). I assume I would need Labels in each row, similar to the asp.net GridView ItemTemplate.
I tried something like this, but it doesn't build:
<?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:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"
xmlns:local="clr-namespace:GasStations"
x:Class="GasStations.MainPage">
<StackLayout>
<ListView x:Name="ListView_Pets" ItemsSource="{Binding MyClass}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Label x:Name="Label_Name" Text="Name" />
<Label x:Name="Label_Desc" Text="Description" />
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
I have access to both Name and Description in the codebehind (with desc = item.Value<string>("description");, but I don't know how to bind these values to the Labels in the Form.
You need to specify the Binding.. Amusing the properties in your class are Name and Description then you would do this
<Label x:Name="Label_Name" Text="{Binding Name}" />
<Label x:Name="Label_Desc" Text="{Binding Description}" />
Note : you don't need to use x:Name unless you need to access them directly from Code-Behind
<Label Text="{Binding Name}" />
<Label Text="{Binding Description}" />
Updated
Thanks for your answer. But what would I need to do in the codebehind?
To what object would I bind them?
When you specify binding in a ItemTemplate, you are binding to the individual properties of the class in the list your listview has specified as the ItemsSource
Define the Class with Properties
public MyClass(){
public string Name{get;set;}
public string Description{get;set;}
}
After then In code Behind
var obj = JsonConvert.DeserializeObject<List<MyClass>>(coord);
ListView_Pets.ItemsSource = obj;
Then update in List View
<?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:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"
xmlns:local="clr-namespace:GasStations"
x:Class="GasStations.MainPage">
<StackLayout>
<ListView x:Name="ListView_Pets" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell>
<StackLayout Orientation="Vertical">
<Label Text="{Binding Name" />
<Label Text="{Binding Description}" />
</StackLayout>
</ViewCell>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
In the Ankit tyagi's code, they wrote two viewcell in his xaml. delete one of them.
There are completly create processes of binding data to a listview .
First of all define your model.
public class MyClass
{
public string Name { get; set; }
public string Description { get; set; }
}
Then, define a datasource in the back code.I suggest you to use ObservableCollection to collect your data.
ObservableCollection is a generic dynamic data collection that provides notifications (using an interface "INotifyCollectionChanged") when items get added, removed, or when the whole collection is refreshed
public ObservableCollection<MyClass> DataSource { get; set; }
public MainPage()
{
this.BindingContext = this;
DataSource = new ObservableCollection<MyClass>();
DataSource.Add(new MyClass() { Name = "Item 1", Description = "Sub Item Text 1" });
DataSource.Add(new MyClass() { Name = "Item 2", Description = "Sub Item Text 2" });
DataSource.Add(new MyClass() { Name = "Item 3", Description = "Sub Item Text 3" });
InitializeComponent();
}
In the end, difine a listview in your xaml. do not forget to binding the source data from the back code.
<StackLayout>
<ListView x:Name="ListView_Pets"
ItemsSource="{Binding DataSource}"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
SeparatorColor="Silver">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" >
<Label Text="{Binding Name}" />
<Label Text="{Binding Description}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Running of these code.

wpf check list box

I m new to wpf.In order to get check list box functionality ,I have added below xaml to my code,but there is no output in my screen.only blank,what it could be?
<TabItem Header="Samples" >
<ListBox Margin="10" Width="373" Height="236">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="MyText"/>
<CheckBox IsChecked="False"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</TabItem>
just have a look at this basic sample
http://merill.net/2009/10/wpf-checked-listbox/
List box is a bit wired for such task..Have a look at ItemsControl.
Here is the code i use:
<ItemsControl
ItemsSource="{Binding ***}" IsTabStop="False">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox
Content="{Binding Name}"
IsChecked="{Binding IsSelected}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Replace your code with this
<TabItem Header="Roles" >
<ListBox Margin="10" Width="373" Height="236">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="MyText"/>
<CheckBox IsChecked="False"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBoxItem>Hi</ListBoxItem>
</ListBox>
</TabItem>
and tell us if it still shows blank
Better still, just use the new CheckListBox control in the Extended WPF Toolkit
http://wpftoolkit.codeplex.com/wikipage?title=CheckListBox&referringTitle=Home
This might help
1.Inorder to work datatemplate you must specify itemsource, here i have bounded a Stateslist a collection of items into it.
2.Also set the Datacontext to ViewModel or the CodeBehind as datacontext.
3.Datacontext will distribute the StateList properties collection to the listbox itemsource
using codebehind -
public Window1()
{
InitializeComponent();
this.DataContext = this;
LoadData();
}
using viewmodel
public Window1()
{
InitializeComponent();
DataContext = new Window1ViewModel();
LoadData();
}
//MyItemsource Property for listbox
private ObservableCollection<States> _stateslist;
public ObservableCollection<States> StatesList
{
get { return _stateslist; }
set
{
_stateslist = value;
RaisePropertyChanged(() => StatesList);
}
}
// Sample Data Loading
public void LoadData()
{
StatesList = new ObservableCollection<States>();
StatesList.Add(new States
{
StateName = "Kerala"
});
StatesList.Add(new States
{
StateName = "Karnataka"
});
StatesList.Add(new States
{
StateName = "Goa"
});
}
Window1.Xaml
<ListBox ItemsSource="{Binding StatesList}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox IsChecked="{Binding IsSelected"} Content="{Binding StateName}" />
<TextBox Text="{Binding TextBoxValue}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Check this out it isworking..you are using TabItem but you didnt define it in TabControl
<TabControl>
<TabItem Header="Tab1">
<ListBox Margin="10" Width="373" Height="236">
<ListBox.Items>
<StackPanel Orientation="Horizontal">
<TextBlock Text="MyText"/>
<CheckBox IsChecked="False"/>
</StackPanel>
</ListBox.Items>
</ListBox>
</TabItem>
</TabControl>
If you are new in WPF use XamlPadX it will give you great help to practice out on it..

Categories