Share via


Présentations personnalisées

Browse sample. Parcourir l'exemple

L’interface utilisateur de l’application multiplateforme .NET (.NET MAUI) définit plusieurs classes de disposition qui organisent leurs enfants d’une manière différente. Une disposition peut être considérée comme une liste de vues avec des règles et des propriétés qui définissent comment organiser ces vues dans la disposition. Voici quelques échantillons de dispositions : Grid, AbsoluteLayoutet VerticalStackLayout.

Les classes de disposition .NET MAUI dérivent de la classe abstraite Layout. Cette classe délègue la disposition et la mesure multiplateformes à une classe de manager de disposition. La classe Layout contient également une méthode substituable CreateLayoutManager() que les dispositions dérivées peuvent utiliser pour spécifier le manager de dispositions.

Chaque manager de disposition de classe implémente l’interface ILayoutManager, qui spécifie que les implémentations Measure et ArrangeChildren doivent être fournies :

  • L’implémentation Measure appelle IView.Measure pour chaque vue dans la disposition et retourne la taille totale de la disposition en fonction des contraintes.
  • L’implémentation ArrangeChildren détermine où chaque vue doit être placée dans les limites de la disposition et appelle Arrange pour chaque vue avec ses limites appropriées. La valeur de retour est la taille réelle de la disposition.

Les dispositions de .NET MAUI ont des managers de disposition prédéfinis pour gérer leur disposition. Toutefois, il est parfois nécessaire d’organiser le contenu de page à l’aide d’une disposition qui n’est pas fournie par .NET MAUI. Cela peut être réalisé en produisant votre propre disposition personnalisée, ce qui vous oblige à comprendre le fonctionnement du processus de disposition multiplateforme de .NET MAUI.

Processus de disposition

Le processus de disposition multiplateforme de .NET MAUI s’appuie sur le processus de disposition natif sur chaque plateforme. En règle générale, le processus de disposition est initié par le système de disposition natif. Le processus multiplateforme s’exécute lorsqu’un contrôle de disposition ou de contenu l’initie en raison d’une mesure ou d’une organisation par le système de disposition natif.

Remarque

Chaque plateforme gère la disposition légèrement différemment. Toutefois, le processus de disposition multiplateforme de .NET MAUI vise à être aussi indépendant de la plateforme que possible.

Le diagramme suivant montre le processus lorsqu’un système de disposition natif lance la mesure de disposition :

The process for layout measurement in .NET MAUI

Toutes les dispositions MAUI .NET ont une vue de stockage unique sur chaque plateforme :

  • Sur Android, cette vue de stockage est LayoutViewGroup.
  • Sur iOS et Mac Catalyst, cette vue de stockage est LayoutView.
  • Sur Windows, cette vue de stockage est LayoutPanel.

Lorsque le système de disposition natif d’une plateforme demande la mesure de l’une de ces vues de stockage, l’affichage de stockage appelle la méthode Layout.CrossPlatformMeasure. Il s’agit du point auquel le contrôle est passé du système de disposition natif au système de disposition de .NET MAUI. Layout.CrossPlatformMeasure appelle la méthode des managers de disposition Measure. Cette méthode est chargée de mesurer les vues enfants en appelant IView.Measure pour chaque vue dans la disposition. La vue mesure son contrôle natif et met à jour sa propriété DesiredSize en fonction de cette mesure. Cette valeur est retournée à la vue de stockage à la suite de la méthode CrossPlatformMeasure. La vue de stockage effectue le traitement interne nécessaire et retourne sa taille mesurée sur la plateforme.

Le diagramme suivant montre le processus lorsqu’un système de disposition natif lance l’arrangement de disposition :

The process for layout arrangement in .NET MAUI

