Fermer

septembre 26, 2024

Maîtriser le shell .NET MAUI – Partie 3

Maîtriser le shell .NET MAUI – Partie 3


Apprenez tout ce dont vous avez besoin pour maîtriser l’utilisation de Shell dans vos applications .NET MAUI. Dans le dernier article de la série : naviguez entre les pages et créez des SearchHandlers pour ajouter une fonctionnalité de recherche à vos pages.

C’est la dernière partie du Maîtriser le shell .NET MAUI série, où vous avez appris ce qui est nécessaire pour créer rapidement des applications multiplateformes à l’aide de Shell. Si vous avez suivi la série, vous savez déjà comment créer votre propre Shell, comment ajouter de nouvelles pages à la hiérarchie via le Flyout et les onglets, ainsi que comment personnaliser les couleurs. Dans cet article, vous apprendrez à naviguer entre les pages et à créer des SearchHandlers pour ajouter une fonctionnalité de recherche à vos pages. Commençons !

Dans un article précédentnous avons vu comment créer une structure hiérarchique dans un fichier Shell, qui crée un système de navigation automatique. Cependant, que se passe-t-il si nous voulons naviguer du code vers d’autres pages ? C’est ce que nous verrons ensuite.

Affectation de routes aux éléments de coque

La première chose à savoir est que le système de navigation Shell permet d’attribuer le Route propriété à chaque élément du Shell, dans le but de les identifier, comme dans l’exemple suivant :

<FlyoutItem
    Title="Productivity Tools"
    Icon="dotnet_bot.png"
    Route="ProductivityTools">
    <Tab
        Title="Text Tools"
        Icon="dotnet_bot.png"
        Route="TextTools">
        <ShellContent
            Title="Word Counter"
            ContentTemplate="{DataTemplate UtilPages:WordCounter}"
            Icon="dotnet_bot.png"
            Route="WordCounter" />
        <ShellContent
            Title="Color Generator"
            ContentTemplate="{DataTemplate UtilPages:RandomColor}"
            Icon="dotnet_bot.png"
            Route="ColorGenerator" />
    </Tab>
</FlyoutItem>

Le parcours complet de chaque élément sera concaténé avec le nom de l’élément hiérarchique supérieur. Autrement dit, l’itinéraire pour WordCounter dans l’exemple précédent sera :

//ProductivityTools/TextTools/WordCounter

De cette façon, si deux ShellContent les éléments ont le même nom, l’itinéraire ne sera pas le même, ce qui évite les conflits de navigation. Maintenant que vous savez comment attribuer des routes aux éléments Shell, voyons comment naviguer du code vers eux.

Ce que je ne vous ai pas dit auparavant, c’est qu’il est possible d’ajouter MenuItem éléments dans un Flyout. Ces MenuItems peuvent être utilisés pour effectuer des opérations qui ne sont pas associées à la navigation vers une autre page.

Des exemples de ce type pourraient être si vous souhaitez que l’utilisateur se déconnecte ou l’envoie vers une page Web pour obtenir des informations supplémentaires.

Dans mon cas, je vais utiliser un MenuItem pour effectuer différents tests de navigation. Le code que vous devez ajouter au Shell devrait ressembler à ceci :

<Shell...>
...
<Shell.MenuItemTemplate>
    <DataTemplate>
        <Grid
            ColumnDefinitions=".2*, .8*"
            HeightRequest="75"
            RowSpacing="0">
            <Rectangle
                x:Name="background"
                Grid.ColumnSpan="2"
                Fill="Black"
                Opacity=".5" />
            <Image
                HeightRequest="30"
                Source="{Binding FlyoutIcon}"
                VerticalOptions="Center" />
            <Label
                Grid.Column="1"
                Margin="20,0,0,0"
                FontSize="20"
                Text="{Binding Title}"
                TextColor="White"
                VerticalOptions="Center" />
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroupList>
                    <VisualStateGroup x:Name="CommonStates">
                        <VisualState x:Name="Normal">
                            <VisualState.Setters>
                                <Setter TargetName="background" Property="Rectangle.Fill" Value="Black" />
                            </VisualState.Setters>
                        </VisualState>
                        <VisualState x:Name="Selected">
                            <VisualState.Setters>
                                <Setter TargetName="background" Property="Rectangle.Fill" Value="DarkRed" />
                            </VisualState.Setters>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateGroupList>
            </VisualStateManager.VisualStateGroups>
        </Grid>
    </DataTemplate>
