Bind BackgroundColor of a view inside DataTemplate Xamarin.Forms - xaml

I have a list view with a DataTemplate and inside it I have a StackLayout that I need set its background but the color of it is inside a variable...When I programmatically set the background of an object using this variable color it works, but when I try to use "binding" in xaml it doesn't work and I can't get this stack programmatically because it's inside a data template...
I really need your answer..
Some help?
<ListView.ItemTemplate>
<DataTemplate>
<!--<DataTemplate.Triggers>
<DataTrigger Binding="{Binding fundoColor}" Value="4">
<Setter TargetName="produtos_stack_color" Property="Background" Value="LawnGreen" />
</DataTrigger>
</DataTemplate.Triggers>-->
<ViewCell>
<StackLayout x:Name="produtos_stack_color" BackgroundColor="{Binding fundoColor}" VerticalOptions="FillAndExpand" Margin="10,10,10,10">
<StackLayout Orientation="Horizontal" Padding="10,10,10,10" BackgroundColor="Black" HorizontalOptions="FillAndExpand">
<Image Source="{Binding imagem}" HeightRequest="80" HorizontalOptions="CenterAndExpand" WidthRequest="160" Aspect="Fill"/>
<StackLayout Orientation="Horizontal" BackgroundColor="Green" VerticalOptions="Center" HorizontalOptions="CenterAndExpand">
<Label Style="{StaticResource labelsfont}" Text="R$" FontSize="Medium" TextColor="White"/>
<Label Style="{StaticResource labelsfont}" Text="{Binding valor}" FontAttributes="Bold" FontSize="Large" TextColor="White"/>
</StackLayout>
</StackLayout>
<StackLayout Margin="0,0,0,10">
<Label Text="{Binding nome}" Style="{StaticResource labelsfont}" FontAttributes="Bold" FontSize="Medium" TextColor="White" VerticalOptions="StartAndExpand" HorizontalOptions="Center"/>
<ContentView BackgroundColor="Chartreuse" HorizontalOptions="FillAndExpand">
<Label Style="{StaticResource labelsfont}" Text="{Binding observacao}" TextColor="White" Margin="10,10,10,10" HorizontalOptions="Center" />
</ContentView>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
I heard about triggers...but I really don't know how it works exactly...
I need produtos_stack_color receive the color
My global variable in code behind...It is setted just in construct of my class
InitializeComponent();
fundoColor = Color.FromHex(this.categEscolhida.corFundo);

You can only bind with properties - so you will have to create a property in code-behind class.
public Color FundoColor { get { return fundoColor; } }
Secondly, in order to refer this property in XAML - you can use Reference extension, and specify parent as Source. For e.g.:
<StackLayout x:Name="produtos_stack_color"
BackgroundColor="{Binding FundoColor, Source={x:Reference ParentHost}}" ..>
And, make sure to set x:Name attribute in root node of your XAML. For e.g:
<ContentPage x:Name="ParentHost" .. />

Related

XAML, Binding, Source and Path

