Issue with FlexLayout and other Layouts not correctly measuring the amount of space available

I am getting an issue where .net MAUI LayoutManagers are not correctly measuring content and using restraints. I have a FlexLayout inside a VerticalStackLayout inside a Frame. The FlexLayout is used for displaying three hyperlinks and two labels used as spacers and wrapping them if they don’t fit the width. The issue I get is that the FlexLayout does not always resize it’s height to fit the content.

FlexLayout style.

<Style TargetType="FlexLayout" x:Key="my_FlexLinkHolder">
    <Setter Property="Direction" Value="Row"/>
    <Setter Property="Wrap" Value="Wrap"/>
    <Setter Property="AlignItems" Value="Start"/>
    <Setter Property="JustifyContent" Value="Start"/>
    <Setter Property="AlignContent" Value="Start"/>
</Style>

If you notice in the picture, one label is being draw over by the table. For whatever reason FlexLayout is not properly expanding height.

I tried switching from a FlexLayout to a custom layout (HorizontalWrapLayout from microsoft). I get the same problem.

HorizontalWrapLayout

public class HorizontalWrapLayout : HorizontalStackLayout
{
    public HorizontalWrapLayout()
    {
    }

    protected override ILayoutManager CreateLayoutManager()
    {
        return new HorizontalWrapLayoutManager(this);
    }
}

HorizontalWrapLayoutManager

public class HorizontalWrapLayoutManager : HorizontalStackLayoutManager
{
    HorizontalWrapLayout _layout;

    public HorizontalWrapLayoutManager(HorizontalWrapLayout horizontalWrapLayout) : base(horizontalWrapLayout)
    {
        _layout = horizontalWrapLayout;
    }

    public override Size Measure(double widthConstraint, double heightConstraint)
    {
        var padding = _layout.Padding;

        widthConstraint -= padding.HorizontalThickness;

        double currentRowWidth = 0;
        double currentRowHeight = 0;
        double totalWidth = 0;
        double totalHeight = 0;

        for (int n = 0; n < _layout.Count; n++)
        {
            var child = _layout[n];
            if (child.Visibility == Visibility.Collapsed)
            {
                continue;
            }

            var measure = child.Measure(double.PositiveInfinity, heightConstraint);

            // Will adding this IView put us past the edge?
            if (currentRowWidth + measure.Width > widthConstraint)
            {
                // Keep track of the width so far
                totalWidth = Math.Max(totalWidth, currentRowWidth);
                totalHeight += currentRowHeight;

                // Account for spacing
                totalHeight += _layout.Spacing;

                // Start over at 0
                currentRowWidth = 0;
                currentRowHeight = measure.Height;
            }
            currentRowWidth += measure.Width;
            currentRowHeight = Math.Max(currentRowHeight, measure.Height);

            if (n < _layout.Count - 1)
            {
                currentRowWidth += _layout.Spacing;
            }
        }

        // Account for the last row
        totalWidth = Math.Max(totalWidth, currentRowWidth);
        totalHeight += currentRowHeight;

        // Account for padding
        totalWidth += padding.HorizontalThickness;
        totalHeight += padding.VerticalThickness;

        // Ensure that the total size of the layout fits within its constraints
        var finalWidth = ResolveConstraints(widthConstraint, Stack.Width, totalWidth, Stack.MinimumWidth, Stack.MaximumWidth);
        var finalHeight = ResolveConstraints(heightConstraint, Stack.Height, totalHeight, Stack.MinimumHeight, Stack.MaximumHeight);

        return new Size(finalWidth, finalHeight);
    }

    public override Size ArrangeChildren(Rect bounds)
    {
        var padding = Stack.Padding;
        double top = padding.Top + bounds.Top;
        double left = padding.Left + bounds.Left;

        double currentRowTop = top;
        double currentX = left;
        double currentRowHeight = 0;

        double maxStackWidth = currentX;

        for (int n = 0; n < _layout.Count; n++)
        {
            var child = _layout[n];
            if (child.Visibility == Visibility.Collapsed)
            {
                continue;
            }

            if (currentX + child.DesiredSize.Width > bounds.Right)
            {
                // Keep track of our maximum width so far
                maxStackWidth = Math.Max(maxStackWidth, currentX);

                // Move down to the next row
                currentX = left;
                currentRowTop += currentRowHeight + _layout.Spacing;
                currentRowHeight = 0;
            }

            var destination = new Rect(currentX, currentRowTop, child.DesiredSize.Width, child.DesiredSize.Height);
            child.Arrange(destination);

            currentX += destination.Width + _layout.Spacing;
            currentRowHeight = Math.Max(currentRowHeight, destination.Height);
        }

        var actual = new Size(maxStackWidth, currentRowTop + currentRowHeight);

        // Adjust the size if the layout is set to fill its container
        return actual.AdjustForFill(bounds, Stack);
    }
}