Lorsque le système de disposition natif d’une plateforme demande l’arrangement ou la disposition de l’une de ces vues de stockage, l’affichage de stockage appelle la méthode Layout.CrossPlatformArrange. Il s’agit du point auquel le contrôle est passé du système de disposition natif au système de disposition de .NET MAUI. Layout.CrossPlatformArrange appelle la méthode des managers de disposition ArrangeChildren. Cette méthode est chargée de déterminer où chaque vue doit être placée dans les limites de la disposition et appelle Arrange pour chaque vue pour définir son emplacement. Cette taille de la disposition est retournée à la vue de stockage à la suite de la méthode CrossPlatformArrange. La vue de stockage effectue le traitement interne nécessaire et retourne sa taille actuelle sur la plateforme.

Remarque

ILayoutManager.Measure peut être appelé plusieurs fois avant que ArrangeChildren soit appelé(e), car une plateforme peut avoir besoin d’effectuer certaines mesures spéculatives avant d’organiser des vues.

Approches de disposition personnalisées

Il existe deux approches principales pour créer une disposition personnalisée :

  1. Créez un type de disposition personnalisé, qui est généralement une sous-classe d’un type de disposition existant ou de Layout, et remplacez le CreateLayoutManager() dans votre type de disposition personnalisé. Ensuite, fournissez une implémentation ILayoutManager qui contient votre logique de disposition personnalisée. Pour plus d’informations, consultez Créer un type de disposition personnalisée.
  2. Modifiez le comportement d’un type de disposition existant en créant un type qui implémente ILayoutManagerFactory. Ensuite, utilisez cette fabrique de manager de disposition pour remplacer le manager de disposition par défaut de .NET MAUI pour la disposition existante par votre propre ILayoutManager implémentation qui contient votre logique de disposition personnalisée. Pour plus d’informations, consultez Modifier le comportement d’une disposition existante.

Créer un type de disposition personnalisée

Le processus de création d’un type de disposition personnalisée consiste à :

  1. Créez une classe qui sous-classe un type de disposition existant ou la classe Layout, puis remplacez la CreateLayoutManager() dans votre type de disposition personnalisé. Pour plus d’informations, consultez Sous-classer une disposition.

  2. Créez une classe de manager de disposition qui dérive d’un manager de disposition existant ou implémente directement l’interface ILayoutManager. Dans votre classe de manager de disposition, vous devez :

    1. Remplacer ou implémenter la méthode Measure pour calculer la taille totale de la disposition en fonction de ses contraintes.
    2. Remplacer ou implémenter la méthode ArrangeChildren pour dimensionner et positionner tous les enfants dans la disposition.

    Pour plus d’informations, consultez Créer un compte de manager de disposition.

  3. Utilisez votre type de disposition personnalisée en l’ajoutant à un Page, et en ajoutant des enfants à la disposition. Pour plus d’informations, consultez Consommer le type de disposition personnalisée.

Un processus sensible HorizontalWrapLayout à l’orientation est utilisé pour illustrer ce processus. HorizontalWrapLayout est similaire à un HorizontalStackLayout dans lequel il organise ses enfants horizontalement sur la page. Toutefois, il encapsule l’affichage des enfants à une nouvelle ligne lorsqu’il rencontre le bord droit de son conteneur

Remarque

L’échantillon définit des dispositions personnalisées supplémentaires qui peuvent être utilisées pour comprendre comment produire une disposition personnalisée.

Sous-classe d’une disposition

Pour créer un type de disposition personnalisée, vous devez d’abord sous-classer un type de disposition existant ou la classe Layout. Ensuite, remplacez CreateLayoutManager() votre type de disposition et retournez une nouvelle instance du manager de disposition pour votre type de disposition :

using Microsoft.Maui.Layouts;

public class HorizontalWrapLayout : HorizontalStackLayout
{
    protected override ILayoutManager CreateLayoutManager()
    {
        return new HorizontalWrapLayoutManager(this);
    }
}

