Custom ViewCell Contain Button Has Command And Binding To This Command - xamarin

I fix customize some problems in some projects
one of them that I need to use custom ViewCell in Separated class and file
like that:
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="HBRS.Controls.ViewCellTemplates.ArticleItemViewCell">
<Image>
<Image.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding BindingContext.clickCommand, Source={x:Reference Name=mArt}}"
CommandParameter="{Binding .}" />
</Image.GestureRecognizers>
</Image>
</ViewCell>
where mArt is the view that will command make some thing with it
after that I used this view cell in on of my xamarin pages like that:
<ListView.ItemTemplate>
<DataTemplate>
<Cell:ArticleItemViewCell />
</DataTemplate>
</ListView.ItemTemplate>
when I run the application on my device, it throw an exception say that cannot find object referenced for 'mArt'
so I need some way to pass Source={x:Reference Name=mArt} up with the same result or make the interact that command will make it

From what you wrote, I assume that you have a view using your ViewCell something like
<ContentView ...
x:Name="mArt">
<ListView ...>
<ListView.ItemTemplate>
<DataTemplate>
<templates:ArticleItemViewCell ... />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentView>
And now you are trying to reference that view mArt from your ViewCell. Unfortunately that is not how things work. mArt is not something like a global variable, but a member of your view class (if you are interested in the details, have a look at the .xaml.g.cs file that is created in your object folder).
ArticleItemViewCell however is a different class from which you can't simply access fields of some other class. ArticleItemViewCell does not know anything about mArt. While it might be possible to access the parent in some way, I'd advise you no to, because you tend to forget these details and some months later you'll look at your view and wonder where the interaction with the cell is implemented, until you realize, that the cell does some fishy things. It will only cost you time. Been there, done that. Believe me.
Rather create a bindable property of type Command in your viewcell, and bind to it from your containing view
In ArticleItemViewCell.xaml.cs
public static readonly BindableProperty TappedCommandProperty = BindableProperty.Create(nameof(TappedCommand), typeof(Command), typeof(ArticleItemViewCell));
public Command TappedCommand
{
get => (Command)GetValue(TappedCommandProperty);
set => SetValue(TappedCommandProperty, value);
}
And now you can bind them from your ArticleItemViewCell
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="HBRS.Controls.ViewCellTemplates.ArticleItemViewCell"
x:Name="Cell">
<Image>
<Image.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding TappedCommand, Source={x:Reference Cell}}"
CommandParameter="{Binding .}" />
</Image.GestureRecognizers>
</Image>
</ViewCell>
And from your view you can bind the clickCommand of your VM
<ContentView ...
x:Name="mArt">
<ListView ...>
<ListView.ItemTemplate>
<DataTemplate>
<templates:ArticleItemViewCell TappedCommand="{Binding Source={x:Reference mArt}, Path=BindingContext.clickCommand}" ... />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentView>
I did not try the exact code, but basically this oughta work.
Please note: Consuming the ItemTapped event (see the docs) with a event to command behavior (see here) is more expressive and spares you the additional command.

Related

How to place focus on my custom input , I am using mvvm , prism

One query I have an entry in a contentview to be able to call it from another page.
the question is how to locate the focus when I click on a button
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
x:Class="MovilERP.Controls.SacTextBox"
x:Name="this">
<Grid RowDefinitions="Auto,Auto">
<!--#region Entry Normal-->
<Grid RowDefinitions="Auto,1"
ColumnDefinitions="*,Auto" RowSpacing="0">
<Entry x:Name="txtBox"
Margin="{OnPlatform Android=0 , iOS=5}"
Text="{Binding Text, Source={x:Reference this}}"
PlaceholderColor="{DynamicResource PlaceHolderColorEntry}"
Style="{StaticResource EstiloEntry}"
IsPassword="{Binding ShowIcoPassword, Source={x:Reference this}}"
IsSpellCheckEnabled="{Binding IsCorrector, Source={x:Reference this}}"
IsTextPredictionEnabled="True">
</grid>
</grid>
</Conteview>
this is how i invoke it from another page
<sofadControls:SacTextBox
x:Name="Descripcion"
Grid.Column="1"
Placeholder="Descripción"
SacAlineacion="Start"
SacValidacion="AlfaNumerico"
Text="{Binding Source={RelativeSource AncestorType={x:Type dialogos:FrmArticulosGeneralViewModel}}, Path=DetalleActual.Descripcion}"/>
In this way I try to invoke the focus but it does not work
Descripcion.Focus();
you need to create a public method in your control
public void Focus()
{
txtBox.Focus();
}
then you can call that public Focus method from the oage that contains your control

I have a Xamarin BindingContext quandary