</Shell.MenuItemTemplate>
...
<FlyoutItem...>
<FlyoutItem...>

<MenuItem Clicked="Navigation_Clicked" Text="Navigate" IconImageSource="dotnet_bot.png"/>

</Shell>

Dans le code ci-dessus, j’ai ajouté un MenuItem avec du texte, une référence pour le Clicked événement et une icône. Pour que l’apparence soit la même que celle des éléments du menu, vous devez appliquer le même style de Shell.ItemTemplate à Shell.MenuItemTemplate.

Une fois le code XAML prêt, l’étape suivante consiste à accéder au code-behind de MyShell.xaml (bien que vous puissiez également le faire à partir d’un ViewModel), en particulier vers le Navigation_Clicked gestionnaire d’événements. Pour effectuer la navigation, nous devons utiliser le GoToAsync méthode qui fait partie de l’instance globale de Shell, à laquelle nous pouvons accéder comme suit :

private async void Navigation_Clicked(object sender, EventArgs e)
{
    await Shell.Current.GoToAsync("Route");
}

Dans le code ci-dessus, vous devez changer le mot Route à l’itinéraire vers lequel vous souhaitez naviguer. Dans une section précédente, j’avais mentionné que le tracé final d’un ContentPage est déterminé par les noms des pages ancêtres, nous pouvons donc accéder à WordCounter en utilisant l’itinéraire complet que je vous ai montré plus tôt :

await Shell.Current.GoToAsync("//ProductivityTools/TextTools/WordCounter");

De cette façon, en appuyant sur le MenuItem nous avons ajouté, la navigation vers le Compteur de mots outil sera réalisé.

Maintenant, vous vous demandez peut-être ce qui se passe si nous n’avons pas attribué de nom à l’un des éléments hiérarchiques supérieurs à ShellContent. Dans ce cas, le framework attribuera un nom générique à l’élément Shell, nous ne pourrons donc pas naviguer avec un itinéraire complet comme nous l’avons fait précédemment.