HorizontalWrapLayout dérive de HorizontalStackLayout pour utiliser ses fonctionnalités de disposition. Les mises en page MAUI .NET délèguent la disposition et les mesures multiplateformes à une classe de manager de disposition. Par conséquent, le remplacement CreateLayoutManager() retourne une nouvelle instance de la classe HorizontalWrapLayoutManager, qui est le manager de disposition abordé dans la section suivante.

Créer un manager de disposition

Une classe de manager de disposition est utilisée pour effectuer une disposition et une mesure multiplateformes pour votre type de disposition personnalisée. Il doit dériver d’un manager de disposition existant, ou il doit implémenter directement l’interface ILayoutManager. HorizontalWrapLayoutManager dérive de HorizontalStackLayoutManager ce qui lui permet d’utiliser ses fonctionnalités sous-jacentes et d’accéder aux membres dans sa hiérarchie d’héritage :

using Microsoft.Maui.Layouts;
using HorizontalStackLayoutManager = Microsoft.Maui.Layouts.HorizontalStackLayoutManager;

public class HorizontalWrapLayoutManager : HorizontalStackLayoutManager
{
    HorizontalWrapLayout _layout;

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

    public override Size Measure(double widthConstraint, double heightConstraint)
    {
    }

    public override Size ArrangeChildren(Rect bounds)
    {
    }
}

Le constructeur HorizontalWrapLayoutManager stocke une instance du type HorizontalWrapLayout dans un champ afin qu’elle soit accessible dans le manager de disposition. Le manager de disposition remplace également les méthodes Measure et ArrangeChildren méthodes de la classe HorizontalStackLayoutManager. Ces méthodes sont là où vous allez définir la logique pour implémenter votre disposition personnalisée.

Mesurer la taille de disposition

L’objectif de l’implémentation ILayoutManager.Measure est de calculer la taille totale de la disposition. Pour ce faire, appelez IView.Measure pour chaque enfant dans la disposition. Il doit ensuite utiliser ces données pour calculer et retourner la taille totale de la disposition en fonction de ses contraintes.

L’échantillon ci-après illustre la mise en œuvre Measure pour la classe HorizontalWrapLayoutManager :

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);
}

La méthode Measure énumère tous les enfants visibles dans la disposition, en appelant la méthode IView.Measure pour chaque enfant. Elle retourne ensuite la taille totale de la disposition, en tenant compte des contraintes et des valeurs des propriétés des Padding et Spacing. La méthode ResolveConstraints est appelée pour garantir que la taille totale de la disposition correspond à ses contraintes.

Important

Lors de l’énumération des enfants dans l’implémentation ILayoutManager.Measure, ignorez tout enfant dont la propriété Visibility est définie sur Collapsed. Cela garantit que la disposition personnalisée ne laissera pas l’espace pour les enfants invisibles.

Organiser les enfants dans la disposition

L’objectif de l’implémentation ArrangeChildren est de dimensionner et de positionner tous les enfants dans la disposition. Pour déterminer où chaque enfant doit être placé dans les limites de la disposition, il doit appeler Arrange pour chaque enfant avec ses limites appropriées. Elle doit ensuite retourner une valeur qui représente la taille réelle de la disposition.

Avertissement

L’échec de l’appel de la méthode ArrangeChildren pour chaque enfant dans la disposition entraîne la réception d’une taille ou d’une position correctes, et par conséquent, l’enfant ne sera pas visible sur la page.

L’échantillon ci-après illustre la mise en œuvre ArrangeChildren pour la classe HorizontalWrapLayoutManager :

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);
}

La méthode ArrangeChildren énumère tous les enfants visibles de la disposition pour les dimensionner et les positionner dans la disposition. Pour ce faire, appelez Arrange pour chaque enfant avec des limites appropriées, qui tiennent compte de la disposition Padding sous-jacente et Spacing de la disposition sous-jacente. Elle retourne ensuite la taille réelle de la disposition. La méthode AdjustForFill est appelée pour s’assurer que la taille prend en compte si la disposition a ses propriétésHorizontalLayoutAlignment et VerticalLayoutAlignmentdéfinies sur LayoutOptions.Fill.

