Pliki i okna dialogowe
Do tej pory wszystko, co użytkownik wpisał, znikało po zamknięciu programu. W tej lekcji program zacznie zapisywać dane do pliku i wczytywać je z powrotem — przez znajome okna systemu Windows „Otwórz” i „Zapisz jako”. Poznasz też try/catch, bo przy plikach błędy zdarzają się naprawdę: plik jest otwarty w innym programie, dysk jest tylko do odczytu, ktoś podał zły format.
Czego się dziś nauczysz
- Zapiszesz i odczytasz plik tekstowy w całości oraz wiersz po wierszu.
- Obsłużysz błędy wejścia-wyjścia przez
try/catch. - Pozwolisz użytkownikowi wybrać plik w oknie „Otwórz” i „Zapisz jako”.
- Zapiszesz listę obiektów w pliku CSV i wczytasz ją z powrotem, pomijając błędne wiersze.
- Zapytasz o zapis niezapisanych zmian przed zamknięciem okna.
Klasa File
Klasa File z przestrzeni nazw System.IO ma gotowe metody, które jednym wywołaniem otwierają plik, zapisują lub czytają całość i zamykają go:
using System.IO;
// cały tekst naraz
File.WriteAllText("notatka.txt", Notatka.Text); // tworzy albo nadpisuje
string tekst = File.ReadAllText("notatka.txt");
// wiersz po wierszu — tablica napisów
string[] wiersze = File.ReadAllLines("oceny.txt");
File.WriteAllLines("oceny.txt", wiersze); // tablica albo List<string>
// dopisanie na końcu (np. dziennik zdarzeń)
File.AppendAllText("log.txt", $"{DateTime.Now:T} uruchomiono\n");
bool jest = File.Exists("notatka.txt");
// części ścieżki
string pelna = @"C:\Users\Ola\Documents\oceny.txt";
string nazwa = Path.GetFileName(pelna); // "oceny.txt"
string rozszerzenie = Path.GetExtension(pelna); // ".txt"
Sama nazwa pliku (bez folderu) oznacza plik obok programu — w folderze bin\Debug\net10.0-windows. Pliki użytkownika lepiej zapisywać tam, gdzie on wskaże, dlatego zwykle używa się okien dialogowych. Polskie znaki są zapisywane w kodowaniu UTF-8.
Znak @ przed napisem wyłącza specjalne znaczenie \ — inaczej "C:\new" zawierałoby znak nowej linii (\n).
try i catch
Do tej pory błędnym danym zapobiegaliśmy przez TryParse i warunki. Przy plikach nie da się wszystkiego sprawdzić z góry: plik mógł zostać usunięty sekundę temu albo jest zablokowany przez Excela. Metody File zgłaszają wtedy wyjątek, który bez obsługi kończy program. Obsługuje się go tak:
try
{
// kod, który może się nie udać
string tekst = File.ReadAllText(sciezka);
Notatka.Text = tekst;
Info.Text = "Wczytano."; // wykona się tylko, gdy odczyt się udał
}
catch (Exception ex)
{
// tu trafiamy, gdy cokolwiek w try zgłosiło wyjątek
MessageBox.Show($"Nie udało się otworzyć pliku:\n{ex.Message}", "Błąd",
MessageBoxButton.OK, MessageBoxImage.Error);
}
Gdy w bloku try wystąpi wyjątek, reszta tego bloku jest pomijana, a program przechodzi do catch. ex.Message zawiera opis błędu (po polsku, jeśli Windows jest po polsku). Exception łapie każdy wyjątek; można też łapać węższe rodzaje, np. IOException albo UnauthorizedAccessException (brak uprawnień).
Nie zamiast sprawdzania
try/catch służy do sytuacji, których program nie może przewidzieć. Liczby z pola tekstowego nadal sprawdzaj przez TryParse — to prostsze i czytelniejsze niż łapanie wyjątku z int.Parse.
OpenFileDialog
Okna dialogowe plików są w przestrzeni nazw Microsoft.Win32. Tworzy się je jak zwykły obiekt, ustawia właściwości i pokazuje metodą ShowDialog, która czeka, aż użytkownik zamknie okno:
using Microsoft.Win32;
OpenFileDialog okno = new OpenFileDialog();
okno.Title = "Wybierz plik z ocenami";
okno.Filter = "Pliki tekstowe (*.txt)|*.txt|Wszystkie pliki (*.*)|*.*";
if (okno.ShowDialog() == true) // bool? — true tylko po kliknięciu „Otwórz”
{
string sciezka = okno.FileName; // pełna ścieżka wybranego pliku
Notatka.Text = File.ReadAllText(sciezka);
}
📁 Dokumenty
📁 Pobrane
systemowe okno „Otwórz” — lista typów plików pochodzi z Filter
Filter to pary opis|wzorzec rozdzielone znakiem |. Kilka wzorców w jednej parze oddziela się średnikiem: "Obrazy|*.png;*.jpg". InitialDirectory ustawia folder startowy, a Multiselect = true pozwala wybrać wiele plików (lista w FileNames). Podobnie działa OpenFolderDialog — wybór folderu (FolderName).
SaveFileDialog
SaveFileDialog okno = new SaveFileDialog();
okno.Filter = "Pliki tekstowe (*.txt)|*.txt";
okno.FileName = "notatka.txt"; // podpowiedź nazwy
if (okno.ShowDialog() == true)
{
File.WriteAllText(okno.FileName, Notatka.Text);
}
Okno „Zapisz jako” samo pyta, czy nadpisać istniejący plik, i samo dopisuje rozszerzenie z filtru, gdy użytkownik go nie poda. Ani OpenFileDialog, ani SaveFileDialog nie czyta i nie zapisuje pliku — tylko zwraca ścieżkę. Pracę z plikiem wykonuje File.
Plik CSV — lista obiektów w pliku
Najprostszy sposób na zapis listy obiektów: jeden obiekt w jednym wierszu, pola rozdzielone średnikiem. Taki plik (CSV) otworzy też Excel. Zapis to pętla budująca wiersze, odczyt — Split(';') każdego wiersza i sprawdzenie pól:
Nazwisko;Imie;Klasa
Nowak;Anna;2TI
Kowalski;Jan;2TI
foreach (string wiersz in File.ReadAllLines(sciezka))
{
string[] pola = wiersz.Split(';');
if (pola.Length != 3)
{
continue; // zły wiersz — pomijamy
}
uczniowie.Add(new Uczen(pola[0], pola[1], pola[2]));
}
Średnik w danych zepsułby podział na pola, dlatego formularz w przykładzie nie przyjmuje nazwy ze średnikiem. Pierwszy wiersz z nazwami kolumn (nagłówek) pomaga człowiekowi, który otworzy plik, ale przy odczycie trzeba go pominąć.
Niezapisane zmiany
Program z plikami potrzebuje dwóch pól: ścieżki bieżącego pliku (albo null, gdy jeszcze go nie zapisano) i flagi „są zmiany”. Każda edycja ustawia flagę, zapis i odczyt ją zerują. Przed porzuceniem danych — nowy plik, otwarcie innego, zamknięcie okna (Closing z lekcji 8) — program pyta:
| Odpowiedź | Co robi program |
|---|---|
| Tak | Zapisuje. Jeśli zapis się nie uda albo użytkownik anuluje okno „Zapisz jako” — zostaje. |
| Nie | Porzuca zmiany i kontynuuje. |
| Anuluj | Nic nie robi — wraca do edycji. |
Gwiazdka w tytule okna („ewidencja.csv *”) to powszechny znak niezapisanych zmian.
Ewidencja sprzętu w pracowni
Opiekun pracowni prowadzi spis sprzętu: numer inwentarzowy, nazwa, sala, stan. Ewidencję można zapisać do pliku CSV, otworzyć ponownie i wyeksportować raport sprzętu do naprawy jako plik tekstowy. Program pilnuje niezapisanych zmian. W folderze projektu, w podfolderze dane, jest gotowy plik sala14.csv z jednym celowo błędnym wierszem.
wczytany plik sala14.csv (wiersz z drukarką był błędny i został pominięty), w trakcie dodawania nowej pozycji — gwiazdka w tytule oznacza niezapisane zmiany
namespace Lekcja38;
// Jedna pozycja ewidencji sprzętu w pracowni
public class Sprzet
{
public string NrInwentarzowy { get; set; }
public string Nazwa { get; set; }
public int Sala { get; set; }
public bool Sprawny { get; set; }
public Sprzet(string nrInwentarzowy, string nazwa, int sala, bool sprawny)
{
NrInwentarzowy = nrInwentarzowy;
Nazwa = nazwa;
Sala = sala;
Sprawny = sprawny;
}
// Wiersz pliku CSV: KOM-014;Komputer Dell;14;tak
public string DoCsv()
{
string stan = Sprawny ? "tak" : "nie";
return $"{NrInwentarzowy};{Nazwa};{Sala};{stan}";
}
public override string ToString()
{
string stan = Sprawny ? "" : " ⚠ do naprawy";
return $"{NrInwentarzowy} {Nazwa} (s. {Sala}){stan}";
}
}
<Window x:Class="Lekcja38.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Ewidencja sprzętu" Width="620" Height="460" MinWidth="520" MinHeight="340"
WindowStartupLocation="CenterScreen" Closing="Window_Closing" KeyDown="Window_KeyDown">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_Plik">
<MenuItem Header="_Nowa ewidencja" Click="Nowy_Click"/>
<MenuItem Header="_Otwórz…" InputGestureText="Ctrl+O" Click="Otworz_Click"/>
<MenuItem Header="_Zapisz" InputGestureText="Ctrl+S" Click="Zapisz_Click"/>
<MenuItem Header="Zapisz _jako…" Click="ZapiszJako_Click"/>
<Separator/>
<MenuItem Header="_Raport do naprawy…" Click="Raport_Click"/>
<Separator/>
<MenuItem Header="Za_kończ" Click="Zakoncz_Click"/>
</MenuItem>
</Menu>
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem>
<TextBlock x:Name="Plik" Text="(nowy plik)"/>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<TextBlock x:Name="Liczba"/>
</StatusBarItem>
</StatusBar>
<!-- formularz dodawania -->
<GroupBox DockPanel.Dock="Top" Header="Nowa pozycja" Margin="10,8,10,4" Padding="6">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="110"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Text="Nr inwentarzowy"/>
<TextBlock Grid.Column="1" Text="Nazwa" Margin="6,0,0,0"/>
<TextBlock Grid.Column="2" Text="Sala" Margin="6,0,0,0"/>
<TextBox x:Name="Nr" Grid.Row="1" Grid.Column="0"/>
<TextBox x:Name="Nazwa" Grid.Row="1" Grid.Column="1" Margin="6,0,0,0"/>
<TextBox x:Name="Sala" Grid.Row="1" Grid.Column="2" Margin="6,0,0,0"/>
<CheckBox x:Name="Sprawny" Grid.Row="1" Grid.Column="3" Content="sprawny"
IsChecked="True" VerticalAlignment="Center" Margin="8,0,0,0"/>
<Button Grid.Row="1" Grid.Column="4" Content="Dodaj" Width="70" Margin="8,0,0,0"
IsDefault="True" Click="Dodaj_Click"/>
</Grid>
</GroupBox>
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Margin="10,4,10,8">
<Button Content="Usuń zaznaczone" Width="120" Click="Usun_Click"/>
<Button Content="Zmień stan" Width="100" Margin="8,0,0,0" Click="ZmienStan_Click"/>
</StackPanel>
<ListBox x:Name="Lista" Margin="10,4,10,4" FontFamily="Consolas"/>
</DockPanel>
</Window>
using System.ComponentModel;
using System.IO;
using System.Windows;
using System.Windows.Input;
using Microsoft.Win32;
namespace Lekcja38;
public partial class MainWindow : Window
{
const string Naglowek = "NrInwentarzowy;Nazwa;Sala;Sprawny";
const string FiltrCsv = "Pliki CSV (*.csv)|*.csv|Wszystkie pliki (*.*)|*.*";
private List<Sprzet> sprzet = new List<Sprzet>();
private string? sciezka = null; // null = ewidencja jeszcze nie była zapisana
private bool zmiany = false; // czy są niezapisane zmiany
public MainWindow()
{
InitializeComponent();
Odswiez();
}
// ---------- menu Plik ----------
private void Nowy_Click(object sender, RoutedEventArgs e)
{
if (!PytajOZapis())
{
return;
}
sprzet.Clear();
sciezka = null;
zmiany = false;
Odswiez();
}
private void Otworz_Click(object sender, RoutedEventArgs e)
{
if (!PytajOZapis())
{
return;
}
OpenFileDialog okno = new OpenFileDialog();
okno.Title = "Otwórz ewidencję";
okno.Filter = FiltrCsv;
if (okno.ShowDialog() == true) // true = użytkownik wybrał plik i kliknął Otwórz
{
Wczytaj(okno.FileName);
}
}
private void Zapisz_Click(object sender, RoutedEventArgs e)
{
Zapisz();
}
private void ZapiszJako_Click(object sender, RoutedEventArgs e)
{
ZapiszJako();
}
private void Raport_Click(object sender, RoutedEventArgs e)
{
SaveFileDialog okno = new SaveFileDialog();
okno.Title = "Zapisz raport";
okno.Filter = "Pliki tekstowe (*.txt)|*.txt";
okno.FileName = $"do-naprawy-{DateTime.Today:yyyy-MM-dd}.txt";
if (okno.ShowDialog() != true)
{
return;
}
string raport = $"Sprzęt do naprawy — stan na {DateTime.Today:d}\n\n";
int ile = 0;
foreach (Sprzet s in sprzet)
{
if (!s.Sprawny)
{
raport += $"sala {s.Sala}: {s.NrInwentarzowy} {s.Nazwa}\n";
ile++;
}
}
raport += $"\nRazem: {ile}";
try
{
File.WriteAllText(okno.FileName, raport);
MessageBox.Show($"Zapisano raport ({ile} poz.).", "Raport",
MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
MessageBox.Show($"Nie udało się zapisać raportu:\n{ex.Message}", "Błąd",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void Zakoncz_Click(object sender, RoutedEventArgs e)
{
Close(); // pytanie o zapis zada Window_Closing
}
private void Window_Closing(object sender, CancelEventArgs e)
{
if (!PytajOZapis())
{
e.Cancel = true;
}
}
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.S)
{
Zapisz();
e.Handled = true;
}
else if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.O)
{
Otworz_Click(sender, e);
e.Handled = true;
}
}
// ---------- pliki ----------
private void Wczytaj(string plik)
{
string[] wiersze;
try
{
wiersze = File.ReadAllLines(plik);
}
catch (Exception ex)
{
MessageBox.Show($"Nie udało się otworzyć pliku:\n{ex.Message}", "Błąd",
MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
sprzet.Clear();
int bledne = 0;
foreach (string wiersz in wiersze)
{
if (wiersz == Naglowek || wiersz.Trim() == "")
{
continue; // nagłówek i puste wiersze pomijamy
}
Sprzet? s = ZWiersza(wiersz);
if (s == null)
{
bledne++;
}
else
{
sprzet.Add(s);
}
}
sciezka = plik;
zmiany = false;
Odswiez();
if (bledne > 0)
{
MessageBox.Show($"Pominięto błędne wiersze: {bledne}.", "Uwaga",
MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
// Wiersz pliku → obiekt; null, gdy wiersz jest niepoprawny
private Sprzet? ZWiersza(string wiersz)
{
string[] pola = wiersz.Split(';');
if (pola.Length != 4 || pola[0].Trim() == "" || pola[1].Trim() == "")
{
return null;
}
if (!int.TryParse(pola[2], out int sala))
{
return null;
}
if (pola[3] != "tak" && pola[3] != "nie")
{
return null;
}
return new Sprzet(pola[0].Trim(), pola[1].Trim(), sala, pola[3] == "tak");
}
// Zwraca true, gdy zapis się udał
private bool Zapisz()
{
if (sciezka == null)
{
return ZapiszJako(); // pierwszy zapis — trzeba zapytać o nazwę
}
return ZapiszDo(sciezka);
}
private bool ZapiszJako()
{
SaveFileDialog okno = new SaveFileDialog();
okno.Title = "Zapisz ewidencję";
okno.Filter = FiltrCsv;
okno.FileName = "ewidencja.csv";
if (okno.ShowDialog() != true)
{
return false; // Anuluj
}
return ZapiszDo(okno.FileName);
}
private bool ZapiszDo(string plik)
{
List<string> wiersze = new List<string>();
wiersze.Add(Naglowek);
foreach (Sprzet s in sprzet)
{
wiersze.Add(s.DoCsv());
}
try
{
File.WriteAllLines(plik, wiersze);
}
catch (Exception ex)
{
MessageBox.Show($"Nie udało się zapisać pliku:\n{ex.Message}", "Błąd",
MessageBoxButton.OK, MessageBoxImage.Error);
return false;
}
sciezka = plik;
zmiany = false;
Odswiez();
return true;
}
// Zwraca true, gdy można porzucić bieżącą ewidencję
private bool PytajOZapis()
{
if (!zmiany)
{
return true;
}
MessageBoxResult odp = MessageBox.Show("Ewidencja ma niezapisane zmiany. Zapisać je?",
"Ewidencja sprzętu", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);
if (odp == MessageBoxResult.Yes)
{
return Zapisz(); // gdy zapis się nie uda lub zostanie anulowany — zostajemy
}
return odp == MessageBoxResult.No;
}
// ---------- edycja ----------
private void Dodaj_Click(object sender, RoutedEventArgs e)
{
if (Nr.Text.Trim() == "" || Nazwa.Text.Trim() == "" ||
!int.TryParse(Sala.Text, out int sala) || Nazwa.Text.Contains(';'))
{
MessageBox.Show("Podaj numer, nazwę (bez znaku ;) i numer sali.", "Nowa pozycja",
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
sprzet.Add(new Sprzet(Nr.Text.Trim(), Nazwa.Text.Trim(), sala, Sprawny.IsChecked == true));
Nr.Clear();
Nazwa.Clear();
Nr.Focus();
zmiany = true;
Odswiez();
}
private void Usun_Click(object sender, RoutedEventArgs e)
{
Sprzet? s = Lista.SelectedItem as Sprzet;
if (s != null)
{
sprzet.Remove(s);
zmiany = true;
Odswiez();
}
}
private void ZmienStan_Click(object sender, RoutedEventArgs e)
{
Sprzet? s = Lista.SelectedItem as Sprzet;
if (s != null)
{
s.Sprawny = !s.Sprawny;
zmiany = true;
Odswiez();
Lista.SelectedItem = s;
}
}
// Lista, pasek stanu i tytuł okna
private void Odswiez()
{
Lista.Items.Clear();
int doNaprawy = 0;
foreach (Sprzet s in sprzet)
{
Lista.Items.Add(s);
if (!s.Sprawny)
{
doNaprawy++;
}
}
string nazwa = sciezka == null ? "bez nazwy" : Path.GetFileName(sciezka);
Title = $"Ewidencja sprzętu — {nazwa}{(zmiany ? " *" : "")}";
Plik.Text = sciezka == null ? "(nowy plik)" : sciezka;
Liczba.Text = $"Pozycji: {sprzet.Count}, do naprawy: {doNaprawy}";
}
}
Co dzieje się po kolei
- Pola
sciezkaizmianyopisują stan pliku.Odswiezbuduje listę, tytuł z gwiazdką i pasek stanu na ich podstawie. Otworz_Clicknajpierw pyta o niezapisane zmiany, potem pokazuje okno „Otwórz”. Odczyt robiWczytaj— wtry, bo plik może być niedostępny.Wczytajpomija nagłówek i puste wiersze, a każdy pozostały wiersz zamienia w obiekt metodąZWiersza. Błędne wiersze są liczone i zgłaszane jednym komunikatem.Sprzet.DoCsviZWierszasą swoimi odwrotnościami: pierwsza robi z obiektu wiersz, druga — z wiersza obiekt.Zapiszprzy pierwszym zapisie przechodzi doZapiszJako. Obie metody zwracająbool, dzięki czemuPytajOZapiswie, czy zapis naprawdę się udał.Window_Closinganuluje zamknięcie, gdy użytkownik wybrał „Anuluj” albo zapis się nie powiódł.Zakoncz_Clickpo prostu wołaClose, więc pytanie pojawia się także przy krzyżyku.- Raport to jeden napis budowany w pętli i zapisany przez
File.WriteAllTextpod nazwą z datą.
Sprawdź trzy rzeczy
- Otwórz
dane/sala14.csv. Pojawi się komunikat o jednym pominiętym wierszu (stan „może”). - Otwórz zapisany plik CSV w Excelu, zostaw go otwartego i zapisz ewidencję z programu. Zamiast awarii zobaczysz komunikat z
catch. - Dodaj pozycję i zamknij okno krzyżykiem, a w pytaniu wybierz Tak, a potem Anuluj w oknie zapisu. Okno się nie zamknie.
Zestawienie elementów
| Element | Typ | Co robi |
|---|---|---|
File.ReadAllText, WriteAllText | metody | Odczyt i zapis całego tekstu. |
File.ReadAllLines, WriteAllLines | metody | Odczyt do string[], zapis tablicy lub listy wierszy. |
File.AppendAllText | metoda | Dopisanie na końcu pliku. |
File.Exists | bool | Czy plik istnieje. |
Path.GetFileName, GetExtension | metody | Nazwa i rozszerzenie z pełnej ścieżki. |
try / catch (Exception ex) | instrukcja | Obsługa wyjątku; ex.Message — opis błędu. |
OpenFileDialog, SaveFileDialog | klasy | Systemowe okna wyboru pliku (Microsoft.Win32). |
Filter, Title, FileName, InitialDirectory | właściwości | Typy plików, tytuł okna, wybrana ścieżka, folder startowy. |
ShowDialog() | bool? | true — użytkownik zatwierdził wybór. |
OpenFolderDialog | klasa | Wybór folderu; wynik w FolderName. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
File bez using System.IO; | CS0103 — nazwa File nie istnieje w bieżącym kontekście. |
if (okno.ShowDialog()) | CS0266 — wynik to bool?; porównaj z true. |
odczyt okno.FileName bez sprawdzenia wyniku | Po „Anuluj” nazwa jest pusta — ArgumentException przy odczycie. |
"C:\nowy\plik.txt" | \n to nowa linia. Użyj @"C:\nowy\plik.txt". |
zapis i odczyt plików bez try/catch | Zablokowany plik kończy program awarią. |
pusty catch { } | Błąd znika bez śladu — użytkownik myśli, że zapisał dane. |
odczyt CSV bez sprawdzenia pola.Length | IndexOutOfRangeException na pierwszym krótszym wierszu. |
Zadania
Każde zadanie wykonaj w nowym projekcie Aplikacja WPF. Każdy zapis i odczyt pliku umieść w try/catch. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.
Wielowierszowe pole i menu „Plik”: Nowy, Otwórz…, Zapisz, Zapisz jako…, Zakończ. Tytuł okna pokazuje nazwę pliku i gwiazdkę przy zmianach, a program pyta o zapis przed porzuceniem tekstu i przy zamknięciu okna. Filtr: pliki tekstowe i wszystkie pliki.
pokaż wskazówkę
- Flagę zmian ustawiaj w
TextChanged— ale nie wtedy, gdy tekst wstawił kod po odczycie pliku (np. ustaw flagę nafalsepo wstawieniu).
Formularz: tytuł książki, autor, data przeczytania (DatePicker), ocena 1–5 (Slider). Przycisk Dopisz dodaje wiersz do pliku przeczytane.csv obok programu (bez okna dialogowego) i odświeża listę wczytaną z tego pliku. Przy starcie program wczytuje plik, jeśli istnieje. Na dole: liczba książek i średnia ocena.
pokaż wskazówkę
- Dopisywanie:
File.AppendAllTextz\nna końcu wiersza; przy starcie sprawdźFile.Exists.
Przycisk Wczytaj wyniki… otwiera plik CSV w formacie nazwisko;punkty (maks. 40 punktów). Program pokazuje listę z ocenami wyliczonymi z progów procentowych (np. 50% — dop., 70% — dst…), statystykę: liczbę uczniów, średnią punktów, liczbę każdej oceny oraz słupki z ProgressBar. Błędne wiersze są wypisane w osobnej liście z numerem wiersza. Przycisk Zapisz oceny… zapisuje plik nazwisko;punkty;ocena.
pokaż wskazówkę
- Numer wiersza dostaniesz, przechodząc pętlą
forpo tablicy zReadAllLineszamiastforeach.
Przycisk Wybierz folder… (OpenFolderDialog) wczytuje listę plików .png i .jpg z wybranego folderu. Kliknięcie na liście pokazuje obraz, nazwę pliku i rozmiar w KB; przyciski ◀ ▶ przechodzą między obrazami. Przycisk Zapisz listę… zapisuje do pliku tekstowego nazwy wszystkich obrazów z rozmiarami.
pokaż wskazówkę
- Pliki z folderu:
Directory.GetFiles(folder, "*.png")(osobno dla*.jpg); rozmiar:new FileInfo(sciezka).Length. Obraz z dysku:new BitmapImage(new Uri(sciezka)).
Program wczytuje zestaw słówek z pliku slowo;tlumaczenie wybranego w oknie „Otwórz”. Tryb nauki: pokazuje słowo, przycisk „Pokaż tłumaczenie”, potem „Umiem” / „Nie umiem”. Słówka „nie umiem” wracają na koniec kolejki. Po skończeniu zestawu wynik i przycisk Zapisz trudne słówka…, który zapisuje do nowego pliku tylko te, które choć raz były „nie umiem”.
pokaż wskazówkę
- Kolejka to zwykła
List: bierzesz element o indeksie 0, usuwasz go i — gdy „nie umiem” — dodajesz na koniec.
Weź biblioteczkę klasową z lekcji 23 i dodaj: menu „Plik” z pełną obsługą plików CSV i niezapisanych zmian, automatyczny zapis kopii zapasowej biblioteka.bak.csv obok programu co 2 minuty (DispatcherTimer, informacja w pasku stanu „Kopia zapasowa: 10:42”), import książek z drugiego pliku CSV (bez duplikatów — ten sam tytuł i autor) z raportem „Dodano 12, pominięto 3 duplikaty, 1 błędny wiersz” oraz eksport listy wypożyczonych książek do pliku tekstowego.
pokaż wskazówkę
- Pole
Wypozyczajacymoże być puste — w CSV to po prostu dwa średniki obok siebie, aSplitzwróci pusty napis. - Kopia zapasowa nie może zmieniać ścieżki ani flagi zmian — to zapis „obok”, nie zwykłe „Zapisz”.
Co trzeba zapamiętać
File.ReadAllLines/WriteAllLinesiReadAllText/WriteAllText— cały plik jednym wywołaniem (using System.IO).- Każdy dostęp do pliku w
try/catch; komunikat zex.Message, nie pustycatch. OpenFileDialog/SaveFileDialog(Microsoft.Win32) tylko wybierają ścieżkę;ShowDialog() == trueoznacza zatwierdzenie.- CSV: obiekt → wiersz z polami przez
;, odczyt przezSplitze sprawdzeniem liczby i formatu pól. - Ścieżka bieżącego pliku + flaga zmian + pytanie Tak/Nie/Anuluj przed porzuceniem danych.
Dokumentacja: Microsoft Learn — okna dialogowe plików, File.