Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
.NET MAUI Cookbook

You're reading from   .NET MAUI Cookbook Build a full-featured app swiftly with MVVM, CRUD, AI, authentication, real-time updates, and more

Arrow left icon
Product type Paperback
Published in Dec 2024
Publisher Packt
ISBN-13 9781835461129
Length 384 pages
Edition 1st Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Alexander Russkov Alexander Russkov
Author Profile Icon Alexander Russkov
Alexander Russkov
Arrow right icon
View More author details
Toc

Table of Contents (11) Chapters Close

Preface 1. Chapter 1: Crafting the Page Layout FREE CHAPTER 2. Chapter 2: Mastering the MVVM Design Pattern 3. Chapter 3: Advanced XAML and UI Techniques 4. Chapter 4: Connecting to a Database and Implementing CRUD Operations 5. Chapter 5: Authentication and Authorization 6. Chapter 6: Real-Life Scenarios: AI, SignalR, and More 7. Chapter 7: Understanding Platform-Specific APIs and Custom Handlers 8. Chapter 8: Optimizing Performance 9. Index 10. Other Books You May Enjoy

Implementing a custom arranging algorithm

Built-in layout panels help you solve the most common tasks in your app. However, sometimes, you may need to achieve a unique layout, which would be difficult to implement with the default .NET MAUI panels (Grid, VerticalStackLayout, etc.). Fortunately, you can easily implement your custom layout logic, where children are measured and arranged according to your rules.

Getting ready

To follow the steps described in this recipe, we just need to create a blank .NET MAUI application. The default template includes sample code in the MainPage.xaml and MainPage.xaml.cs files, but you can remove it and leave only a blank ContentPage in XAML and a constructor with the InitializeComponent method in CS. When copying code snippets with namespaces, don’t forget to replace them with the namespaces in your project.

The code for this recipe is available at https://github.com/PacktPublishing/.NET-MAUI-Cookbook/tree/main/Chapter01/c1-CustomLayout.

How to do it…

Let’s create a panel that arranges its children in a circle with a specified radius, as demonstrated in the following figure:

Figure 1.8 – A custom circular layout