I am learning to understand, how the binding mechanism works in XAML for .NET MAUI. I am assuming this is the same for all XAML projects, WPF, MAUI etc.
At the end is the whole XAML.
This XAML works fine:
<Button WidthRequest="150" Text="Add Activity"
Command="{Binding AddActivityEntityCommand}"
IsEnabled="{Binding IsNotBusy}"
Grid.Row="2"
Margin="8"/>
Is the reason why this works because the Button is part of the ContentPage, which has it's x:DataType set to MainPageViewModel, which is where the command lives?
The Binding is set to AddActivityEntityCommand, while the actual method signature is
async Task AddActivityEntityAsync(). How is this resolved? Since it obviously doesn't match the name, but it works. And what are the method signature requirements for this to work/being recognized?
This on the other hand, doesn't just work as easy out of the box:
<Label HorizontalOptions="End" TextColor="Red" Padding="0,0,10,0" Text="🗑"
IsVisible="{Binding IsSynchronized}">
<Label.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={x:Type viewmodel:MainPageViewModel},
Path=DeleteActivityCommand}" />
</Label.GestureRecognizers>
</Label>
In this context, adding Command="{Binding DeleteActivityCommand} doesn't work, because it derives its Path from <DataTemplate x:DataType="model:ActivityEntity">, I am assuming, which is the data object and not the ViewModel, where the command actually is.
The problem here is, that as soon as I enter this XAML Command="{Binding Source={x:Type viewmodel:MainPageViewModel}, Path=DeleteActivityCommand}", the CollectionView shows empty and there is an unhandled exception thrown when the view is loaded:
System.Reflection.TargetException: Object does not match target type.
The method signature for this command is this async Task DeleteActivityAsync()
What am I missing?
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="OnesieMobile.View.MainPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:model="clr-namespace:OnesieMobile.Model"
xmlns:viewmodel="clr-namespace:OnesieMobile.ViewModel"
x:DataType="viewmodel:MainPageViewModel"
Title="{Binding Title}">
<Grid
ColumnDefinitions="*"
RowDefinitions="20,50,50,*"
RowSpacing="0">
<Label HorizontalOptions="End" Margin="10,0,10,0" Text="{Binding CurrentDateTime}" Grid.Row="0"/>
<Entry Margin="10,0,10,0"
Grid.Row="1" x:Name="entryNewActivity"
Placeholder="New Activityssss" HeightRequest="30" Text="{Binding NewActivityTitle}" />
<Button WidthRequest="150" Text="Add Activity"
Command="{Binding AddActivityEntityCommand}"
IsEnabled="{Binding IsNotBusy}"
Grid.Row="2"
Margin="8"/>
<CollectionView
Grid.Row="3"
ItemsSource="{Binding ActivityEntities}"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:ActivityEntity">
<Grid Padding="10,0,10,0">
<Frame Style="{StaticResource CardView}">
<Grid ColumnDefinitions="*,30,50">
<StackLayout Padding="10,5,0,0" Grid.Column="0">
<Label Text="{Binding Title}" />
</StackLayout>
<StackLayout Padding="10,5,0,0" Grid.Column="1">
<Label HorizontalOptions="End" TextColor="Red"
Padding="0,0,10,0" Text="🗑" IsVisible="{Binding IsSynchronized}" >
<Label.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={x:Type viewmodel:MainPageViewModel},
Path=DeleteActivityCommand}" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
<StackLayout Padding="10,5,0,0" Grid.Column="2">
<Label HorizontalOptions="End"
Padding="0,0,10,0" Text="✔" IsVisible="{Binding IsSynchronized}" />
</StackLayout>
</Grid>
</Frame>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<ActivityIndicator IsVisible="{Binding IsBusy}"
IsRunning="{Binding IsBusy}"
HorizontalOptions="FillAndExpand"
VerticalOptions="CenterAndExpand"
Grid.RowSpan="3"
Grid.ColumnSpan="2"/>
</Grid>
</ContentPage>
Update:
MainPageViewModel.cs contains these Commands
[ICommand]
async Task DeleteActivityAsync()
{
}
[ICommand]
async Task AddActivityEntityAsync()
{
}
Inside an item template, there are several ways to refer to the original BindingContext. I like to do it by getting it from the collection itself:
<CollectionView x:Name="myCollection" ...>
...
<CollectionView.ItemTemplate>
...
Command="{Binding Source={x:Reference myCollection},
Path=BindingContext.DeleteActivityCommand}" />
FUTURE TBD:
I don't like the command name not existing anywhere in the implementing code. Hopefully there will eventually be a way to specify the command name in the ICommand attribute, to make it obvious:
// This won't work today.
[ICommand Name="DeleteActivityCommand"]
...
For now, we have to learn [ICommand]'s magic naming rules, which seem to be "Remove Async from end (if present); Add Command to end".

Xamarin Forms ignore Button binding from DataType on StackLayout

I have got the following xaml code:
<StackLayout Padding="10" x:DataType="model:Light">
<StackLayout HorizontalOptions="FillAndExpand" Grid.Column="0" Grid.Row="1">
<Label Text="{Binding Name}" LineBreakMode="NoWrap" Style="{DynamicResource ListItemTextStyle}" FontSize="24" HorizontalOptions="StartAndExpand" VerticalOptions="StartAndExpand"/>
</StackLayout>
<StackLayout HorizontalOptions="FillAndExpand" Grid.Column="1" Grid.Row="1" >
<Button Text="{Binding Enabled}" Command="{Binding ToggleLight}" CommandParameter="{Binding .}" HorizontalOptions="CenterAndExpand"></Button>
</StackLayout>
</StackLayout>
How do I exclude Command="{Binding ToggleLight}" on my Button form the x:DataType="model:Light" defined on my StackLayout?
Use binding-path.
Define "x:Name" outside the StackLayout (StackLayout, Grid, ListView and etc.)
Add "x:Reference" for your Command binding inside the StackLayout
The command is excluded.
Like:
<Grid x:Name="WhateverParent">
<StackLayout x:DataType="model:Light">
<Button ... Command="{Binding Path=BindingContext.ToggleLight, Source={x:Reference WhateverParent}}" />
</StackLayout>
</Grid>

