Creare l'interfaccia accessibile e adattiva

Chiedere ora all'assistente di creare la pagina. Assegnare un nome ai controlli WinUI desiderati in modo da non sostituire un controllo da un altro framework dell'interfaccia utente.

Chiedere il codice XAML

Usare questa richiesta:

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.

Esaminare questi dettagli nel codice XAML generato.

Immissione dati

<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" />

Il valore visibile Header indica che l'input non si basa sul testo segnaposto come unica etichetta. UpdateSourceTrigger=PropertyChanged aggiorna il modello di visualizzazione mentre l'utente digita, in modo che il comando possa essere abilitato immediatamente.

Raccolta di attività

Definire un modello di dati tipizzato:

<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>

Utilizzare una funzione statica per includere il titolo dell'attività nel nome di automazione del pulsante Elimina, in modo che un lettore dello schermo possa distinguere le diverse righe:

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

Associare quindi la raccolta:

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

ListView fornisce lo scorrimento e la virtualizzazione dell'interfaccia utente. Avvolgerlo in un ScrollViewer interferisce con entrambi.

Altre informazioni: ListView e GridView eData binding con XAML.

Stato e stato vuoto

Usare InfoBar per un messaggio di stato non bloccante:

<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}" />

Usare una funzione statica per convertire il valore booleano vuoto in Visibility senza aggiungere una classe convertitore:

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}" />

Eseguire l'app prima di aggiungere dati e verificare che lo stato vuoto sia chiaro e che il pulsante Aggiungi attività sia disabilitato:

Task Tally nello stato vuoto con una nuova casella di testo dell'attività, un pulsante Aggiungi attività disabilitato e indicazioni per aggiungere una prima attività.

Layout adattivo

Usare gli stati di visualizzazione per spostare il pulsante sotto l'input:

<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>

Ridimensiona l'app in esecuzione oltre il breakpoint. Un'affermazione sulla responsività nel prompt non dimostra che il layout funzioni.

Ulteriori informazioni: Layout adattivi con XAML.

Connettere eventi specifici della visualizzazione

La pagina carica i dati persistenti una volta e delega le modifiche dell'attività al modello di visualizzazione:

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

I gestori di eventi con tipo restituito async void sono appropriati qui perché gli eventi XAML richiedono void. Altri metodi asincroni nell'app restituiscono Task.

Compilare ed eseguire

Compilare l'app, avviarla tramite il pacchetto e testare tutti i criteri di accettazione implementati:

  1. Aggiungere un'attività con il mouse.
  2. Aggiungere un'attività usando solo la tastiera.
  3. Completare ed eliminare un'attività.
  4. Chiudere e riavviare l'app per verificare la persistenza.
  5. Ridimensionare la finestra al di sotto e al di sopra di 640 pixel effettivi.
  6. Passare da un tema chiaro, scuro e a un tema a contrasto.