I've created a custom CheckBox control to which I have added a Command/CommandParameter capability. I tried this using a Behavior and ran into the same problem. Here is my XAML:
`<ContentPage.BindingContext>
<vm:ShoppingListViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<CollectionView ItemsSource="{Binding MyShoppingList}"
SelectionMode="None"
EmptyView="No items in your Shopping List">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="0" ColumnDefinitions="30, *, 200">
<controls:CheckBoxEx x:Name="CompletedCheckBox"
IsChecked="{Binding Completed, Mode=TwoWay}"
Command="{Binding ToggledCommand}"
CommandParameter="{Binding .}"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Label Text="{Binding ItemName}" Grid.Column="1" HorizontalOptions="StartAndExpand" VerticalOptions="Center"/>
<Label Text="{Binding StoreName}" Grid.Column="2" HorizontalOptions="EndAndExpand" VerticalOptions="Center"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage.Content>
`
The BindingContext for my CollectionView is clearly my ViewModel; MyShoppingList is an ObservableCollection created in the ViewModel. The CheckBox IsChecked value is located in my Model as the Completed property. The Command, however, is not found because CollectionView apparently changes the BindingContext from the ViewModel to the Model, hence the Completed, ItemName, and StoreName properties are properly bound. How can I associate the Command with the ViewModel so that the Binding occurs correctly? The purpose of the Command is to trigger a database update. Here is the runtime error I see:
[0:] Binding: 'ToggledCommand' property not found on 'ShoppingList.Models.ShoppingListItem', target property: 'GLLCustomControls.CheckBoxEx.Command'
something like this should work
Command="{Binding Source={RelativeSource AncestorType={x:Type vm:ShoppingListViewModel}}, Path=ToggledCommand}"
the Xamarin docs have multiple examples of more complex binding scenarios

Command-Binding (with RelativeSourceExtension) within a StackLayout using BindableLayout.ItemsSource

I am trying to bind a command (with RelativeSourceExtension) to a button within a StackLayout using BindableLayout.ItemsSource in an Xamarin.Forms App.
Not working version of MyControl.xaml:
<?xml version="1.0" encoding="UTF-8"?>
<Grid xmlns="http://xamarin.com/schemas/2014/forms"
...
x:Class="MyApp.Views.MyControl">
<StackLayout Orientation="Horizontal"
BindableLayout.ItemsSource="{Binding Data}">
<Button Command="{Binding Source={RelativeSource AncestorType={x:Type views:MyControl}}, Path=BindingContext.RemoveCommand}"
CommandParameter="{Binding}"
Text="Remove"/>
</StackLayout>
</Grid>
Unfortunately the button is "inactive" and does not let you "click".
The same command binding works fine in a ListView with ItemsSource.
Working version of MyControl.xaml:
<?xml version="1.0" encoding="UTF-8"?>
<Grid xmlns="http://xamarin.com/schemas/2014/forms"
...
x:Class="MyApp.Views.MyControl">
<ListView ItemsSource="{Binding Data}"
HasUnevenRows="true">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Button Command="{Binding Source={RelativeSource AncestorType={x:Type views:MyControl}}, Path=BindingContext.RemoveCommand}"
CommandParameter="{Binding}"
Text="Remove"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Control Usage in MainPage.xmal:
<Grid BindingContext="{Binding Cart}">
...
<views:MyControl/>
....
</Grid>
Am I generally doing something wrong? Or is there a trick to make this work?
You missed using StackLayout.ItemTemplate and after DataTemplate in you xaml. Your button is not wrapped in a good way. That's it.

Can't access XAML elements after calling InitializeComponent on BasePage class