Pass parameter from list to viewmodel constructor

I have a list of Players
public ObservableCollection<PlayerModel> Players { get; set; }
A flexlayout bindable items is set to players list
<FlexLayout x:Name="playerscollection" Wrap="Wrap" BindableLayout.ItemsSource="{Binding Players}"/>
The items view is a separate content view "PlayerView" with ViewModel "PlayerVieModel" that has a parametrized constructor.
PlayerView =>
<ContentView.Content>
<StackLayout>
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand" >
<Entry x:Name="entry" Placeholder="الاسم" HorizontalOptions="FillAndExpand" Text="{Binding PlayerName}" />
<Label TextColor="Black" FontSize="Medium" HorizontalOptions="End" Text="اللاعب" VerticalOptions="Center"/>
</StackLayout>
<views:SportsListView x:Name="sportsList" BindingContext="{Binding ListViewModel}"
HorizontalOptions="FillAndExpand"
Pickervisibility="True"/>
<Label x:Name="payment" Text="{Binding Source={x:Reference sportsList}, Path=BindingContext.SportsTotalPayments, StringFormat='{0:D2}'}" HorizontalTextAlignment="Start" Margin="0,0,10,0" Padding="0" VerticalTextAlignment="Center" HorizontalOptions="CenterAndExpand" FontSize="20"/>
</StackLayout>
</ContentView.Content>
public PlayerViewModel(bool showPicker = true, PlayerModel player = null){}
I need to bind the list view items to the PlayerVieModel
<FlexLayout x:Name="playerscollection" Wrap="Wrap" BindableLayout.ItemsSource="{Binding Players}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<views:PlayerView >
<views:PlayerView.BindingContext>
<viewModels:PlayerViewModel>
<x:Arguments>
<x:Boolean>false</x:Boolean>
<models:PlayerModel/>
</x:Arguments>
</viewModels:PlayerViewModel>
</views:PlayerView.BindingContext>
</views:PlayerView>
</DataTemplate>
</BindableLayout.ItemTemplate>
</FlexLayout>
But PlayerModel is always null. So, I used the biniding for the model
<viewModels:PlayerViewModel>
<x:Arguments>
<x:Boolean>false</x:Boolean>
<models:PlayerModel
PlayerName="{Binding Source={x:Reference playerscollection},Path=PlayerName}"
PlayerPayment="{Binding Source={x:Reference playerscollection},Path=PlayerPayment}"
Sports="{Binding Source={x:Reference playerscollection},Path=Sports}" />
</x:Arguments>
</viewModels:PlayerViewModel>
It Gives Error Missmatch x:argument

Get SelectedItem from a nested RepeaterView

I am using DottorPagliaccius repeater view thats nested inside another repeaterview. The command RSSFeedSelectedCommand doesnt work unless on top repeater, which in theory makes sense but how can i get it to work?
I have looked into using Converters and bindtoeventcommand but cant see to get them working.
<repeater:RepeaterView x:Name="MainRepeater" SeparatorHeight="25" ItemsSource={Binding Feeds}">
<repeater:RepeaterView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Spacing="10">
<Label Text="{Binding Title}" TextColor="Blue" />
<Label Text="{Binding Description}" TextColor="Red" FontSize="12" />
<repeater:RepeaterView x:Name="MainRepeater2" EmptyText="No elements" ShowSeparator="true" SeparatorHeight="2" SeparatorColor="Silver" ItemsSource="{Binding Items}" SelectedItemCommand="{Binding RSSFeedSelectedCommand}">
<repeater`enter code here`:RepeaterView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Image Source="{Binding Image.Url}"></Image>
<StackLayout Orientation="Vertical">
<Label Text="{Binding Title}" TextColor="Black" />
<Label Text="{Binding Description}" TextColor="Black" FontSize="12" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</repeater:RepeaterView.ItemTemplate>
</repeater:RepeaterView>
</StackLayout>
</ViewCell>
</DataTemplate>
</repeater:RepeaterView.ItemTemplate>
</repeater:RepeaterView>
The Repeater was trying to find the command in Feeds however exists in the ViewModel.
<repeater:RepeaterView x:Name="MainRepeater2" EmptyText="No elements" ShowSeparator="true" SeparatorHeight="2" SeparatorColor="Silver" ItemsSource="{Binding Items}" SelectedItemCommand="{Binding Source={x:Reference RSSPage}, Path=BindingContext.RSSFeedSelectedCommand}">
and you have to name the page:
<ContentPage x:Name="PageName" />
If my assumption is correct, your class structure should be something like,
ViewModel
|__List of Feeds
|__List of Items
|__RSSFeedSelectedCommand
So, DataContext of your inner repeater control (MainRepeater2) will be Feed. So, try with DataContext of outer repeater control. Like,
<repeater:RepeaterView x:Name="MainRepeater2" ItemsSource="{Binding Items}"
SelectedItemCommand="{Binding DataContext.RSSFeedSelectedCommand,
ElementName=MainRepeater}"/>
For Xamarin:
<repeater:RepeaterView x:Name="MainRepeater2" ItemsSource="{Binding Items}"
SelectedItemCommand="{Binding DataContext.RSSFeedSelectedCommand,
Source={x:Reference MainRepeater}}"/>

