Dangers of referencing elements by name
When following the MVVM pattern and using a view model for a Xamarin.Forms page, most of the bindings will simply bind to the view model properties. However, there are still cases when that's not enough and a reference by name to another element is required.
Here's an example of such a case:
<?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:local="clr-namespace:NameReference"
x:Class="NameReference.MainPage"
x:Name="root">
<FlexLayout BindableLayout.ItemsSource="{Binding Users}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<local:Avatar Text="{Binding Initials}">
<local:Avatar.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding BindingContext.UserTappedCommand,
Source={x:Reference root}}"
CommandParameter="{Binding}"/>
</local:Avatar.GestureRecognizers>
</local:Avatar>
</DataTemplate>
</BindableLayout.ItemTemplate>
</FlexLayout>
</ContentPage>
The TapGestureRecognizer
Command
binding references the page element by name to get access to its view model where the command is defined. This is necessary because the parent element of the gesture is in a DataTemplate
for items in the bound (Users
) collection and its binding context is set to a specific in that collection.
In most cases, this works just fine. But can you think of something that would make it not work (assuming all the reference properties are defined as expected)?
Well, the Avatar
template could be a reason for that. For example, the following one:
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="NameReference.Avatar"
x:Name="root">
<ContentView.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="64"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="64"/>
</Grid.ColumnDefinitions>
<BoxView BackgroundColor="Bisque"
CornerRadius="32"/>
<Label Text="{Binding Text, Source={x:Reference root}}"
FontSize="24"
FontAttributes="Bold"
HorizontalOptions="Center"
VerticalOptions="Center"/>
</Grid>
</ContentView.Content>
</ContentView>
Why? Because it declares the same name: root
. Although the name is in a separate template belonging to a different view, it still interferes with the name reference in the parent page using that view. You can avoid this problem by using unique names; not just within a template but across all of them. One way to ensure that is to use prefixes for names, e.g. avatar_root
instead of root
for the template of the Avatar
view.
You can check out the described behavior yourself in the sample project from my GitHub repository. The latest commit contains working code. The one before that features the broken command binding because of the duplicate name.
When using names in Xamarin.Forms XAML templates make sure that they are always globally unique to avoid broken bindings that can be difficult to troubleshoot and fix. Using prefixes for names in each template is a simple and reliable way to achieve that.