Edit: Here's a gist of some of the codebehinds and what it reports out: https://gist.github.com/mattkenefick/5c4effdbad712eb1a42f6cf7207226a6
Note: The question is specific to what's happening in an InitializeComponent call and not to much else. Please read the question in its entirety.
I have a MyPage->BasePage->ContentPage structure. If BasePage executes InitializeComponent() then the XAML elements lose scope within MyPage.. meaning I can't call MyPage.MyListView.ItemSource = xyz without getting a NullReferenceException.
If the InitializeComponent() call only happens in MyPage, then everything works fine (meaning that it doesn't get called in BasePage)
This question is very specifically about understanding why the BasePage.InitializeComponent() call breaks the references to XAML elements like x:MyListView.
Models
Pages
├ BasePage.xaml
| ⤷ BasePage.xaml.cs
└ MyPage.xaml
⤷ MyPage.xaml.cs
Views
App.xaml
⤷ App.xaml.cs
On my MyPage.xaml markup, I have various StackLayout elements, ListView, etc. It all exists within a pages:BasePage.Content tag, like so:
<!-- for s/o: MyPage.xaml -->
<?xml version="1.0" encoding="utf-8" ?>
<pages:BasePage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:Namespace.Pages"
xmlns:views="clr-namespace:Namespace.Views"
x:Class="Namespace.Pages.MyPage">
<pages:BasePage.Content>
<ListView x:Name="ListViewView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Image Source="{Binding imageUrl}" />
<Label Text="{Binding formattedDayOfWeek}" />
<Label Text="{Binding formattedDate}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</pages:BasePage.Content>
</pages:BasePage>
Within my MyPage.xaml.cs class, the constructor executes the InitializeComponent() method.
Here's what the BasePage.xaml looks like:
<!-- for s/o: BasePage.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"
x:Class="Namespace.Pages.BasePage"
x:Name="_parent">
<ContentPage.Content>
<StackLayout x:Name="VerticalLayout" BackgroundColor="#f1f1f1">
<ContentView
x:Name="cv"
x:FieldModifier="public"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
Content="{Binding Path=ViewContent, Source={x:Reference _parent}}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
So to reiterate:
Within the MyPage.xaml.cs, I'm trying to call ListViewView.ItemsSource = SomeDataModel after an async fetch from a REST server.
If the extended BasePage.xaml.cs class calls InitializeComponent() in its constructor... I will get a NullReferenceException when setting the ItemsSource.
If the extended BasePage.xaml.cs class DOES NOT call InitializeComponent() in its constructor... The ItemsSource is correctly set and the list appears.
Can someone explain to me why the parent InitializeComponent call causes the NullReferences in the MyPage class?
Thanks!
By default, the variables that Xamarin generates from your XAML are private, so you can't access them in inherited classes.
Field Modifers allow you change that default behavior
<Label x:Name="publicLabel" x:FieldModifier="public" />
First, I assume what you said is true (haven't tried it myself).
If that is the case this happens because your XAML is not inheritable the way C# class is inheritable. So what you do by inhering the XAML page is that you inherit ONLY its C# code, not its XAML contents. Having that in mind, InitializeComponent shouldn't work.

Xamarin Forms: Call Command of ViewModel from DataTemplate

I am stuck on a binding problem here.
I created a bindable layout which is inside a control template:
<ContentView x:Name="SettingsMenu" ControlTemplate="{StaticResource HeaderTemplate}" AbsoluteLayout.LayoutBounds="0.5,0.5,1,1"
AbsoluteLayout.LayoutFlags="All">
<ScrollView Orientation="Vertical" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<StackLayout x:Name="SettingsStack" BindableLayout.ItemsSource="{Binding Settings}" BindableLayout.ItemTemplateSelector="{StaticResource SettingsSelectorTemplate}" Orientation="Vertical" Spacing="0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
</ScrollView>
</ContentView>
What I want to do is call a command inside the view model. The call is inside a item template selector as a resource dictionary inside App.xml
<ResourceDictionary>
<DataTemplate x:Key="PlaceholderSettingsTemplate">
### SOME STUFF
</DataTemplate>
<DataTemplate x:Key="HeaderSettingsTemplate">
### SOME STUFF
<Grid ...>
<Grid.GestureRecognizers>
<TapGestureRecognizer Tapped="ButtonClick" Command="{Binding BindingContext.SettingsTap, Source={x:Reference SettingsPage}}" CommandParameter="{Binding}" /> ########## <--------- WHAT TO USE FOR SOURCE?
</Grid.GestureRecognizers>
</Grid>
</DataTemplate>
<data:SettingsSelector x:Key="SettingsSelectorTemplate" Placeholder="{StaticResource PlaceholderSettingsTemplate}" Heading="{StaticResource HeaderSettingsTemplate}" Content="{StaticResource ContentSettingsTemplate}" />
</ResourceDictionary>
Before I moved it inside a resource dictionary in the App.xml file, I simply used the x:Name of the Parent Contentview. But: I can't reference it by name anymore because I moved it into a resource dictionary inside App.xml.
Now, the answer may be trivial but I just can't find a solution.
Any help is appreciated.
Kind regards
You can find the SettingsStack StackLayout using the data template's Grid which wraps all the content. Since the SettingsStack has the same binding context as the parent content view, you can access the binding context in App.cs like:
<DataTemplate x:Key="HeaderSettingsTemplate">
<!--### SOME STUFF-->
<Grid x:Name="ParentGrid">
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Parent.BindingContext.SettingsTap, Source={x:Reference ParentGrid}}" CommandParameter="{Binding}" />
</Grid.GestureRecognizers>
</Grid>
</DataTemplate>
The ParentGrid's parent is SettingsStack on your current page.
From the item template (even if it's in a different file), you can access the view model of it's ancestor, using the following:
Command="{Binding Source={RelativeSource AncestorType={x:Type SettingsViewModel}}, Path=SettingsTap}" CommandParameter="{Binding}"
In this case I just assumed that the name of the ancestor's view model is SettingsViewModel, but you get the point.
Here is an interesting article regarding relative binding, that describes other scenarios as well: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/data-binding/relative-bindings#bind-to-an-ancestor
Cheers.
In another way we can use ContentPage as AncestorType directly. Then bind command with BindingContext.
{Binding BindingContext.SomeCommand, Source={RelativeSource AncestorType={x:Type ContentPage}}}
Complete example
<DragGestureRecognizer
DragStartingCommand="{Binding BindingContext.SomeCommand, Source={RelativeSource AncestorType={x:Type ContentPage}}}"
DragStartingCommandParameter="{Binding}"/>
Also this way works with separate resource files (eg. MySeparateResource.xml -> MyPageView.xaml).

Resources