I did some debugging and noticed the issue is that the widthConstraint for Measure is different than the width of bounds for ArrangeChildren.

What in happening is the Measure thinks we have enough space, so we don’t need to add another line, but when ArrangeChildren is called it sees that there is actually less space and adds another line.

Any ideas how to fix this, or any workarounds?

EDIT 9/12/24:
The code I am using for the DataTemplate


<ContentPage.Resources>
    <ResourceDictionary>
        <DataTemplate x:Key="FooterTemplate">
            <Grid FlexLayout.Grow="1" VerticalOptions="End">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto"/>
                </Grid.RowDefinitions>
                <Button Text="< Previous" Grid.Row="0" Grid.Column="0" Command="{Binding Source={x:Reference this}, Path=BindingContext.PrevPageCommand}"/>
                <Label Text="{Binding Source={x:Reference this}, Path=BindingContext.PageLabelText}" Grid.Row="0" Grid.Column="1" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>
                <Button Text="Next >" Grid.Row="0" Grid.Column="2" Command="{Binding Source={x:Reference this}, Path=BindingContext.NextPageCommand}"/>
            </Grid>
        </DataTemplate>
        <DataTemplate x:Key="RecordTemplate">
            <controls:MyResultCardView MyDataRecord="{Binding}" IsVisible="{Binding Source={x:Reference this}, Path=BindingContext.DoneLoading}"/>
        </DataTemplate>
        <controls:CustomTemplateSelector FooterTemplate="{StaticResource FooterTemplate}" CardTemplate="{StaticResource RecordTemplate} x:Key="CustomSelector"/>
    </ResourceDictionary>
</ContentPage.Resources>

The data template selector class.

public class CustomTemplateSelector : DataTemplateSelector
{
    public DataTemplate CardTemplate { get; set; }
    public DataTemplate FooterTemplate { get; set; }

    protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
    {
        //If the type of object is my custom footer class, will use footer template, otherwise we will use data card template
        if (item is CustomFooter)
            return FooterTemplate;
        else
            return CardTemplate;
    }
}

The selector checks if the object being bound is a Footer, otherwise it will use my custom control.

Code for MyResultCardView

public class MyResultCardView : Frame
{
    public static readonly BindableProperty MyDataRecordProperty =
           BindableProperty.Create("MyDataRecord", typeof(MyDataRecord), typeof(MyResultCardView), null, defaultBindingMode: BindingMode.TwoWay, propertyChanged: HandleValuePropertyChanged);
    
    public MyDataRecord MyDataRecord
    {
        get => (MyDataRecord)GetValue(MyDataRecordProperty);
        set => SetValue(MyDataRecordProperty, value);
    }
    
    public MyResultCardView() : base()
    {
    
    }
    
    private static void HandleValuePropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        MyResultCardView _view;
        MyDataRecord _rec;
    
        _view = (MyResultCardView)bindable;
        _rec = (MyDataRecord)newValue;
        if (_view != null && _rec != null)
        {
            _rec.DumpContentToFrame(_view);
        }
    }
}

The method DumpContentToFrame takes the Frame and programmatically adds a VerticalStackLayout to it and and populates it with labels containing text from the DataClass.

It does this kinda stuff.

VerticalStackLayout stackLayout = new VerticalStackLayout();
frame.Content = stackLayout;
Label label1 = new Label(){Text = dataProperty1};
stackLayout.Add(label1);

Then when I add a flex layout with the style my_FlexLinkHolder to the ‘VerticalStackLayout’, the flex layout does not measure correctly. Possibly because it doesn’t like being nested.

For now I found a simple work around by just putting the label links on separate lines, instead of wrapping. However, I am curious if there are some issues that happen when FlexLayout is nested inside other layouts, because I use FlexLayout frequently.

6

If you want to wrap the Label, you don’t have to use FlexLayout. You can Control text truncation and wrapping by setting the LineBreakMode property. To create hyperlinks label, you may use formatted text. Code snippets in code behind,

Label myLabel = new Label() 
{ 
    LineBreakMode=LineBreakMode.WordWrap
};
FormattedString  formattedString = new FormattedString();
Span span1 = new Span() { 
    Text="Map The Location ",
    TextColor = Colors.Blue,
    TextDecorations = TextDecorations.Underline
};
//create more span if you want

formattedString.Spans.Add(span1);

myLabel.FormattedText = formattedString;

And you may add the Label in VerticleStackLayout

stackLayout.Add(myLabel);

Also, I didn’t see the issue when FlexLayout nested in a VerticleStackLayout. If I wrap the Label with FlexLayout and then added to the stackLayout again, also works like below.


Screenshot

1

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật