Dataniedziela, 9 sierpnia 2026 Czas07:11:09
← Aplikacje desktopowe (WPF)

Lekcja 15. Praca z plikami w aplikacji WPF (zapis i odczyt danych)

Trudny / egzaminacyjny

Po co się tego uczymy?

Dane trzymane tylko w pamięci (w kolekcji z poprzedniej lekcji) znikają, gdy zamkniesz aplikację. Żeby dane PRZETRWAŁY między uruchomieniami programu, trzeba je zapisać na dysku — do pliku tekstowego, JSON, albo (w kolejnej lekcji) do bazy danych. Ta umiejętność łączy WPF z tym, czego nauczyłeś się już w dziale programowania konsolowego o pracy z plikami.

Teoria

Zasady pracy z plikami w WPF są dokładnie takie same, jak w aplikacji konsolowej — klasy System.IO.File (do prostych operacji: WriteAllText, ReadAllText) oraz System.Text.Json (do zapisu/odczytu całych obiektów jako JSON) działają identycznie. Różnica polega tylko na TYM, KIEDY wywołujemy zapis/odczyt — zwykle: odczyt danych przy starcie okna (w konstruktorze albo zdarzeniu Loaded), a zapis przy zamykaniu okna (zdarzenie Closing) albo po każdej zmianie danych (np. po dodaniu elementu do listy).

WPF dodaje też własne, wygodne okna dialogowe do wyboru pliku przez użytkownika: OpenFileDialog (wybór pliku do otwarcia) i SaveFileDialog (wybór miejsca zapisu) z przestrzeni nazw Microsoft.Win32 — pokazują znajomy, systemowy eksplorator plików Windows, więc użytkownik czuje się jak w każdej innej aplikacji.

Typowy przepływ pracy: użytkownik klika "Zapisz jako" → otwiera się SaveFileDialog → program pobiera wybraną ścieżkę (dialog.FileName) → zapisuje dane pod tą ścieżką metodą File.WriteAllText (albo serializując do JSON).

Przykład z życia

Aplikacja notatnika: przy zamykaniu programu (zdarzenie Closing głównego okna) automatycznie zapisuje treść notatek do pliku na dysku, a przy następnym uruchomieniu (konstruktor okna) wczytuje tę treść z powrotem — użytkownik ma wrażenie, że aplikacja "pamięta" jego pracę, choć tak naprawdę za każdym razem od nowa odczytuje ją z pliku.

Kod (C#)

using System.IO;
using System.Text.Json;
using System.Windows;
using Microsoft.Win32;
using BibliotekaApp.Modele;
using System.Collections.ObjectModel;

namespace BibliotekaApp
{
    public partial class MainWindow : Window
    {
        private const string PlikDanych = "ksiazki.json";
        public ObservableCollection<Ksiazka> Ksiazki { get; set; } = new();

        public MainWindow()
        {
            InitializeComponent();
            WczytajDane();
            ListaKsiazek.ItemsSource = Ksiazki;

            // Zapisz dane automatycznie przy zamykaniu okna
            this.Closing += (s, e) => ZapiszDane();
        }

        private void WczytajDane()
        {
            if (!File.Exists(PlikDanych))
            {
                return; // pierwsze uruchomienie - jeszcze nie ma pliku
            }

            string json = File.ReadAllText(PlikDanych);
            var wczytane = JsonSerializer.Deserialize<Ksiazka[]>(json);

            if (wczytane != null)
            {
                foreach (var k in wczytane)
                {
                    Ksiazki.Add(k);
                }
            }
        }

        private void ZapiszDane()
        {
            string json = JsonSerializer.Serialize(Ksiazki, new JsonSerializerOptions { WriteIndented = true });
            File.WriteAllText(PlikDanych, json);
        }

        private void PrzyciskEksportuj_Click(object sender, RoutedEventArgs e)
        {
            var dialog = new SaveFileDialog
            {
                Filter = "Pliki tekstowe (*.txt)|*.txt",
                FileName = "eksport_ksiazek.txt"
            };

            if (dialog.ShowDialog() == true)
            {
                File.WriteAllText(dialog.FileName, string.Join("n", Ksiazki));
            }
        }
    }
}

Komentarz i wyjaśnienie kodu

WczytajDane sprawdza najpierw, czy plik w ogóle istnieje (File.Exists) — przy pierwszym uruchomieniu aplikacji plik jeszcze nie istnieje, więc metoda po prostu kończy działanie bez błędu. Jeśli plik istnieje, odczytuje jego zawartość jako tekst i zamienia go z powrotem na tablicę obiektów Ksiazka przez JsonSerializer.Deserialize.

Konstruktor okna podpina this.Closing += (s, e) => ZapiszDane(); — to zapisuje dane automatycznie za każdym razem, gdy użytkownik zamyka okno, bez potrzeby klikania osobnego przycisku "Zapisz".

PrzyciskEksportuj_Click pokazuje użycie SaveFileDialog: tworzymy go z filtrem typów plików i domyślną nazwą, wywołujemy ShowDialog() (to okno MODALNE, program czeka na wybór użytkownika — pamiętasz to z lekcji o oknach?), i dopiero jeśli użytkownik kliknął "Zapisz" (wynik true), zapisujemy dane pod wybraną przez niego ścieżką.

Ćwiczenie samodzielne

Dodaj do aplikacji z listą książek (z poprzedniej lekcji) automatyczny zapis do pliku JSON przy zamykaniu okna i odczyt przy starcie, dokładnie jak w przykładzie. Zamknij i uruchom aplikację ponownie — sprawdź, czy dane "przetrwały".

Zadania do pracy własnej

  1. Dodaj przycisk "Zapisz teraz", który wywołuje ZapiszDane() ręcznie, niezależnie od automatycznego zapisu przy zamykaniu — przydatne, gdyby aplikacja "zawiesiła się" i użytkownik nie zdążył jej normalnie zamknąć.

  2. Dodaj przycisk "Importuj z pliku" wykorzystujący OpenFileDialog do wyboru pliku JSON i wczytania z niego dodatkowych książek (dopisywanych do istniejącej listy, nie zastępujących jej).

  3. Zbuduj prosty edytor notatek (jedno duże pole TextBox z AcceptsReturn="True") z menu "Plik" zawierającym "Nowy", "Otwórz..." (OpenFileDialog) i "Zapisz jako..." (SaveFileDialog) — dokładnie jak w prawdziwym Notatniku Windows, z obsługą plików tekstowych.

Typowe błędy

Brak sprawdzenia File.Exists przed odczytem — próba odczytu nieistniejącego pliku przy pierwszym uruchomieniu aplikacji spowoduje wyjątek FileNotFoundException i awarię programu.

Zapominanie o sprawdzeniu wyniku ShowDialog() dla SaveFileDialog/OpenFileDialog — jeśli użytkownik kliknie "Anuluj", dialog.FileName będzie puste albo nieaktualne; zawsze trzeba sprawdzić, czy wynik to true, zanim użyjemy wybranej ścieżki.

Zapisywanie danych PRZED zamknięciem zamiast W TRAKCIE zamykania — jeśli zapis jest podpięty pod zły przycisk albo w ogóle nie jest wywoływany automatycznie, użytkownik może stracić dane, jeśli zamknie okno "krzyżykiem" bez kliknięcia "Zapisz".

Nawiązanie do egzaminu zawodowego

To bezpośrednie zastosowanie umiejętności z lekcji "Praca z plikami (TXT i JSON)" z działu programowania konsolowego, tym razem w kontekście prawdziwej aplikacji okienkowej — realizuje fragment INF.04.5.3 dotyczący budowy kompletnej, użytecznej aplikacji desktopowej.