Important

Lors de l’énumération des enfants dans l’implémentation ArrangeChildren, ignorez tout enfant dont la propriété Visibility est définie sur Collapsed. Cela garantit que la disposition personnalisée ne laissera pas l’espace pour les enfants invisibles.

Utiliser le type de disposition

La classe HorizontalWrapLayout peut être consommée en la plaçant dans un type Page dérivé :

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:layouts="clr-namespace:CustomLayoutDemos.Layouts"
             x:Class="CustomLayoutDemos.Views.HorizontalWrapLayoutPage"
             Title="Horizontal wrap layout">
    <ScrollView Margin="20">
        <layouts:HorizontalWrapLayout Spacing="20">
            <Image Source="img_0074.jpg"
                   WidthRequest="150" />
            <Image Source="img_0078.jpg"
                   WidthRequest="150" />
            <Image Source="img_0308.jpg"
                   WidthRequest="150" />
            <Image Source="img_0437.jpg"
                   WidthRequest="150" />
            <Image Source="img_0475.jpg"
                   WidthRequest="150" />
            <Image Source="img_0613.jpg"
                   WidthRequest="150" />
            <!-- More images go here -->
        </layouts:HorizontalWrapLayout>
    </ScrollView>
</ContentPage>

Les contrôles peuvent être ajoutés au HorizontalWrapLayout au besoin. Dans cet échantillon, lorsque la page contenant le HorizontalWrapLayout s’affiche, les contrôles Image sont affichés :

Screenshot of the horizontal wrap layout on a Mac with two columns.

Le nombre de colonnes de chaque ligne dépend de la taille de l’image, de la largeur de la page et du nombre de pixels par unité indépendante de l’appareil :

Screenshot of the horizontal wrap layout on a Mac with five columns.

Remarque

Le défilement est pris en charge en encapsulant le contenu HorizontalWrapLayout dans un ScrollView.

Modifier le comportement d’une disposition existante

Dans certains scénarios, vous pouvez modifier le comportement d’un type de disposition existant sans avoir à créer un type de disposition personnalisée. Pour ces scénarios ILayoutManagerFactory, vous pouvez créer un type qui implémente et l’utiliser pour remplacer le manager de disposition par défaut de .NET MAUI pour la disposition existante par votre propre implémentation ILayoutManager. Cela vous permet de définir un nouveau manager de disposition pour une disposition existante, comme fournir un manager de disposition personnalisé pour Grid. Cela peut être utile pour les scénarios où vous souhaitez ajouter un nouveau comportement à une disposition, mais ne souhaitez pas mettre à jour le type d’une disposition largement utilisée dans votre application.

Le processus de modification du comportement d’une disposition existante, avec une fabrique de manager de disposition, consiste à :

  1. Créez un manager de disposition qui dérive de l’un des types de manager de disposition de .NET MAUI. Pour plus d’informations, consultez Créer un manager de disposition personnalisée.
  2. Créer un type qui implémente ILayoutManagerFactory. Pour plus d’informations, consultez Créer une fabrique de manager de disposition.
  3. Inscrivez votre fabrique de manager de disposition auprès du fournisseur de services de l’application. Pour plus d’informations, consultez Inscrire la fabrique du manager de disposition.

Créer un manager de disposition personnalisée

Un manager de disposition est utilisé pour effectuer une disposition multiplateforme et une mesure pour une disposition. Pour modifier le comportement d’une disposition existante, vous devez créer un manager de disposition personnalisé qui dérive du manager de disposition pour la disposition :

using Microsoft.Maui.Layouts;

public class CustomGridLayoutManager : GridLayoutManager
{
    public CustomGridLayoutManager(IGridLayout layout) : base(layout)
    {
    }

