De toegankelijke en adaptieve interface bouwen

Vraag nu de assistent om de pagina te maken. Benoem de gewenste WinUI-besturingselementen, zodat deze niet worden vervangen door een besturingselement uit een ander UI-framework.

Vraag om de XAML

Gebruik deze prompt:

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.

Inspecteer deze details in de gegenereerde XAML.

Gegevensinvoer

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

De zichtbare Header betekent dat het invoerveld niet uitsluitend afhankelijk is van placeholdertekst als enige label. UpdateSourceTrigger=PropertyChanged werkt het viewmodel bij terwijl de gebruiker typt, zodat de opdracht onmiddellijk kan worden ingeschakeld.

Taakverzameling

Definieer een getypte gegevenssjabloon:

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

Gebruik een statische functie om de taaktitel op te nemen in de automatiseringsnaam van de knop Verwijderen, zodat een schermlezer onderscheid kan maken tussen rijen:

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

Bind vervolgens de verzameling:

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

ListView biedt schuiffunctionaliteit en gebruikersinterfacevirtualisatie. Het inpakken ervan in een ScrollViewer verstoort beide.

Meer informatie: ListView en GridView en Gegevensbinding met XAML.

Status en lege toestand

Gebruiken InfoBar voor een niet-blokkeringsstatusbericht:

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

Gebruik een statische functie om de Booleaanse waarde voor de lege status naar Visibility te converteren zonder een converterklasse toe te voegen:

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

Voer de app uit voordat u gegevens toevoegt en controleer of de lege status leeg is en de knop Taak toevoegen is uitgeschakeld:

Task Tally in lege toestand met een tekstvak voor een nieuwe taak, een uitgeschakelde knop ‘Taak toevoegen’ en aanwijzingen om een eerste taak toe te voegen.

Adaptieve indeling

Gebruik visuele statussen om de knop onder de invoer te verplaatsen:

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

Pas het formaat van de actieve toepassing aan over het breakpoint heen. Een responsieve claim in een prompt is geen bewijs dat de indeling werkt.

Meer informatie: Responsieve indelingen met XAML.

Aanzichtspecifieke gebeurtenissen koppelen

De pagina laadt persistente gegevens eenmaal en delegeert taakwijzigingen aan het weergavemodel:

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

Gebeurtenis-handlers met een async void retourtype zijn hier geschikt omdat XAML-gebeurtenissen vereisen void. Andere asynchrone methoden in de app retourneren Task.

Bouwen en uitvoeren

Bouw de app, start deze via het bijbehorende pakket en test elk acceptatiecriterium dat nu is geïmplementeerd:

  1. Voeg een taak toe met de muis.
  2. Voeg een taak toe met alleen het toetsenbord.
  3. Een taak voltooien en verwijderen.
  4. Sluit de app en start deze opnieuw om persistentie te controleren.
  5. Pas het formaat van het venster onder en boven de 640 effectieve pixels aan.
  6. Schakelen tussen licht, donker en een contrastthema.