Skapa det tillgängliga och anpassningsbara gränssnittet

Be nu assistenten att skapa sidan. Namnge de WinUI-kontroller som du vill använda så att den inte ersätter en kontroll från ett annat gränssnittsramverk.

Fråga efter XAML

Använd den här uppmaningen:

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.

Granska den här informationen i den genererade XAML-filen.

Dataregistrering

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

Den synliga etiketten Header innebär att inmatningsfältet inte enbart använder platshållartext som sin enda etikett. UpdateSourceTrigger=PropertyChanged uppdaterar vymodellen när användaren skriver, så att kommandot kan aktiveras direkt.

Uppgiftssamling

Definiera en typ av datamall:

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

Använd en statisk funktion för att inkludera aktivitetsrubriken i borttagningsknappens automationsnamn, så att en skärmläsare kan skilja mellan rader:

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

Bind sedan samlingen:

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

ListView tillhandahåller rullnings- och gränssnittsvirtualisering. Att omsluta den i en ScrollViewer påverkar båda negativt.

Läs mer: ListView- och GridView- och databindning med XAML.

Status och tomt tillstånd

Använd InfoBar för ett statusmeddelande som inte blockeras:

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

Använd en statisk funktion för att konvertera det booleska värdet för det tomma tillståndet till Visibility utan att lägga till en konverterarklass:

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

Kör appen innan du lägger till data och bekräfta att det tomma tillståndet är klart och att knappen Lägg till uppgift är inaktiverad:

Task Tally i tomt läge med en textruta för ny uppgift, den inaktiverade knappen Lägg till uppgift och vägledning om att lägga till en första uppgift.

Anpassningsbar layout

Använd visuella tillstånd för att flytta knappen under indata:

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

Ändra storlek på den app som körs förbi brytpunkten. Ett påstående om responsivitet i en prompt är inget bevis för att layouten fungerar.

Läs mer: Dynamiska layouter med XAML.

Anslut vyspecifika händelser

Sidan läser in beständiga data en gång och delegerar aktivitetsändringar till vymodellen:

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

Händelsehanterare med returtyp async void är lämpliga här eftersom XAML-händelser kräver void. Andra asynkrona metoder i appen returnerar Task.

Bygg och kör

Skapa appen, starta den via paketet och testa varje godkännandevillkor som nu implementeras:

  1. Lägg till en uppgift med musen.
  2. Lägg till en uppgift med endast tangentbordet.
  3. Slutför och ta bort en uppgift.
  4. Stäng och starta om appen för att verifiera beständighet.
  5. Ändra storlek på fönstret till mindre än och större än 640 effektiva pixlar.
  6. Växla mellan ljust, mörkt och ett kontrasttema.