Hinzufügen von Daten und Persistenz

Fragen Sie in diesem Schritt den Assistenten nach den nichtvisualen Teilen der App. Durch die Trennung dieser Änderung vom XAML-Code lassen sich Compilerfehler und Entwurfsprobleme einfacher isolieren.

Anfordern eines Modells und eines Speicherdiensts

Verwenden Sie diese Eingabeaufforderung:

Add the data layer for TaskTally.

- Create a TaskItem model with an ID, title, and completion state.
- Use MVVM Toolkit observable properties.
- Create a TaskStorage service that serializes the task list to JSON.
- Store tasks in ApplicationData.Current.LocalFolder because this is
  a packaged WinUI 3 app.
- Return an empty list when the file doesn't exist.
- Serialize save operations so rapid changes don't write the file concurrently.
- Surface real read and write errors to the caller.
- Build the project after the change.

Vergleichen Sie das generierte Modell mit dieser Implementierung:

using CommunityToolkit.Mvvm.ComponentModel;

namespace TaskTally.Models;

public partial class TaskItem : ObservableObject
{
    public Guid Id { get; init; } = Guid.NewGuid();

    [ObservableProperty]
    public partial string Title { get; set; } = string.Empty;

    [ObservableProperty]
    public partial bool IsComplete { get; set; }
}

Das [ObservableProperty] Attribut generiert öffentliche Eigenschaften und Änderungsbenachrichtigungen. Die Klasse muss sein partial , damit der Quellgenerator des MVVM-Toolkits diesen Code hinzufügen kann.

Der Speicherdienst sollte wie folgt aussehen:

using System.Text.Json;
using System.Threading;
using TaskTally.Models;
using Windows.Storage;

namespace TaskTally.Services;

internal static class TaskStorage
{
    private const string FileName = "tasks.json";
    private static readonly SemaphoreSlim SaveLock = new(1, 1);

    public static async Task<IReadOnlyList<TaskItem>> LoadAsync()
    {
        StorageFile? file =
            await ApplicationData.Current.LocalFolder.TryGetItemAsync(FileName) as StorageFile;

        if (file is null)
        {
            return [];
        }

        string json = await FileIO.ReadTextAsync(file);
        return JsonSerializer.Deserialize<List<TaskItem>>(json) ?? [];
    }

    public static async Task SaveAsync(IEnumerable<TaskItem> tasks)
    {
        await SaveLock.WaitAsync();
        try
        {
            string json = JsonSerializer.Serialize(tasks, new JsonSerializerOptions
            {
                WriteIndented = true
            });

            StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(
                FileName,
                CreationCollisionOption.ReplaceExisting);

            await FileIO.WriteTextAsync(file, json);
        }
        finally
        {
            SaveLock.Release();
        }
    }
}

ApplicationData.Current.LocalFolder ist hier angemessen, da die App die Paketidentität aufweist. SaveLock verhindert, dass Add-, Delete- und Abschlussvorgänge gleichzeitig dieselbe Datei schreiben. Wenn Sie das Verteilungsmodell später ändern, überprüfen Sie die Paketidentitätsannahme erneut, anstatt diese Speicherauswahl automatisch zu kopieren.

Weitere Informationen: Speichern und Abrufen von Einstellungen und anderen App-Daten und ApplicationData.LocalFolder.

Anfordern des Ansichtsmodells

Verwenden Sie als Nächstes diese Eingabeaufforderung:

Add MainPageViewModel.

- Expose an ObservableCollection<TaskItem>.
- Add observable properties for the new-task text and status message.
- Add asynchronous commands to add and delete tasks.
- Disable AddTaskCommand when the title is blank.
- Load tasks once when the page starts.
- Save after add, delete, or completion changes.
- Expose a summary and empty-state property for x:Bind.
- Catch storage exceptions only where the UI can report them.
- Build and fix all warnings.

Die wichtigsten Muster, die in der Antwort untersucht werden sollen, sind:

public ObservableCollection<TaskItem> Tasks { get; } = [];

[ObservableProperty]
public partial string NewTaskTitle { get; set; } = string.Empty;

public string Summary =>
    Tasks.Count == 0
        ? "No tasks yet"
        : $"{Tasks.Count(task => !task.IsComplete)} of {Tasks.Count} remaining";

[RelayCommand(CanExecute = nameof(CanAddTask))]
private async Task AddTaskAsync()
{
    string title = NewTaskTitle.Trim();
    Tasks.Add(new TaskItem { Title = title });
    NewTaskTitle = string.Empty;
    RefreshTaskState();
    await SaveAsync($"Added \"{title}\".");
}

[RelayCommand]
private async Task DeleteTaskAsync(TaskItem task)
{
    Tasks.Remove(task);
    RefreshTaskState();
    await SaveAsync($"Deleted \"{task.Title}\".");
}

private bool CanAddTask() => !string.IsNullOrWhiteSpace(NewTaskTitle);

partial void OnNewTaskTitleChanged(string value)
{
    AddTaskCommand.NotifyCanExecuteChanged();
}

Überprüfen Sie, ob der Assistent die asynchrone Arbeit mit .Wait() oder .Resultnicht blockiert. Speichervorgänge sollten abgewartet werden.

Das ViewModel kann Speicherausnahmen abfangen, wenn es sie in eine Nachricht für die Seite umwandelt:

private async Task SaveAsync(string successMessage)
{
    try
    {
        await TaskStorage.SaveAsync(Tasks);
        ShowStatus(successMessage);
    }
    catch (Exception ex)
    {
        ShowStatus($"Changes couldn't be saved: {ex.Message}");
    }
}

Dies ist kein stillschweigender Fallback: Der Benutzer erhält eine Fehlermeldung, und das Viewmodel signalisiert nicht, dass der Speichervorgang erfolgreich war.

Erstellen und Prüfen

Für x64 erstellen. Behandeln von Warnungen als Arbeitsaufgaben, insbesondere Bindungen und Warnungen mit Nullverweis:

dotnet build -p:Platform=x64

Bitten Sie den Assistenten, jedes hinzugefügte Paket zu erläutern. Für diese App sollte kein neues Paket über die Verweise auf Windows App SDK und MVVM Toolkit in der Vorlage hinaus benötigt werden.