0
votes

I think I found a bug in the FlexLayout. I've tried to nest 2 FlexLayouts where the outer one should be the column container and the inner one the row container. But the page stays empty. I've already filed a bug report which was accept for triage but my guess is that it's going take some time to sort that out. So I have to solve this one myself I guess.

So here's the problem I would like to solve in a class I call the ColumnLayout. I am trying to get a text to flow from column to the next column and the next, etc like this.

The quick brown fox jumps over the lazy dog. --> The quick brown fox jumps over the lazy dog. --> etc.
The quick brown fox jumps over the lazy dog. |   The quick brown fox jumps over the lazy dog. |
The quick brown fox jumps over the lazy dog. |   The quick brown fox jumps over the lazy dog. |
The quick brown fox jumps over the lazy dog. |   The quick brown fox jumps over the lazy dog. |
The quick brown fox jumps over the lazy dog. |   The quick brown fox jumps over the lazy dog. |
The quick brown fox jumps over the lazy dog. |   The quick brown fox jumps over the lazy dog. |
The quick brown fox jumps over the lazy dog. |   The quick brown fox jumps over the lazy dog. |
----------------------------------------------   ----------------------------------------------

The text is inside StackLayouts with a horizontal orientation. Here's an impression of that. You can see the text flowing off of the page at the bottom. That's where I would like to a new column to begin to the right of the first one.

enter image description here

Here's the code that produces this output:

    private BindableObject GetXaml(Document doc)
    {
        var column = new ColumnLayout()
        {
            StyleClass = new List<string> { "ColumnContainer" }
        };

        foreach (var line in doc.Lines)
        {
            var row = new FlexLayout
            {
                StyleClass = new List<string> { "RowContainer" }
            };

            column.Children.Add(row);

            InterpretDirective(row, line);
        }

        return column;
    }

This is ColumnLayout class which is currently just the StackLayout:

using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;

namespace TGB.Xamarin.Forms.Layouts
{
    public class ColumnLayout : StackLayout
    {
        public ColumnLayout() { }

        protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
        {
            var size = base.OnMeasure(widthConstraint, heightConstraint);

            return size;
        }
    }
}

I'm not sure how to solve this puzzle. Xamarin components measure items from the outside in. It seems that the OnMeasure(double widthConstraint, double heightConstraint) somehow comes into play. Can someone explain to me how this works.

My idea is to add a stacklayout with vertical orientation into the main horizontal stacklayout when size of the column is exhausted but I don't know if that is the right strategy. Any help appreciated.

**** EDIT ****

Here's an example from another program that does just that:

enter image description here

1
I am trying to get a text to flow from column to the next column and the next, so you want to set two columns in UI? From your screenshot, I am not sure what is your problem, can you provide one screenshot that you want to do? According to your code, you want to display document in UI?Cherry Bu - MSFT
No, not quite. Take a look at the 'Quick brown fox...' line and scroll to the right. It says 'etc'. So the columns should grow to the right for any lines that do not fit at the bottom for the current column (Added a screenshot from GuitarTapp Pro).Paul Sinnema
B.T.W. the 'Document' comes from a library called Konves.ChordPro. It holds the parsed ChordPro text in an object tree.Paul Sinnema
Can you post more code here? Because I am not sure what your want to do?Cherry Bu - MSFT
I think I found a solution that will work here: docs.microsoft.com/en-us/samples/xamarin/xamarin-forms-samples/…Paul Sinnema

1 Answers

0
votes

Ok, after some fiddling around I've got it working. Documentation on some stuff of Xamarin.Forms is sparse but I managed to analyze how this should be done. Here's the full code for the ColumnLayout class:

using System;
using System.Collections.Generic;
using System.Linq;
using Xamarin.Forms;

namespace TGB.Xamarin.Forms.Layouts
{
    public class ColumnLayout : Layout<View>
    {
        public static readonly BindableProperty ColumnSpacingProperty = BindableProperty.Create(
            "ColumnSpacing",
            typeof(double),
            typeof(ColumnLayout),
            5.0,
            propertyChanged: (bindable, oldvalue, newvalue) =>
            {
                ((ColumnLayout)bindable).InvalidateLayout();
            });

        public static readonly BindableProperty RowSpacingProperty = BindableProperty.Create(
            "RowSpacing",
            typeof(double),
            typeof(ColumnLayout),
            5.0,
            propertyChanged: (bindable, oldvalue, newvalue) =>
            {
                ((ColumnLayout)bindable).InvalidateLayout();
            });

        public double ColumnSpacing
        {
            set { SetValue(ColumnSpacingProperty, value); }
            get { return (double)GetValue(ColumnSpacingProperty); }
        }

        public double RowSpacing
        {
            set { SetValue(RowSpacingProperty, value); }
            get { return (double)GetValue(RowSpacingProperty); }
        }

        private (double totalHeight, double largestWidth) GetTotalChildSizes()
        {
            double totalHeight = 0;
            double largetsWidth = 0;

            foreach (var child in Children)
            {
                var request = child.Measure(double.PositiveInfinity, double.PositiveInfinity);
                largetsWidth = request.Request.Width > largetsWidth ? request.Request.Width : largetsWidth;
            }

            largetsWidth = largetsWidth > Application.Current.MainPage.Width ? Application.Current.MainPage.Width : largetsWidth;

            foreach (var child in Children)
            {
                var request = child.Measure(largetsWidth, double.PositiveInfinity);
                totalHeight += request.Request.Height;
            }

            return (totalHeight, largetsWidth);
        }

        protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
        {
            if (Children.Count(child => child.IsVisible) == 0)
            {
                return new SizeRequest();
            }

            var sizes = GetTotalChildSizes();

            var columns = 0;

            if (sizes.totalHeight < heightConstraint)
            {
                columns = 1;
            }
            else
            {
                columns = ((int)(sizes.totalHeight / heightConstraint)) + 1;
            }

            var requestedSize = new Size(columns * sizes.largestWidth, sizes.totalHeight) ;

            return new SizeRequest(requestedSize);
        }

        protected override void LayoutChildren(double x, double y, double width, double height)
        {
            double xChild = x;
            double yChild = y;
            double yNew = 0;

            var sizes = GetTotalChildSizes();

            foreach (View child in Children)
            {
                if (child.IsVisible)
                {
                    SizeRequest childSizeRequest = child.Measure(sizes.largestWidth, Double.PositiveInfinity);

                    if (yChild + childSizeRequest.Request.Height > height)
                    {
                        yChild = y;
                        yNew = y + RowSpacing + childSizeRequest.Request.Height;
                        xChild += ColumnSpacing + childSizeRequest.Request.Width;
                    }
                    else
                    {
                        yNew = yChild + RowSpacing + childSizeRequest.Request.Height;
                    }

                    LayoutChildIntoBoundingRegion(child, new Rectangle(new Point(xChild, yChild), childSizeRequest.Request));

                    yChild = yNew;
                }
            }
        }
    }
}

Here's the result.

enter image description here

I've not added caching yet but I think I should. The Measure() routines are called pretty often to get the right sizes so some caching would realy speed things up.