Erstellen der barrierefreien und adaptiven Schnittstelle

Bitten Sie nun den Assistenten, die Seite zu erstellen. Benennen Sie die gewünschten WinUI-Steuerelemente, damit es kein Steuerelement aus einem anderen Benutzeroberflächenframework ersetzt.

Bitten Sie um den XAML-Code

Verwenden Sie diese Eingabeaufforderung:

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.

Überprüfen Sie diese Details im generierten XAML.

Dateneingabe

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

Das Sichtbare Header bedeutet, dass die Eingabe nicht auf Platzhaltertext als einzige Beschriftung angewiesen ist. UpdateSourceTrigger=PropertyChanged aktualisiert das Ansichtsmodell während der Benutzereingabe, sodass der Befehl sofort aktiviert werden kann.

Aufgabensammlung

Definieren Sie eine typisierte Datenvorlage:

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

Verwenden Sie eine statische Funktion, um den Aufgabentitel in den Automatisierungsnamen der Löschschaltfläche einzuschließen, damit eine Bildschirmsprachausgabe zwischen Zeilen unterscheiden kann:

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

Binden Sie dann die Sammlung:

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

ListView stellt Bildlauf und UI-Virtualisierung bereit. Das Einbetten in ein ScrollViewer beeinträchtigt beide.

Weitere Informationen: ListView und GridView undDatenbindung mit XAML.

Status und leerer Zustand

Verwenden Sie InfoBar für eine nicht blockierende Statusmeldung:

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

Verwenden Sie eine statische Funktion, um den booleschen Visibility Zustand in einen leeren Zustand zu konvertieren, ohne eine Konverterklasse hinzuzufügen:

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

Führen Sie die App vor dem Hinzufügen von Daten aus, und vergewissern Sie sich, dass der leere Zustand gelöscht ist, und die Schaltfläche " Aufgabe hinzufügen " ist deaktiviert:

Task Tally im leeren Zustand mit einem Textfeld für eine neue Aufgabe, einer deaktivierten Schaltfläche „Aufgabe hinzufügen“ und einem Hinweis zum Hinzufügen der ersten Aufgabe.

Adaptives Layout

Verwenden Sie visuelle Zustände, um die Schaltfläche unter der Eingabe zu verschieben:

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

Ändern Sie die Größe der laufenden App über den Breakpoint hinweg. Die Behauptung in einem Prompt, dass das Layout responsiv ist, ist kein Beleg dafür, dass es tatsächlich funktioniert.

Weitere Informationen: Dynamische Layouts mit XAML.

Ansichtsspezifische Ereignisse verbinden

Die Seite lädt gespeicherte Daten einmal und delegiert Aufgabenänderungen an das Ansichtsmodell:

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

Ereignishandler mit einem async void-Rückgabetyp sind hier angemessen, da XAML-Ereignisse void erfordern. Andere asynchrone Methoden in der App geben zurück Task.

Erstellen und Ausführen

Erstellen Sie die App, starten Sie sie über ihr Paket, und testen Sie jedes Akzeptanzkriterium, das jetzt implementiert wird:

  1. Fügen Sie eine Aufgabe mit der Maus hinzu.
  2. Fügen Sie eine Aufgabe nur über die Tastatur hinzu.
  3. Abschließen und Löschen einer Aufgabe.
  4. Schließen Sie die App, und starten Sie sie neu, um die Persistenz zu überprüfen.
  5. Ändern Sie die Größe des Fensters unter und über 640 effektiven Pixeln.
  6. Wechseln zwischen hellem, dunklem und kontrastreichem Design.