Search code examples
xamarin.formsdata-bindingabsolutelayout

Binding AbsoluteLayout.LayoutBounds in Xamarin.Forms


I am trying to layout some rectangles in an AbsoluteLayout using a ViewModel to set LayoutBounds:

    <AbsoluteLayout x:Name="outerLayout" Grid.Row="1" Grid.Column="1"
                    VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" Margin="0,0,10,10" >
        <RefreshView x:DataType="vm:MyViewModel" Command="{Binding ProjectEditor.LoadItemsCommand}" IsRefreshing="{Binding ProjectEditor.IsBusy, Mode=TwoWay}">
            <CollectionView x:Name="ItemsListView"
                            ItemsSource="{Binding Items}"
                            SelectionMode="None">
                <CollectionView.ItemTemplate>
                    <DataTemplate>
                        <Frame x:DataType="model:LayoutItem" 
                               AbsoluteLayout.LayoutBounds="{Binding Rect, Mode=OneWay}" AbsoluteLayout.LayoutFlags="All"                                              
                               BackgroundColor="LightSalmon" >
                            <Frame.GestureRecognizers>
                                <TapGestureRecognizer Tapped="Test_Clicked" />
                            </Frame.GestureRecognizers>
                        </Frame>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>
        </RefreshView>
    </AbsoluteLayout>

outerLayout is located in a Grid that has an X-axis in Grid.Row="0" Grid.Column="1" and an Y-Axis in Grid.Row="1" Grid.Column="0".

Frame theBorder is layed out correctly but not the frames in the CollectionView: They have the appropriate width but heights are too small and identical for all Frame objects.

For test purposes I defined a TapGestureRecognizer to check the Frame's properties in the debugger:

        Frame frm = sender as Frame;
        var x = frm.AnchorX;
        var y = frm.AnchorY;
        var h = frm.Height;
        var w = frm.Width;
        var bounds = AbsoluteLayout.GetLayoutBounds(frm);

bounds has the values I returned in Rect through the DataModel but h and w are too small.

What is wrong?

Edit:

I applied the changes suggested by ToolmakerSteve.

HeightRequest seems to work. However, AnchorX, AnchorY and WidthRequest have no effect. The Frame is always drawn to the end of the Parent View:

<Frame x:DataType="model:LayoutItem" 
         AnchorX="{Binding Rect.X}" AnchorY="{Binding Rect.Y}" 
         HeightRequest="{Binding Rect.Height}" WidthRequest="{Binding Rect.Width}"                                       
         BackgroundColor="LightSalmon" BorderColor="Black" HorizontalOptions="StartAndExpand" >
                        

Edit:

I removed the first <Frame /> because it is confusing and not necessary to understand my intention. What I want is to layout a pattern of rectangles whose coordinates are given by ItemsSource. Can anybody give me some hints how to proceed?

Perhaps the best approach is to implement a CollectionView custom renderer.

(My activity here is my hobby, not my job. I am still learning about Xamarin MVVM Patterns, so it will last some time until I can show you my solution here.)


Solution

  • AbsoluteLayout.LayoutBounds has no effect except on direct chidren of the AbsoluteLayout.
    The two children here are the Frame and the RefreshView.
    Cannot apply it to nested elements.

    • Inside DataTemplate, bind that inner Frame's HeightRequest and WidthRequest.

    • Also set CollectionView's ItemSizingStrategy="MeasureAllItems".


    Positioning items of a CollectionView

    You can't. The CollectionView controls their position.
    By default, they are in a vertical list.

    Similarly, the Width is the width of the CollectionView itself.

    If child is appearing at end of area, instead of start, see https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/layouts/layout-options.

    Specifically VerticalOptions="Start". Set that on the collectionview itself.


    Use BindableLayout with AbsoluteLayout

    A similar question now exists.

    See Jason's comment with link to BindableLayout.

    You would use AbsoluteLayout as the Layout. Read there for more details.


    A c# approach

    If you need to independently control the positions of items, they can't be part of a CollectionView (or any similar group, such as ListView).

    Could create the elements in c# code, and add them to the AbsoluteLayout:

    // Loop over your data.
    foreach (.. item in ...)
    {
        var element = new BoxView(...);
        AbsoluteLayout.SetLayoutBounds(element, ...);
        outerLayout.Children.Add(element);    
    }
    

    Look up docs for BoxView and AbsoluteLayout SetLayoutBounds, to see the parameters.

    NOTE: I've used BoxView instead of Frame. Test with that first. Frame won't work well until it has some element inside of it. You could test with a Frame containing a BoxView.