Figure 1.8 – A custom circular layout

  1. Create a class inherited from Layout and override CreateLayoutManager. In CreateLayoutManager, return an instance of a class that will measure and arrange children in your panel:

    MainWindow.xaml.cs

    public class CircularLayout : Layout {
        protected override ILayoutManager CreateLayoutManager() {
            return new CircularLayoutManager(this);
        }
        public double Radius {
            get { return (double)GetValue(RadiusProperty); }
            set { SetValue(RadiusProperty, value); }
        }
        public static readonly BindableProperty RadiusProperty =
            BindableProperty.Create("Radius", typeof(double), 
              typeof(CircularLayout));
    }

    The Radius dependency property will help us arrange elements according to a specified radius.

  1. Create a class that implements ILayoutManager:
    public class CircularLayoutManager : ILayoutManager {
        readonly CircularLayout parentLayout;
        public CircularLayoutManager(CircularLayout layout) {
            this.parentLayout = layout;
        }
        public Size Measure(double widthConstraint, double 
          heightConstraint) {
           throw new NotImplementedException();
        }
        public Size ArrangeChildren(Rect bounds) {
            throw new NotImplementedException();
        }
    }
  2. Implement the ILayoutManager.Measure method. In the method, iterate through all children of the panel and call Measure for each of them (this is required to update the desired size of the child items):
    public class CircularLayoutManager : ILayoutManager {
        //…
        public Size Measure(double widthConstraint, double 
          heightConstraint) {
            double radius = parentLayout.Radius;
            for (int n = 0; n < parentLayout.Count; n++) {
                var child = parentLayout[n];
                if (child.Visibility == Visibility.Collapsed) {
                    continue;
                }
                child.Measure(double.PositiveInfinity, double.
                  PositiveInfinity);
            }
            return new Size(parentLayout.WidthRequest, parentLayout.
              HeightRequest);
        }
    }

    The returned value determines what desired size the panel will have.

  3. Implement the ILayoutManager.ArrangeChildren method. For each child in the parent layout panel (CircularLayout), call Arrange. Pass a rectangle to this method, indicating what exact position and bounds a child should have:
    public class CircularLayoutManager : ILayoutManager {
    //...
        public Size ArrangeChildren(Rect bounds) {
            double radius = parentLayout.Radius;
            double angleStep = Math.PI * 2 / parentLayout.Count;
            for (int i = 0; i < parentLayout.Count; i++) {
                var child = parentLayout[i];
                if (child.Visibility == Visibility.Collapsed) {
                    continue;
                }
                child.Arrange(new Rect(
                    radius * Math.Cos(angleStep * i) + radius,
                    radius * Math.Sin(angleStep * i) + radius,
                    child.DesiredSize.Width,
                    child.DesiredSize.Height));
            }
            return new Size(parentLayout.WidthRequest, parentLayout.
              HeightRequest);
        }
    }

    We use the sin and cos functions to calculate the points of the top-left point of each child. We have intentionally avoided taking into account properties such as Padding and VerticalOptions/HorizontalOptions to simplify the logic.

  4. That’s it! Now, you can use CircularLayout in XAML:
    <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/
                        maui"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/
                          xaml"
                 x:Class="c1_CustomLayout.MainPage" 
                 xmlns:local="clr-namespace:c1_CustomLayout"
                 Title="CustomLayoutPage">
        <local:CircularLayout WidthRequest="300"
                              HeightRequest="300"
                              BackgroundColor="There are no actions"
                              Radius="100"
                              VerticalOptions=Start
                              HorizontalOptions="Start">
            <Button Text="Button1"/>
            <Button Text="Button2"/>
            <Button Text="Button3"/>
            <Button Text="Button4"/>
            <Button Text="Button5"/>
            <Button Text="Button6"/>
            <Button Text="Button7"/>
            <Button Text="Button8"/>
        </local:CircularLayout>
    </ContentPage>

    CircularLayout is intentionally colored so that you can see its bounds and how child elements are distributed in the panel. The panel’s children don’t occupy all available space. Instead, they are arranged according to the specified radius.

Now, you can run the project to see the result.

How it works…

The platform automatically calls the layout manager’s Measure method when it needs to update the layout. The layout manager, in turn, calls Measure for each of its child elements (in the preceding example, CircularLayoutManager calls Measure for each button added to CircularLayout). Once a child’s Measure is called, the child calculates what size it wants to have to properly fit all its content – this size is called DesiredSize. The layout manager can use the DesiredSize property of its children to calculate its own DesiredSize and return this value in its ILayoutManager.Measure method. In the preceding example, we return the size based on the specified radius, without taking into account the panel’s children.

In the ArrangeChildren method, we again iterate through all the child elements and call the Arrange method. This method accepts the rectangle in which a child element should be located. So, here, we specify the position and size of each element.

In .NET MAUI, layouts are created in a two-step process – measuring and arranging. Here is a simplified diagram illustrating the algorithm:

Figure 1.9 – A .NET MAUI layout algorithm

Figure 1.9 – A .NET MAUI layout algorithm

There’s more…

In step 3 of the How to do it… section, we measured all the child elements of our custom panel and returned a value that doesn’t depend on the panel’s children:

public class CircularLayoutManager : ILayoutManager {
    //…
    public Size Measure(double widthConstraint, double 
      heightConstraint) {
        //…
        for (int n = 0; n < parentLayout.Count; n++) {
            //…
            child.Measure(double.PositiveInfinity, 
              double.PositiveInfinity);
        }
        return new Size(parentLayout.WidthRequest, 
          parentLayout.HeightRequest);
    }
}

You may ask, why measure children if we don’t take their desired size into account? The answer is that if you don’t call Measure for a child element, this element won’t calculate its desired size and, as a result, won’t be rendered.

Key point

Always call the Measure element for all panel children, even if you don’t take into account their desired size during the measuring cycle.

You have been reading a chapter from
.NET MAUI Cookbook
Published in: Dec 2024
Publisher: Packt
ISBN-13: 9781835461129
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image