Home > OS >  How to place text in view at locations specified by databinding in Xamarin Forms
How to place text in view at locations specified by databinding in Xamarin Forms

Time:10-08

The functionality I'm looking for is tappable labels where the both the text and locations are databound, and the locations are relative to the image that is also being displayed.

I've tried both AbsoluteLayout and RelativeLayout, but the labels don't seem to be able to escape the confines of the CollectionView; i.e the labels keep iterating down or across the page based on the label's size. I don't care if labels overlap each other. In fact if the label's font is large enough and the databound locations are close enough, then overlapping labels is a certainty and the tap event could logically happen to whatever label is on top at that pixel location. That is fine with me.

For example, I have an image that is 640x480 pixels in size that I want to display. The label' text and locations that I want placed over them need to be databound. Here is the sample MockData.

        public MockDataStore()
        {
            items = new List<Item>()
            {
                new Item { Id = Guid.NewGuid().ToString(), Text = "A"  , Description="My A"  , X= 85, Y=130 },
                new Item { Id = Guid.NewGuid().ToString(), Text = "B"  , Description="My B"  , X=140, Y=150 },
                new Item { Id = Guid.NewGuid().ToString(), Text = "C"  , Description="My C"  , X=190, Y=165 },
                new Item { Id = Guid.NewGuid().ToString(), Text = "D"  , Description="My D"  , X=240, Y=175 },
                new Item { Id = Guid.NewGuid().ToString(), Text = "E"  , Description="My E"  , X=290, Y=180 },
                new Item { Id = Guid.NewGuid().ToString(), Text = "F"  , Description="My F"  , X=335, Y=185 }
            };
        }

And here is the XAML I'm trying.

    <Grid x:Name="grid" 
          x:DataType="local:ItemsViewModel">
        <!--<Image Source="bg.png" Aspect="AspectFit" HorizontalOptions="Start" VerticalOptions="Start" x:Name="bg"/>-->
        <RefreshView x:DataType="local:ItemsViewModel" 
                        Command="{Binding LoadItemsCommand}" 
                        IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
        <CollectionView x:Name="ItemsListView"
                        ItemsSource="{Binding Items}"
                        SelectionMode="None">
            <CollectionView.ItemTemplate>
                    <DataTemplate>
                        <RelativeLayout Padding="10" x:DataType="model:Item">
                            <Label Text="{Binding Text}" 
                                    RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent, Property=X, Constant={Binding X}}"
                                    RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent, Property=Y, Constant={Binding Y}}"
                                    BackgroundColor="Transparent" 
                                    FontSize="16" />
                            <RelativeLayout.GestureRecognizers>
                                <TapGestureRecognizer 
                                    NumberOfTapsRequired="1"
                                    Command="{Binding Source={RelativeSource AncestorType={x:Type local:ItemsViewModel}}, Path=ItemTapped}"     
                                    CommandParameter="{Binding .}">
                                </TapGestureRecognizer>
                            </RelativeLayout.GestureRecognizers>
                        </RelativeLayout>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>
        </RefreshView>
    </Grid>

What is happening is that the relative locations of the labels are being placed in incremented location down the page. I'm not married to using CollectionView as long as I can still get ItemTapped.

I've also tried using BindableLayout with no success, though I'm not sure I understand how to use that class correctly, and moreover the data source is all text and locations.

What I want to accomplish is similar to how a GPS app displays a map and overlays tappable points of interest that are loaded from data and relative to the coordinates within the map.

Is what I want to accomplish even possible using Xamarin's XAML?

EDIT: I have discovered that RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent, Property=Width, Factor=0, Constant=0}" TranslationX="{Binding X}" will place the labels at the correct X location. However, this idea doesn't work with the Y location. CollectionView keeps incrementing the Y location down the page. I wish CollectionView could be set to not do any positioning of controls and just allow me to set the label's x/y position via binding.

CodePudding user response:

will place the labels at the correct X location. However, this idea doesn't work with the Y location.

The problem is you display the label in each CollectionView item, But they have different RelativeLayout parent. And it has enough width for setting Y value, but not for X value.

For this scenario, we suggest you compose items with BindableLayout

For example

<ContentPage.BindingContext>
   <local:ItemsViewModel />
</ContentPage.BindingContext>

<RelativeLayout BindableLayout.ItemsSource="{Binding items}">
    <BindableLayout.ItemTemplate>
        <DataTemplate x:DataType="local:Item">
            <Label
                BackgroundColor="Transparent"
                FontSize="16"
                RelativeLayout.XConstraint="{Binding X}"
                RelativeLayout.YConstraint="{Binding Y}"
                Text="{Binding Text}" />
        </DataTemplate>
    </BindableLayout.ItemTemplate>
</RelativeLayout>

Code Behind

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }

}
public class ItemsViewModel
{
    public ItemsViewModel()
    {
        MockDataStore();
    }
    public List<Item> items { get; set; }
    public void MockDataStore()
    {
        items = new List<Item>()
        {
            new Item { Id = Guid.NewGuid().ToString(), Text = "A"  , Description="My A"  ,X = Constraint.Constant(85),Y= Constraint.Constant(130)},
            new Item { Id = Guid.NewGuid().ToString(), Text = "B"  , Description="My B"  ,X = Constraint.Constant(140),Y= Constraint.Constant(150)},
            new Item { Id = Guid.NewGuid().ToString(), Text = "C"  , Description="My C"  ,X = Constraint.Constant(190),Y= Constraint.Constant(165)},
            new Item { Id = Guid.NewGuid().ToString(), Text = "D"  , Description="My D"  ,X = Constraint.Constant(240),Y= Constraint.Constant(175)},
            new Item { Id = Guid.NewGuid().ToString(), Text = "E"  , Description="My E"  ,X = Constraint.Constant(290),Y= Constraint.Constant(180)},
            new Item { Id = Guid.NewGuid().ToString(), Text = "F"  , Description="My F"  ,X = Constraint.Constant(335),Y= Constraint.Constant(185)}
        };
    }
}
public class Item
{
    public string Id { get; internal set; }
    public string Text { get; internal set; }
    public string Description { get; internal set; }
    public Constraint X { get; set; }
    public Constraint Y { get; set; }
}
  • Related