skip to Main Content

I’m trying to build a table in .NET MAUI based on a Grid Layout. This is the code:

<CollectionView ItemsSource="{Binding digitalInputs}">
    <CollectionView.Header>
        <Grid ColumnDefinitions="*,*,*,*">
            <Label Text="Name" Grid.Column="0"/>
            <Label Text="Typ" Grid.Column="1"/>
            <Label Text="Status" Grid.Column="2"/>
            <Label Text="Aktiv" Grid.Column="3"/>
        </Grid>
    </CollectionView.Header>
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="services:DigitalInput">
            <Grid ColumnDefinitions="*,*,*,*">
                <Label Text="{Binding pName}" Grid.Column="0"/>
                <Label Text="{Binding pDigitalType}" Grid.Column="1"/>
                <Label Text="{Binding pValueText}" Grid.Column="2"/>
                <Label Text="{Binding pActive}" Grid.Column="3"/>
             </Grid>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

This is the result when running in Debug Mode on MacCatalyst (Visual Studio for Mac):

MAUI Macos screenshot

Now I wonder how I can align the header grid properly to the grid in the data template? Does someone have a suggestion on how I can improve the code to build a proper table?

Edit: This seems to be a bug in the IDE. When I change the HorizontalOptions property on the Grid in the CollectionView.Header, as a comment suggested, the XAML Hot-Reload triggers a re-rendering of the view and all of a sudden the header grid aligns correctly with the grid in the ItemTemplate.

2

Answers


  1. If you mean by arranging children horizontally first and then pushing down to the next row then the current MAUI still does not support that, you can only trigger span (also span isn’t changing at runtime on WinUI right now, I think the team is fixing it) or create a custom one

        public class HorizontalWrapLayout : StackLayout
    {
        public HorizontalWrapLayout()
        {
        }
    
        protected override ILayoutManager CreateLayoutManager()
        {
            return new HorizontalWrapLayoutManager(this);
        }
    }
    
    
    
        public class HorizontalWrapLayoutManager : StackLayoutManager
    {
        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;
    
            var rows = new Dictionary<int, List<Size>>();
            var currentRowIndex = 0;
            var currentRow = new List<Size>();
    
            rows.Add(currentRowIndex, currentRow);
    
            foreach (var child in _layout)
            {
                if (child.Visibility == Visibility.Collapsed)
                {
                    continue;
                }
    
                var childSize = child.Measure(double.PositiveInfinity, heightConstraint);
                var childWidth = childSize.Width + (currentRow.Any() ? _layout.Spacing : 0);
    
                var rowWidth = currentRow.Aggregate(0.0, (w, x) => w + x.Width);
                if (rowWidth + childWidth > widthConstraint)
                {
                    if (currentRow.Any())
                    {
                        currentRowIndex++;
                        currentRow = new List<Size>();
                        rows.Add(currentRowIndex, currentRow);
                    }
                }
                else if (currentRow.Any())
                {
                    currentRow.Add(new Size(_layout.Spacing, 0));
                }
    
                currentRow.Add(childSize);
            }
    
            var totalWidth = 0.0;
            var totalHeight = 0.0;
    
            if (rows.Any())
            {
                var rowWidths = rows.Select(x => x.Value.Aggregate(0.0, (result, item) => result + item.Width)).ToList();
                var rowHeights = rows.Select(x => x.Value.Any() ? x.Value.Max(i => i.Height) : 0).ToList();
    
                totalWidth = rowWidths.Any() ? rowWidths.Max() : 0;
                totalHeight = rowHeights.Any() ? rowHeights.Sum() : 0;
                if (rows.Keys.Count > 1)
                {
                    totalHeight += _layout.Spacing * (rows.Keys.Count - 1);
                }
            }
    
            totalWidth += padding.HorizontalThickness;
            totalHeight += padding.VerticalThickness;
    
            var finalHeight = ResolveConstraints(heightConstraint, Stack.Height, totalHeight, Stack.MinimumHeight, Stack.MaximumHeight);
            var finalWidth = ResolveConstraints(widthConstraint, Stack.Width, totalWidth, Stack.MinimumWidth, Stack.MaximumWidth);
    
            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);
    
            return actual.AdjustForFill(bounds, Stack);
        }
    

    Usage

    <app:HorizontalWrapLayout
                BindableLayout.ItemTemplate="{x:StaticResource HorizontalWrapLayoutItemTemplate}"
                BindableLayout.ItemsSource="{Binding ControlGroups, Mode=OneWay}"
                HorizontalOptions="Center"
                Spacing="50"
                VerticalOptions="Center" />
    
    Login or Signup to reply.
  2. I tested the code you provided in iOS, Windows in MAUI. And it can align the header grid properly to the grid in the data template in CollectionView. So the issue could be related with the services:DigitalInput retrieving the data, they should be correctly formatted with no blank space in those properties.

    Below are the code sample and running output, hope it can shed some light for you!

    XAML:

    <CollectionView ItemsSource="{Binding digitalInputs}"> 
    
            <CollectionView.Header>
     
                <Grid ColumnDefinitions="*,*,*,*">
                    <Label Text="Name" Grid.Column="0"/>
                    <Label Text="Typ" Grid.Column="1"/>
                    <Label Text="Status" Grid.Column="2"/>
                    <Label Text="Aktiv" Grid.Column="3"/>
                </Grid>
            </CollectionView.Header>
    
            <CollectionView.ItemTemplate>
    
                <DataTemplate >
    
                    <Grid ColumnDefinitions="*,*,*,*">
    
                        <Label Text="{Binding pName}" Grid.Column="0"/>
    
                        <Label Text="{Binding pDigitalType}" Grid.Column="1"/>
    
                        <Label Text="{Binding pValueText}" Grid.Column="2"/>
    
                        <Label Text="{Binding pActive}" Grid.Column="3"/>
    
                    </Grid>
    
                </DataTemplate>
    
            </CollectionView.ItemTemplate>
    
        </CollectionView>
    

    Code-behind:

    
        public ObservableCollection<Model> digitalInputs { get; set; } 
        public NewPage1()
          {
                InitializeComponent();
    
                //assign data
                digitalInputs = new ObservableCollection<Model>()
                {
    
                      new Model{pName="KipSchalter", pDigitalType="Zustand",pActive="OFF", pValueText="True" },
                      new Model{pName="KipSchalter", pDigitalType="Zustand",pActive="OFF", pValueText="True" },
                      new Model{pName="Digital In 3", pDigitalType="Zustand",pActive="OFF", pValueText="FALSE" },
                      new Model{pName="Digital In 4", pDigitalType="Zustand",pActive="OFF", pValueText="FALSE" }
            }
                ;
    
                BindingContext = this;
          }
    
    

    iOS output:

    enter image description here

    Windows output:
    enter image description here

    Update:

    This seems to be a potential issue in the IDE. When changing the HorizontalOptions property on the Grid as Jason suggested, the header grid aligns correctly with the grid in the ItemTemplate.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search