액세스 가능하고 적응형 인터페이스 빌드

이제 도우미에게 페이지를 만들도록 요청합니다. 다른 UI 프레임워크의 컨트롤을 대체하지 않도록 원하는 WinUI 컨트롤의 이름을 지정합니다.

XAML 요청하기

다음 프롬프트를 사용합니다.

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.

생성된 XAML에서 이러한 세부 정보를 검사합니다.

데이터 입력

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

표시 Header 됨은 입력이 자리 표시자 텍스트를 유일한 레이블로 사용하지 않음을 의미합니다. UpdateSourceTrigger=PropertyChanged는 사용자가 입력하는 동안 뷰 모델을 업데이트하므로 명령이 즉시 활성화될 수 있습니다.

작업 모음

형식화된 데이터 템플릿을 정의합니다.

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

정적 함수를 사용하여 삭제 단추의 자동화 이름에 작업 제목을 포함하면 화면 읽기 프로그램에서 행을 구분할 수 있습니다.

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

그런 다음 컬렉션을 바인딩합니다.

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

ListView 는 스크롤 및 UI 가상화를 제공합니다. 이를 ScrollViewer로 감싸면 둘 다를 방해합니다.

자세한 정보: XAML을 사용한 ListView 및 GridView 및 데이터 바인딩.

상태 및 빈 상태

차단 해제 상태 메시지에 사용합니다 InfoBar .

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

정적 함수를 사용하여 변환기 클래스를 추가하지 않고 빈 상태 Boolean 값을 Visibility로 변환합니다:

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

데이터를 추가하기 전에 앱을 실행하고 빈 상태가 명확하고 작업 추가 단추가 비활성화되어 있는지 확인합니다.

새 작업 텍스트 상자, 비활성화된 작업 추가 단추 및 첫 번째 작업을 추가하기 위한 지침이 포함된 빈 상태의 작업 집계입니다.

적응형 레이아웃

시각적 상태를 사용하여 입력 아래의 단추를 이동합니다.

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

중단점에서 실행 중인 앱의 크기를 조정합니다. 프롬프트의 반응형 클레임은 레이아웃이 작동한다는 증거가 아닙니다.

자세한 정보: XAML을 사용한 반응형 레이아웃.

보기별 이벤트 연결

페이지는 지속형 데이터를 한 번 로드하고 뷰 모델에 태스크 변경 내용을 위임합니다.

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

반환 형식의 async void 이벤트 처리기는 XAML 이벤트가 필요하기 때문에 여기에 적합합니다 void. 앱의 다른 비동기 메서드는 Task를 반환합니다.

빌드 및 실행

앱을 빌드하고, 패키지를 통해 시작하고, 이제 구현된 모든 수락 조건을 테스트합니다.

  1. 마우스를 사용하여 작업을 추가합니다.
  2. 키보드만 사용하여 작업을 추가합니다.
  3. 작업을 완료하고 삭제합니다.
  4. 앱을 닫고 다시 시작하여 지속성을 확인합니다.
  5. 창 크기를 640 유효 픽셀보다 작게 또는 크게 조정하세요.
  6. 라이트, 다크 및 대비 테마 간에 전환합니다.