Set platform specific control to only one platform in xamarin forms

I have a page that contains a listview and Search bar control. There is a custom listview for android. My code as following:
<ContentPage.Content>
<ContentView>
<OnPlatform x:TypeArguments="View">
<OnPlatform.iOS>
<StackLayout Spacing="0">
<SearchBar x:Name="IosSearch"
Placeholder="Search"
TextChanged="IosSearchBar_OnTextChanged"
SearchButtonPressed="OnSearch" BackgroundColor="#19588F">
</SearchBar>
<ListView x:Name="EmpListViewIos"
ItemsSource="{Binding EmpModel.GroupedItems}"
ItemSelected="OnItemSelected"
SeparatorVisibility="Default">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="8">
<StackLayout Spacing="4" Orientation="Horizontal">
<Label Text="{Binding FullName}" FontSize="15" TextColor="Gray" LineBreakMode="NoWrap"/>
</StackLayout>
<StackLayout Spacing="4" Orientation="Horizontal">
<Label Text="{Binding Department}" FontSize="Small" TextColor="#F68933" LineBreakMode="WordWrap"/>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</OnPlatform.iOS>
<OnPlatform.Android>
<StackLayout x:Name="androidListView">
<SearchBar x:Name="DroidSearch"
Placeholder="Search"
TextChanged="DroidSearchBar_OnTextChanged"
SearchButtonPressed="OnSearch" BackgroundColor="#19588F">
</SearchBar>
<local:CustomListView x:Name="customListView"
Items="{Binding EmpModel.Items}"
VerticalOptions="FillAndExpand" MyListItemSelected="nativeListView_ItemSelected"
SeparatorColor="Black">
</local:MyListView>
</StackLayout>
</OnPlatform.Android>
<OnPlatform.WinPhone>
<StackLayout Spacing="0">
<SearchBar x:Name="WinSearch"
Placeholder="Search"
TextChanged="WinSearchBar_OnTextChanged"
SearchButtonPressed="OnSearch" BackgroundColor="#19588F">
</SearchBar>
<ListView x:Name="EmpListViewWin"
ItemsSource="{Binding EmpModel.GroupedItems}"
ItemSelected="OnItemSelected"
SeparatorVisibility="Default">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="8">
<StackLayout Spacing="4" Orientation="Horizontal">
<Label Text="{Binding FullName}" FontSize="15" TextColor="Gray" LineBreakMode="NoWrap"/>
</StackLayout>
<StackLayout Spacing="4" Orientation="Horizontal">
<Label Text="{Binding Department}" FontSize="Small" TextColor="#F68933" LineBreakMode="WordWrap"/>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</OnPlatform.WinPhone>
</OnPlatform>
</ContentView>
</ContentPage.Content>
</ContentPage>
As the representation of data is different due to that I am required custom listview for android according to it's layout defined. The above code works fine. The only problem is the redundancy, the search bar control is common for all platform but when I put the search bar control above the <ContentView> it doesn't work throws exception. Similarly the ListView is common for ios and windows only there is custom listview for android but I am forced to repeat the code for windows also same as android.
Can anyone suggest a more efficient way to achieve this. How can I apply platform specific condition only for android and common for windows and ios.
You could share the iOS and WinPhone views using a static resource:
<ContentPage>
<ContentPage.ResourceDictionary>
<StackLayout x:Key="iosOrWpView">
...
</StackLayout>
</ContentPage.ResourceDictionary>
<ContentView>
<OnPlatform x:TypeArguments="View" iOS="{StaticResource iosOrWpView}"
WinPhone="{StaticResource iosOrWpView}">
<OnPlatform.Android>
<StackLayout x:Name="androidListView">
...
</StackLayout>
</OnPlatform.Android>
</OnPlatform>
</ContentView>
<ContentPage>