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).
Related
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
I am using a custom activity indicator experimenting a block on map interaction.
The map and the activity are in a grid and I use a binding property to running or not the map as:
<ContentPage>
...
<Grid>
....
<local:CustomMap HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Grid.Row="2" Grid.ColumnSpan="2"
x:Name="MyMap" IsVisible="{Binding InMap}" ItemsSource="{Binding Items}"
IsShowingUser="true" PinClicked="PinClicked"
MapType="Street"/>
<customActivity:CustomActivityIndicator
x:Name="BusyIndicator"
IsRunning= "false"
Grid.ColumnSpan="2" Grid.RowSpan="3" />
</Grid>
</ContentPage>
The problem is on the map that has bloking when user interact with it, inclusive if IsRunning is ever false.
Why it could be happen?
Your CustomActivityIndicator blocks interaction, because it's first element that you tap/touch.
You have to remove it from your grid for interacting with other view or use some hacks like setting HeightRequest=0.
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.
My application has objects that are surrounded by this:
<StackLayout HeightRequest="49" BackgroundColor="Red" Orientation="Vertical" Margin="0" >
<Grid VerticalOptions="CenterAndExpand">
<!-- XAML here -->
</Grid>
</StackLayout>
I would like to make it so that I don't need to enter the XAML on the 1st, 2nd, last and next to last lines each time.
Can I use something like a control template to avoid doing this and if so how would I use it?
You will need to use a combination of ContentPresenter and ControlTemplate (as mentioned in the note on this page)
On a ContentPage (or ContentView), the Content property can be assigned and the ControlTemplate property can also be set. When this occurs, if the ControlTemplate contains a ContentPresenter instance, the content assigned to the Content property will be presented by the ContentPresenter within the ControlTemplate.
<ControlTemplate x:Key="DefaultTemplate">
<StackLayout HeightRequest="49" BackgroundColor="Red"
Orientation="Vertical" Margin="0">
<Grid VerticalOptions="CenterAndExpand">
<ContentPresenter /> <!-- This line is replaced by actual content -->
</Grid>
</StackLayout>
</ControlTemplate>
Usage:
<ContentView ControlTemplate="{StaticResource DefaultTemplate}">
<!-- XAML here (basically the view that is to be surrounded by above layout) -->
<Label TextColor="Yellow" Text="I represent the content" />
</ContentView>
Yes, Xamarin also provide the Control Template functionalities in XAML.
Write a below code in App.xaml
<Application.Resources>
<ResourceDictionary>
<ControlTemplate x:Key="TealTemplate">
<Grid>
<Label Text="Control Template Demo App"
TextColor="White"
VerticalOptions="Center" />
</ControlTemplate>
</ResourceDictionary>
</Application.Resources>
Call the template in side ContentPage
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="SimpleTheme.HomePage">
<ContentView x:Name="contentView" Padding="0,20,0,0"
ControlTemplate="{StaticResource TealTemplate}">
<StackLayout VerticalOptions="CenterAndExpand">
<Label Text="Welcome to the app!" HorizontalOptions="Center" />
<Button Text="Change Theme" Clicked="OnButtonClicked" />
</StackLayout>
</ContentView>
</ContentPage>
I have this code in my application:
<StackLayout x:Name="pfs" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<StackLayout Grid.Row="0" Grid.Column="1" Padding="0,0,20,0" Orientation="Horizontal"
HorizontalOptions="EndAndExpand" VerticalOptions="CenterAndExpand">
<Label x:Name="scoreCountLabel"
TextColor="{Binding BindingContext.TickMarkColor, Source={Reference pfs}}"/>
</StackLayout>
I am trying to understand how the TextColor is set. Can someone explain to me why the developer has set the Source={Reference pfs}
Why is this needed as it's already inside a StackLayout with the name of pfs?
It's completely unnecessary from what I can tell. BindingContext is inherited by all subviews, so referencing the parent view's BindingContext is redundant.
TextColor="{Binding TickMarkColor}"/>
Done.