Créer l’interface accessible et adaptative

Demandez maintenant à l’assistant de créer la page. Nommez les contrôles WinUI souhaités afin qu’il ne remplace pas un contrôle d’une autre infrastructure d’interface utilisateur.

Demander le code XAML

Ignorez cette invite :

Create MainPage.xaml for TaskTally.

- Use a TextBox with a visible Header and an Add task Button.
- Use ListView for the task collection. Don't wrap it in ScrollViewer.
- In each row, use a CheckBox, task title, and delete Button.
- Use InfoBar for save and error status.
- Use x:Bind and specify Mode for every binding that changes.
- For TextBox.Text, use Mode=TwoWay and
  UpdateSourceTrigger=PropertyChanged.
- Add accessible names and tooltips for icon-only commands.
- Add an empty state.
- At widths below 640 effective pixels, move the Add task button
  below the TextBox and stretch it.
- Use theme resources instead of hard-coded colors.
- Build and fix all XAML compiler and analyzer warnings.

Inspectez ces détails dans le code XAML généré.

Entrée de données

<TextBox
    x:Name="NewTaskTextBox"
    Header="New task"
    PlaceholderText="For example, review the generated XAML"
    Text="{x:Bind ViewModel.NewTaskTitle, Mode=TwoWay,
                  UpdateSourceTrigger=PropertyChanged}" />
<Button
    x:Name="AddTaskButton"
    Grid.Column="1"
    VerticalAlignment="Bottom"
    AutomationProperties.Name="Add task"
    Command="{x:Bind ViewModel.AddTaskCommand}"
    Content="Add task" />

Le libellé visible Header signifie que le champ de saisie ne s’appuie pas sur le texte d’espace réservé comme unique libellé. UpdateSourceTrigger=PropertyChanged met à jour le modèle de vue à mesure que l’utilisateur tape, afin que la commande puisse être activée immédiatement.

Collection de tâches

Définissez un modèle de données typé :

<DataTemplate x:Key="TaskTemplate" x:DataType="models:TaskItem">
    <Grid Padding="8" ColumnSpacing="12">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <CheckBox
            VerticalAlignment="Center"
            AutomationProperties.Name="{x:Bind Title}"
            Click="TaskCheckBox_Click"
            IsChecked="{x:Bind IsComplete, Mode=TwoWay}" />
        <TextBlock
            Grid.Column="1"
            VerticalAlignment="Center"
            Text="{x:Bind Title}" />
        <Button
            Grid.Column="2"
            VerticalAlignment="Center"
            AutomationProperties.Name="{x:Bind local:MainPage.DeleteTaskName(Title),
                                                Mode=OneWay}"
            Click="DeleteButton_Click"
            DataContext="{x:Bind}"
            ToolTipService.ToolTip="Delete task">
            <FontIcon Glyph="&#xE74D;" />
        </Button>
    </Grid>
</DataTemplate>

Utilisez une fonction statique pour inclure le titre de la tâche dans le nom d’automatisation du bouton De suppression, afin qu’un lecteur d’écran puisse faire la distinction entre les lignes :

public static string DeleteTaskName(string title) => $"Delete {title}";

Ensuite, liez la collection :

<ListView
    AutomationProperties.Name="Tasks"
    ItemTemplate="{StaticResource TaskTemplate}"
    ItemsSource="{x:Bind ViewModel.Tasks, Mode=OneWay}"
    SelectionMode="None" />

ListView fournit la virtualisation du défilement et de l’interface utilisateur. L’encapsuler dans un ScrollViewer interférent avec les deux.

En savoir plus : ListView et GridView et Liaison de données avec XAML.

État et état vide

Utiliser InfoBar pour un message d’état non bloquant :

<InfoBar
    IsClosable="True"
    IsOpen="{x:Bind ViewModel.IsStatusOpen, Mode=TwoWay}"
    Message="{x:Bind ViewModel.StatusMessage, Mode=OneWay}"
    Severity="{x:Bind local:MainPage.StatusSeverity(ViewModel.HasError),
                      Mode=OneWay}" />

Utilisez une fonction statique pour convertir le booléen Visibility à état vide sans ajouter de classe de convertisseur :

public static Visibility BoolToVisibility(bool value) =>
    value ? Visibility.Visible : Visibility.Collapsed;

public static InfoBarSeverity StatusSeverity(bool hasError) =>
    hasError ? InfoBarSeverity.Error : InfoBarSeverity.Success;
<TextBlock
    HorizontalAlignment="Center"
    VerticalAlignment="Center"
    Text="Add a task to get started."
    Visibility="{x:Bind local:MainPage.BoolToVisibility(ViewModel.HasNoTasks),
                        Mode=OneWay}" />

Exécutez l’application avant d’ajouter des données et vérifiez que l’état vide est clair et que le bouton Ajouter une tâche est désactivé :

Tâche Tally dans son état vide avec une nouvelle zone de texte de tâche, un bouton Ajouter une tâche désactivé et des conseils pour ajouter une première tâche.

Disposition adaptative

Utilisez des états visuels pour déplacer le bouton sous l’entrée :

<VisualStateManager.VisualStateGroups>
    <VisualStateGroup>
        <VisualState x:Name="Narrow">
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="0" />
            </VisualState.StateTriggers>
            <VisualState.Setters>
                <Setter Target="AddTaskButton.(Grid.Row)" Value="1" />
                <Setter Target="AddTaskButton.(Grid.Column)" Value="0" />
                <Setter Target="AddTaskButton.HorizontalAlignment" Value="Stretch" />
            </VisualState.Setters>
        </VisualState>
        <VisualState x:Name="Standard">
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="640" />
            </VisualState.StateTriggers>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

Redimensionnez l’application en cours d’exécution au-delà du point de rupture. Le fait d’affirmer dans un prompt qu’un contenu est responsive ne prouve pas que la mise en page fonctionne.

En savoir plus : dispositions réactives avec XAML.

Connecter des événements spécifiques à l’affichage

La page charge les données persistantes une fois et délègue les modifications apportées au modèle d’affichage :

public sealed partial class MainPage : Page
{
    public MainPageViewModel ViewModel { get; } = new();

    public MainPage()
    {
        InitializeComponent();
        Loaded += MainPage_Loaded;
    }

    private async void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        Loaded -= MainPage_Loaded;
        await ViewModel.InitializeAsync();
        NewTaskTextBox.Focus(FocusState.Programmatic);
    }

    private async void TaskCheckBox_Click(object sender, RoutedEventArgs e)
    {
        await ViewModel.TaskCompletionChangedAsync();
    }

    private async void DeleteButton_Click(object sender, RoutedEventArgs e)
    {
        if (sender is Button { DataContext: TaskItem task })
        {
            await ViewModel.DeleteTaskCommand.ExecuteAsync(task);
        }
    }
}

Les gestionnaires d’événements avec un type de async void retour sont appropriés ici, car les événements XAML nécessitent void. D’autres méthodes asynchrones dans l’application retournent Task.

Compiler et exécuter

Générez l’application, la lancez-la via son package et testez chaque critère d’acceptation qui est désormais implémenté :

  1. Ajoutez une tâche avec la souris.
  2. Ajoutez une tâche à l’aide uniquement du clavier.
  3. Terminez et supprimez une tâche.
  4. Fermez et redémarrez l’application pour vérifier la persistance.
  5. Redimensionnez la fenêtre ci-dessous et au-dessus de 640 pixels effectifs.
  6. Basculez entre un thème clair, sombre et contrasté.