L’alternative est d’utiliser directement la route du ShellContent, à l’aide de trois barres obliques (///), comme suit :

await Shell.Current.GoToAsync("///WordCounter");

De cette façon, nous pouvons désormais effectuer une navigation correcte. Dans ce cas, nous devons avoir des itinéraires uniques pour chacun ShellContent pour éviter les problèmes de navigation.

Cinquième utilitaire : dictionnaire

Jusqu’à présent, nous avons créé des outils d’une seule page. Créons un nouvel exemple de type dictionnaire, qui sera composé d’une première page où l’utilisateur pourra saisir un mot à rechercher et d’une deuxième page pour les résultats de la recherche.

La première page que nous ajouterons s’appellera Dictionary.xaml et sera composé des éléments suivants :

  • UN VerticalStackLayout pour regrouper les champs
  • UN Label pour afficher du texte à l’utilisateur
  • UN RadBorder pour améliorer l’esthétique visuelle d’un RadEntry
  • UN RadEntry pour que l’utilisateur saisisse le mot à rechercher

Le RadBorder Le contrôle est très utile pour regrouper des éléments et en même temps, appliquer une bordure pour donner une apparence visuelle différente. Le code XAML est le suivant :

<VerticalStackLayout
    HorizontalOptions="Center"
    Spacing="15"
    VerticalOptions="Center">
    <Label
        FontSize="25"
        HorizontalOptions="Center"
        Text="Search in dictionary:"
        VerticalOptions="Center" />
    <telerik:RadBorder
        AutomationId="border"
        BorderColor="#2679FF"
        BorderThickness="3"
        CornerRadius="5">
        <telerik:RadEntry
            x:Name="searchTerm"
            Completed="RadEntry_Completed"
            FontSize="14"
            Placeholder="Telerik"
            PlaceholderColor="#99000000"
            WidthRequest="250" />
    </telerik:RadBorder>
    <Button Clicked="Search_Clicked" Text="Search!" />
</VerticalStackLayout>

La deuxième page s’appellera DictionaryDefinition.xaml et sera composé des contrôles suivants :

  • UN Grid comme conteneur principal, divisé en deux rangées : la première avec 20 % d’espace et la seconde avec les 80 % restants.
  • UN VerticalStackLayout pour l’en-tête, qui à son tour contiendra deux Label commandes pour afficher le mot et son expression phonétique.
  • Une seconde VerticalStackLayout qui contiendra une série de Label contrôles pour afficher le type de mot, les définitions, les synonymes et les antonymes.

Le code XAML de la page est le suivant :

<Grid RowDefinitions=".2*,.8*">
    <Grid Background="#4a4a4a">
        <VerticalStackLayout Spacing="15" VerticalOptions="Center">
            <Label
                FontSize="25"
                HorizontalOptions="Center"
                Text="{Binding Word}"
                TextColor="White"
                VerticalOptions="Center" />
            <Label
                FontSize="20"
                HorizontalOptions="Center"
                Text="{Binding Phonetic}"
                TextColor="#9c9a9a"
                VerticalOptions="Center" />
        </VerticalStackLayout>

    </Grid>
    <ScrollView Grid.Row="1">
        <VerticalStackLayout Margin="20">
            <Label
                FontAttributes="Bold"
                FontSize="16"
                Text="Noun"
                TextColor="#363535" />
            <Label Text="{Binding Definitions}" />
            <Label
                Margin="0,20,0,0"
                FontAttributes="Bold"
                FontSize="16"
                Text="Synonyms"
                TextColor="#363535" />
            <Label Text="{Binding Synonyms}" />
            <Label
                Margin="0,20,0,0"
                FontAttributes="Bold"
                FontSize="16"
                Text="Antonyms"
                TextColor="#363535" />
            <Label Text="{Binding Antonyms}" />
        </VerticalStackLayout>
    </ScrollView>
</Grid>

Une fois les deux pages XAML prêtes, n’oubliez pas d’ajouter à la hiérarchie la page principale qui appartiendra à la navigation Shell. Pour cet exemple, j’ai ajouté le Dictionary.xaml page vers le TextTools languette:

<Tab
    Title="Text Tools"
    Icon="dotnet_bot.png"
    Route="TextTools">
   ...
   <ShellContent
       Title="Dictionary"
       ContentTemplate="{DataTemplate UtilPages:Dictionary}"
       Icon="dotnet_bot.png" />
</Tab>

Grâce à cela, il est désormais possible de naviguer vers la nouvelle page dans l’interface graphique, comme indiqué ci-dessous :

Utilitaire de dictionnaire

Voyons maintenant comment accéder à la page de résultats.

Une fois que nous pouvons naviguer dans l’interface de l’outil Dictionnaire, l’étape suivante consiste à pouvoir accéder au DictionaryDefinition.xaml page qui ne fait pas partie du Shell. Pour y parvenir, nous devons enregistrer les pages qui ne font pas partie de la hiérarchie Shell initiale, mais que nous souhaitons utiliser pour les actions de navigation. Nous le ferons à travers le RegisterRoute méthode.

Le processus d’enregistrement doit être effectué avant que le Shell ne soit affiché à l’utilisateur. L’endroit idéal est donc dans App.xaml.cs comme suit:

public partial class App : Application
{
    public App()
    {
        InitializeComponent();

        RegisterRoutes();

        MainPage = new MyShell();
    }

    private void RegisterRoutes()
    {
        Routing.RegisterRoute("dictionarydefinition", typeof(DictionaryDefinition));
    }
}

Dans le code ci-dessus, vous pouvez voir que RegisterRoute reçoit un identifiant pour l’itinéraire avec lequel nous arriverons à la page (il peut s’agir de n’importe quel nom que vous souhaitez) et le type de page. Grâce à ce code, il sera possible de naviguer vers la page de détails.

Dans notre exemple, nous y parviendrons en allant au Dictionary.xaml.cs fichier et création d’une méthode appelée Search pour exécuter la navigation à travers le code suivant :

public partial class Dictionary : ContentPage
{
    public Dictionary()
    {
        InitializeComponent();
    }

    private void RadEntry_Completed(object sender, EventArgs e)
    {
        Search();
    }

    private void Search_Clicked(object sender, EventArgs e)
    {
        Search();
    }

    private async void Search()
    {
        if (!string.IsNullOrEmpty(searchTerm.Text))
        {
            await Shell.Current.GoToAsync($"dictionarydefinition");
        }
    }
}

Lorsque vous exécutez l’application, vous verrez que si vous entrez un mot dans la zone de recherche et appuyez sur le bouton Rechercher, vous accéderez à la page de détails. La page de détails est vide pour le moment car nous devons transmettre les informations du mot recherché, ce que nous ferons ensuite.

Dictionnaire Définition Page vide

Transmission d’informations aux pages de détails

Une exigence dans la plupart des applications mobiles est de pouvoir envoyer des informations d’une page à une autre. Cela se produit généralement lorsque vous souhaitez transmettre des données d’une page faisant partie de la hiérarchie Shell vers une page de détails.

Dans .NET MAUI, il existe plusieurs façons de transmettre des informations, que nous analyserons ci-dessous.

Envoi d’une seule donnée à la page de détails à l’aide de QueryProperty

Pour continuer notre démonstration, nous avons besoin que des informations circulent entre les pages. C’est pourquoi je vais modifier la méthode Search que nous avons vue précédemment, pour interroger un service gratuit qui nous permet d’obtenir les données d’un mot dans un dictionnaire, comme suit :

private async void Search()
{
    if (string.IsNullOrEmpty(searchTerm.Text))
    {
        return;
    }

    using (var client = new HttpClient())
    {
        string url = $"https://api.dictionaryapi.dev/api/v2/entries/en/{searchTerm.Text}";
        try
        {
            var response = await client.GetAsync(url);
            response.EnsureSuccessStatusCode();

            var content = await response.Content.ReadAsStringAsync();
            var definitions = JsonSerializer.Deserialize<List<DictionaryRecord>>(content);

            var firstDefinition = definitions?.FirstOrDefault();
            if (firstDefinition != null)
            {
                var word = firstDefinition.word;
                await Shell.Current.GoToAsync($"dictionarydefinition?word={word}");
            }
        }
        catch (Exception ex)
        {            
            Debug.WriteLine($"Error fetching definition: {ex.Message}");
        }
    }
}

Vous pouvez voir que le code ci-dessus appelle le service en utilisant HttpClienten effectuant une désérialisation et en obtenant le premier enregistrement renvoyé. Une fois ce premier enregistrement obtenu, le word la propriété qui représente le mot recherché est obtenue et envoyée en paramètre à la page enregistrée sous dictionarydefinition.

En revanche, la page qui recevra l’information (dans notre exemple DictionaryDefinition.xaml.cs) doit être prêt à recevoir les données que nous avons envoyées. Ceci est facile à réaliser, car cela nécessite uniquement une propriété où la valeur sera stockée, ainsi que l’ajout du QueryProperty attribut, qui reçoit le nom de la propriété de destination, ainsi que le nom du paramètre spécifié dans la requête à partir de laquelle la navigation a été effectuée (dans notre exemple word). Dans notre exemple, le code est le suivant :

[QueryProperty(nameof(Word), "word")]
public partial class DictionaryDefinition : ContentPage
{
    private string word;

    public string Word
    {
        get => word; 
        set
        {
            word = value;
            OnPropertyChanged(nameof(Word));
        }
    }
    public DictionaryDefinition()
	{
		InitializeComponent();
        BindingContext = this;
	}
}

Lorsque nous exécuterons à nouveau l’application, nous verrons correctement le mot recherché sur la page de détails :

Page de détails affichant la valeur envoyée

Passer des objets à la page de détails à l’aide de IQueryAttributable

Il peut arriver que vous souhaitiez envoyer plusieurs données à la page de détails. Dans cette situation, nous devrions utiliser le IQueryAttributable interface, ce qui nécessite la mise en œuvre de l’interface ApplyQueryAttributes méthode qui a la forme suivante :

public void ApplyQueryAttributes(IDictionary<string, object> query)
{
    throw new NotImplementedException();
}

Vous pouvez voir que la méthode reçoit un dictionnaire appelé queryoù la clé représente le nom du paramètre reçu de la page précédente, tandis que la valeur correspond à l’objet envoyé. Pour notre exemple, notre classe ressemblerait à ceci :

public partial class DictionaryDefinition : ContentPage, IQueryAttributable
{
    private string word;
    private string phonetic;
    private string definitions;
    private string synonyms;
    private string antonyms;

    public string Word
    {
        get => word;
        set
        {
            word = value;
            OnPropertyChanged(nameof(Word));
        }
    }

    public string Phonetic
    {
        get => phonetic;
        set
        {
            phonetic = value;
            OnPropertyChanged(nameof(Phonetic));
        }
    }

    public string Definitions
    {
        get => definitions;
        set
        {
            definitions = value;
            OnPropertyChanged(nameof(Definitions));
        }
    }
    public string Synonyms
    {
        get => synonyms;
        set
        {
            synonyms = value;
            OnPropertyChanged(nameof(Synonyms));
        }
    }
    public string Antonyms
    {
        get => antonyms;
        set
        {
            antonyms = value;
            OnPropertyChanged(nameof(Antonyms));
        }
    }

    public DictionaryDefinition()
    {
        InitializeComponent();
        BindingContext = this;
    }

    public void ApplyQueryAttributes(IDictionary<string, object> query)
    {
        var dictionaryRecord = query["definition"] as DictionaryRecord;
        Word = dictionaryRecord.word;
        Phonetic = dictionaryRecord.phonetics.FirstOrDefault().text;
        
        Definitions = string.Join("\n\n", dictionaryRecord.meanings.FirstOrDefault().definitions.Select(d => d.definition));

        Synonyms = string.Join("\n\n", dictionaryRecord.meanings.FirstOrDefault().synonyms);

        Antonyms = string.Join("\n\n", dictionaryRecord.meanings.FirstOrDefault().antonyms);
    }
}

Par contre, nous remplacerons le Search méthode dans le Dictionary.xaml.cs déposer comme suit :

private async void Search()
{
    if (string.IsNullOrEmpty(searchTerm.Text))
    {
        return;
    }

    using (var client = new HttpClient())
    {
        string url = $"https://api.dictionaryapi.dev/api/v2/entries/en/{searchTerm.Text}";

        try
        {
            var response = await client.GetAsync(url);
            response.EnsureSuccessStatusCode();

            var content = await response.Content.ReadAsStringAsync();
            var definitions = JsonSerializer.Deserialize<List<DictionaryRecord>>(content);

            var firstDefinition = definitions?.FirstOrDefault();
            if (firstDefinition != null)
            {
                var parameters = new Dictionary<string, object>
                {
                    { "definition", firstDefinition }
                };
                await Shell.Current.GoToAsync($"dictionarydefinition", parameters);
            }
        }
        catch (Exception ex)
        {                
            Debug.WriteLine($"Error fetching definition: {ex.Message}");
        }
    }
}

Dans le code ci-dessus, vous pouvez voir que l’on crée une variable de type Dictionary appelés paramètres, qui ont la même signature que le paramètre attendu par le ApplyQueryAttributes méthode, et que nous avons initialisé avec la clé definition et la valeur de firstDefinition.

Aussi, dans le GoToAsync méthode, nous avons remplacé le paramètre dans la chaîne par un paramètre de méthode. Avec cela, lors de l’exécution de l’application, nous pourrons voir les informations du mot recherché :

Page de détails affichant les informations d'un objet envoyé

Dans l’image ci-dessus, vous pouvez voir le résultat de la recherche d’un mot.

Ajout d’une fonction de recherche avec .NET MAUI Shell

.NET MAUI Shell comprend une fonction de recherche que nous pouvons ajouter pour effectuer des recherches d’informations. Pour l’activer, nous devons créer une classe qui hérite de SearchHandlercomme dans l’exemple suivant :

public class DataSearchHandler : SearchHandler
{
    public DataSearchHandler()
    {
        ItemsSource = new List<string>
        {
            "Apple",
            "Banana",
            "Cherry",
            "Date",
            "Grape",
            "Lemon",
            "Mango",
            "Orange",
            "Pineapple",
            "Strawberry",
            "Watermelon"
        };
    }
}

Le point le plus important à souligner est qu’une fois que nous héritons de SearchHandler, nous avons le ItemsSource propriété, qui contiendra les informations sur les éléments que nous souhaitons inclure dans la recherche.

Une fois que nous avons un SearchHandlernous pouvons l’ajouter à n’importe quelle page. À titre d’exemple, j’ai créé une nouvelle page appelée SearchPage.xaml avec le SearchHandler comme contenu :

<Shell.SearchHandler>
    <search:DataSearchHandler Placeholder="Search Item" ShowsResults="True" />
</Shell.SearchHandler>

Après avoir ajouté cette page à la structure Shell, nous pouvons voir que la recherche apparaît sur la nouvelle page :

Une ContentPage qui affiche un SearchHandler .NET MAUI Shell

Dans le cas où nous souhaitons traiter les informations de recherche, il existe deux méthodes qui nous seront très utiles, comme nous le voyons ci-dessous :

protected override void OnQueryChanged(string oldValue, string newValue)
{
    var items = OriginalList;

    if (string.IsNullOrWhiteSpace(newValue))
    {
        ItemsSource = items;
    }
    else
    {
        ItemsSource = items.Where(fruit => fruit.ToLower().Contains(newValue.ToLower())).ToList();
    }

}

protected override void OnItemSelected(object item)
{
    var fruit = item as string;
    Shell.Current.GoToAsync($"fruitdetails?name={fruit}");
}

La première méthode appelée OnQueryChanged sera exécuté dès qu’une modification sera apportée au terme de recherche. D’un autre côté, le OnItemSelected La méthode nous permettra de récupérer la valeur sélectionnée et de la traiter selon nos besoins. Dans notre exemple, j’ai mis une navigation vers une page de détails, en envoyant le paramètre.

Conclusion de la série

Avec cela, nous avons atteint la fin du Maîtriser le shell .NET MAUI série. Vous savez maintenant ce qu’est .NET MAUI Shell et comment l’utiliser en combinaison avec Progress Telerik pour .NET MAUI contrôles à votre avantage pour créer des applications plus rapidement.

Si vous n’avez pas encore essayé Telerik UI pour .NET MAUI par vous-même, il est accompagné d’un essai gratuit !

Essayez maintenant




Source link