    public override Size Measure(double widthConstraint, double heightConstraint)
    {
        EnsureRows();
        return base.Measure(widthConstraint, heightConstraint);
    }

    void EnsureRows()
    {
        if (Grid is not Grid grid)
        {
            return;
        }

        // Find the maximum row value from the child views
        int maxRow = 0;
        foreach (var child in grid)
        {
            maxRow = Math.Max(grid.GetRow(child), maxRow);
        }

        // Add more rows if we need them
        for (int n = grid.RowDefinitions.Count; n <= maxRow; n++)
        {
            grid.RowDefinitions.Add(new RowDefinition(GridLength.Star));
        }
    }
}

Dans cet échantillon, CustomGridLayoutManager dérive de la classe GridLayoutManager de .NET MAUI et remplace sa méthode Measure. Ce manager de disposition personnalisé garantit qu’au moment de l’exécution, les RowDefinitions pour le Grid incluent suffisamment de lignes pour tenir compte de chaque propriété Grid.Row jointe définie dans une vue enfant. Sans cette modification, le RowDefinitions pour le Grid doit être spécifiée au moment du design.

Important

Lorsque vous modifiez le comportement d’un manager de disposition existant, n’oubliez pas de vous assurer que vous appelez la méthode base.Measure à partir de votre implémentation Measure.

Créer une fabrique de manager de disposition

Le manager de disposition personnalisé doit être créé dans une fabrique de manager de disposition. Pour ce faire, créez un type qui implémente l’interface ILayoutManagerFactory :

using Microsoft.Maui.Layouts;

public class CustomLayoutManagerFactory : ILayoutManagerFactory
{
    public ILayoutManager CreateLayoutManager(Layout layout)
    {
        if (layout is Grid)
        {
            return new CustomGridLayoutManager(layout as IGridLayout);
        }
        return null;
    }
}

Dans cet échantillon, une instance CustomGridLayoutManager est retournée si la disposition est un Grid.

Inscrire la fabrique du manager de disposition

La fabrique du manager de disposition doit être inscrite auprès du fournisseur de services de votre application dans votre classe MauiProgram :

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            });

        // Setup a custom layout manager so the default manager for the Grid can be replaced.
        builder.Services.Add(new ServiceDescriptor(typeof(ILayoutManagerFactory), new CustomLayoutManagerFactory()));

        return builder.Build();
    }
}

Ensuite, lorsque l’application affiche un rendu Grid, elle utilise le manager de disposition personnalisé pour s’assurer qu’au moment de l’exécution, les RowDefinitions pour le Grid incluent suffisamment de lignes pour prendre en compte chaque propriété Grid.Row jointe définie dans les vues enfants.

L’échantillon suivant montre un Grid qui définit la propriété Grid.Row jointe dans les vues enfants, mais qui ne définit pas la propriété RowDefinitions :

<Grid>
    <Label Text="This Grid demonstrates replacing the LayoutManager for an existing layout type." />
    <Label Grid.Row="1"
           Text="In this case, it's a LayoutManager for Grid which automatically adds enough rows to accommodate the rows specified in the child views' attached properties." />
    <Label Grid.Row="2"
           Text="Notice that the Grid doesn't explicitly specify a RowDefinitions collection." />
    <Label Grid.Row="3"
           Text="In MauiProgram.cs, an instance of an ILayoutManagerFactory has been added that replaces the default GridLayoutManager. The custom manager will automatically add the necessary RowDefinitions at runtime." />
    <Label Grid.Row="5"
           Text="We can even skip some rows, and it will add the intervening ones for us (notice the gap between the previous label and this one)." />
</Grid>

La fabrique du manager de disposition utilise le manager de disposition personnalisé pour vous assurer que l’échantillon Grid suivant s’affiche correctement, malgré la propriété RowDefinitions qui n’est pas définie :

Screenshot of a Grid customized by using a layout manager factory.