W dzisiejszym artykule stworzymy prostą aplikację w Windows Forms w języku C#, która będzie tworzyła oraz zapisywała dane do pliku z rozszerzeniem CSV, a także pokaże Ci jak odczytywać dane z takiego pliku i wyświetlać je w aplikacji. Opowiem także, czym jest format CSV i dlaczego często jest stosowany w programowaniu.
Czym są pliki CSV?
Plik o rozszerzeniu CSV jest to specjalny typ pliku, który przechowuje wartości rozdzielone przecinkami. Takie pliki można między innymi uruchamiać, a także edytować w programie Excel. Taki typ pliku jest często używany w programowaniu, ponieważ są to zwykłe, proste, lekkie pliki, gdzie oddzielamy od siebie jakieś dane, które później możemy łatwo przywrócić i zmapować na model. Możemy przechowywać w takim formacje różne dane. Często możemy dać opcję importowania danych użytkownikowi i właśnie w łatwy sposób możemy importować takie dane do formatu CSV. Ja pokaże Ci w tym artykule, jak obsłużyć to po stronie języka C#.
Zadanie do zrealizowania
Jako, że lubię uczyć poprzez praktykę, to stworzymy sobie teraz prostą aplikację w C#.
Mamy do stworzenia aplikację, która ma przechowywać listę mailową użytkownika. Czyli użytkownik zbiera sobie dane swoich klientów, takie jak:
- email,
- imię,
- datę dodania,
- informacje o tym, czy adres e-mail został potwierdzony
Użytkownik musi mieć możliwość:
- Dodania nowego klienta ze wszystkimi danymi z samej aplikacji.
- Zapis danych, tak żeby dane były dostępne po zamknięciu i ponownym uruchomieniu aplikacji.
- Podgląd tych danych w aplikacji.
- I właśnie możliwość eksportowania danych do formatu CSV, tak żeby można było je podejrzeć w Excelu.
Projekt
W poprzednich artykułach pokazywałem Ci, jak tworzyć nowe projekty w Windows Forms i jak używać podstawowych komponentów. W związku z tym, aby nie przedłużać tego materiału przygotowałem już wcześniej projekt od którego będziemy teraz zaczynać implementację. Jeżeli nie wiesz jak założyć nowy projekt i dodać te komponenty, to zajrzyj wcześniej do poprzednich odcinków, gdzie wszystko szczegółowo opisuje.
Mamy tutaj kilka komponentów, to znaczy:
1) TextBox, który umożliwia nam wprowadzenie adresu e-mail klienta.
2) TextBox, gdzie będziemy wpisywać imię klienta.
3) DateTimePicker, w celu wprowadzenia daty dodania klienta.
4) CheckBox, który możemy zaznaczyć, jeżeli adres e-mail został potwierdzony.
5) Button, po kliknięciu którego dodamy do listy nowego klienta.
6) Button, po kliknięciu którego zapiszemy dane do pliku CSV.
7) DataGridView w którym wyświetlamy wszystkich klientów z listy mailowej w naszej aplikacji.
Dodatkowo dodałem kilka etykiet w celu poprawienia czytelności aplikacji. Każda kontrolka ma również uzupełnioną właściwość Name, czyli nazwę po której będziemy odnosić się do tych kontrolek w code behind, czyli w kodzie C#.
Przejdźmy do kodu C#.
Mamy tutaj klasę Client, czyli jest to nasz główny model, którego obiekty będziemy zapisywać w pliku.
public class Client
{
public string Email { get; set; }
public string Name { get; set; }
public DateTime AddedDate { get; set; }
public bool IsConfirmed { get; set; }
}
Składa się ona z 4 właściwości, czyli danych klienta, która będziemy zapisywać.
I mamy jeszcze code behind dla formatki głównej.
public partial class Form1 : Form
{
private BindingList<Client> _clients = new BindingList<Client>();
public Form1()
{
InitializeComponent();
dgvClients.DataSource = _clients;
}
private void btnAdd_Click(object sender, EventArgs e)
{
var client = new Client
{
Email = tbEmail.Text,
Name = tbName.Text,
AddedDate = dtpAddedDate.Value,
IsConfirmed = cbIsConfirmed.Checked
};
_clients.Add(client);
ClearData();
}
private void ClearData()
{
tbEmail.Text = "";
tbName.Text = "";
dtpAddedDate.Value = DateTime.Now;
cbIsConfirmed.Checked = false;
}
private void btnSave_Click(object sender, EventArgs e)
{
}
}
Mamy tutaj listę _clients, która będzie przechowywała wszystkich klientów w aplikacji. W konstruktorze przypisujemy listę do DataGridView (dgvClients) DataSource w celu wyświetlenia jej na widoku. Ponadto zostały dodane 2 metody, które są wywoływane po kliknięciu kolejno przycisków BtnAdd oraz BtnSave. Jeżeli zostanie kliknięty przycisk BtnAdd, to do listy klientów dodamy nowego klienta z danymi wprowadzonymi na formularzu. Zostanie on również automatycznie wyświetlony w DataGridView. Po dodaniu klienta ustawiamy wartości początkowe dla wszystkich kontrolek formularza. Zobaczmy jak to teraz działa.
Jeżeli coś w tym kodzie na ten moment nie jest dla Ciebie jasne, to napisz proszę do mnie na maila lub w komentarzu i wszystko Ci wytłumaczę. Możemy teraz przejść do faktycznej implementacji i działaniu na plikach CSV.
Instalacja biblioteki
Zarówno kod zapisu jak i odczytu danych z pliku CSV możemy sami napisać od zera. Natomiast możemy też ułatwić sobie to zadania i skorzystać z gotowych już bibliotek. Popularną biblioteką do działania na plikach CSV jest biblioteka CsvHelper, z której również skorzystamy. Zainstalujemy tę bibliotekę przez NuGeta.
Zapis danych do pliku CSV
Zacznijmy od zapisu danych do pliku CSV. W naszym przypadku będziemy zapisywać całą listę obiektów, czyli _clients do pliku np. o nazwie clients.csv.
private void btnSave_Click(object sender, EventArgs e)
{
using var writer = new StreamWriter(ClientsFileName);
using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
csv.WriteRecords(_clients);
}
Za pomocą obiektu klasy CsvWriter z biblioteki CsvHelper możemy zapisać dane do pliku. Przy inicjalizacji CsvWriter przekazujemy jako argument obiekt StreamWriter oraz CultureInfo. Następnie wystarczy wywołać metodę WriteRecords i przekazać listę obiektów, które chcemy zapisać w pliku CSV.
private const string ClientsFileName = "clients.csv"
Nazwę pliku będziemy przechowywać w stałej o nawie ClientsFilename, ponieważ będziemy ją jeszcze używać w innych miejscach.
Zobaczmy, czy to faktycznie działa. Uruchomimy teraz aplikację i zobaczymy czy faktycznie został utworzony plik CSV z wcześniej wprowadzonymi danymi.
Jeżeli miałbyś taką potrzebę, to również zamiast za każdym razem nadpisywać wszystkie dane, możesz dopisywać tylko nowe. Możesz to zrobić w taki sposób:
private void btnSave_Click(object sender, EventArgs e)
{
using var stream = File.Open(ClientsFileName, FileMode.Append);
using var writer = new StreamWriter(stream);
using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
csv.WriteRecords(_clients);
}
Możesz też przekazać jakieś dodatkowe dane konfiguracyjne inicjalizując nowy obiekt klasy CsvConfiguration.
private void btnSave_Click(object sender, EventArgs e)
{
var configPersons = new CsvConfiguration(CultureInfo.InvariantCulture)
{
HasHeaderRecord = false
};
using var stream = File.Open(ClientsFileName, FileMode.Append);
using var writer = new StreamWriter(stream);
using var csv = new CsvWriter(writer, configPersons);
csv.WriteRecords(_clients);
}
Natomiast w naszym przypadku będziemy zawsze nadpisywać wszystkich klientów, także możemy wrócić do pierwszej wersji tej metody.
private void btnSave_Click(object sender, EventArgs e)
{
using var writer = new StreamWriter(ClientsFileName);
using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
csv.WriteRecords(_clients);
}
Super, zapis działa prawidłowo. Dodatkowo możemy jeszcze dostosować nasz plik i zmienić np. nazwy nagłówków, kolejność kolumn, czy formatowanie za pomocą mapowania.
Mapowanie
Mapowanie możemy zrobić na 2 sposoby, to znaczy poprzez zmiany bezpośrednio w modelu, dodając odpowiednie atrybuty dla właściwości.
public class Client
{
[Name("E-mail")]
[Index(2)]
public string Email { get; set; }
[Name("Imię")]
[Index(1)]
public string Name { get; set; }
[Format("dd-MM-yyyy")]
[Name("Data dodania")]
[Index(0)]
public DateTime AddedDate { get; set; }
[Name("Czy potwierdzony")]
[Index(3)]
public bool IsConfirmed { get; set; }
}
Możemy za pomocą atrybutu Name – ustawić nazwę nagłówka.
Dzięki atrybutowi Index - ustalimy kolejność kolumn.
A za pomocą atrybutu Format - sformatujemy w dowolny sposób kolumny.
I to jak najbardziej zadziała i zapisze plik w odpowiedni sposób.
Natomiast jest drugi moim zdaniem lepszy sposób, to znaczy zapisywanie konfiguracji w osobnej klasie mapującej. Dzięki temu nie musimy dodawać atrybutów do klasy naszego modelu, który często jest też modelem domenowym, także jest to rozwiązanie takie bardziej uniwersalne i zgodne z najlepszymi praktykami.
Dodamy nową klasę o nazwie ClientMap. Ta klasa musi dziedziczyć po klasie ClassMap i jako typ generyczny wskazujemy klasę do której tworzymy mapowanie. Następnie w konstruktorze konfigurujemy kolumny wywołując odpowiednie metody.
public class ClientMap : ClassMap<Client>
{
public ClientMap()
{
Map(p => p.Email)
.Index(2)
.Name("E-mail");
Map(p => p.Name)
.Index(1)
.Name("Imię");
Map(p => p.AddedDate)
.Name("Data dodania")
.TypeConverterOption.Format("dd-MM-yyyy")
.Index(0);
Map(p => p.IsConfirmed)
.Name("Czy potwierdzony")
.Index(3);
}
}
Teraz możemy usunąć atrybuty z klasy modelu.
public class Client
{
public string Email { get; set; }
public string Name { get; set; }
public DateTime AddedDate { get; set; }
public bool IsConfirmed { get; set; }
}
Natomiast przed zapisem musimy zarejestrować to mapowanie.
private void btnSave_Click(object sender, EventArgs e)
{
using var writer = new StreamWriter(ClientsFileName);
using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
csv.Context.RegisterClassMap<ClientMap>();
csv.WriteRecords(_clients);
}
I teraz już działanie będzie identyczne jak wcześniej.
Super, możemy teraz przejść do odczytu danych.
Odczyt danych z pliku CSV
W naszej aplikacji odczyt danych z pliku CSV chcemy zrobić zaraz po załadowaniu aplikacji. Chcemy, żeby DataGridView został odpowiednio uzupełniony i wyświetlił po starcie aplikacji wszystkie wcześniej wprowadzane dane klientów. Najlepiej taki kod dodać w zdarzeniu Load, który zostanie automatycznie uruchomiony po starcie aplikacji.
private void Form1_Load(object sender, EventArgs e)
{
}
Odczyt danych będzie podobny do zapisu. Tak samo skorzystamy tutaj z biblioteki CsvHelper, która została wcześniej dodana do projektu. Tylko tym razem skorzystamy z klasy CsvReader i metody GetRecords. A tak wygląda cała implementacja:
private void Form1_Load(object sender, EventArgs e)
{
using var reader = new StreamReader(ClientsFileName);
using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
csv.Context.RegisterClassMap<ClientMap>();
var clients = csv.GetRecords<Client>().ToList();
FillTable(clients);
}
private void FillTable(IList<Client> clients)
{
_clients.Clear();
if (clients == null || !clients.Any())
return;
foreach (var client in clients)
_clients.Add(client);
}
Zobaczmy czy to faktycznie działa.
Jak widzisz, wszystkie dane zostały poprawnie wczytane z pliku CSV.
A tak wygląda nasz plik w Excelu:
Możemy jeszcze dodać zabezpieczenie w konstruktorze, które utworzy plik CSV przy pierwszym uruchomieniu aplikacji, tak aby nie został wtedy rzucony wyjątek.
public Form1()
{
InitializeComponent();
File.Create(ClientsFileName).Close();
dgvClients.DataSource = _clients;
}
To w zasadzie wszystko. Taką aplikację można jeszcze bardziej rozwinąć. Można tutaj jeszcze zaimplementować więcej funkcji np. edycję czy usuwanie danych, natomiast nie będziemy się już tym zajmować. W tym odcinku chciałem się skupić tylko na działaniu na plikach CSV.
To wszystkie na dzisiaj. Jeżeli taki artykuł Ci się spodobał, to koniecznie dołącz do mojej społeczności – darmowe zapisy, gdzie będziesz również miał dostęp do dodatkowych materiałów i przede wszystkim bonusów. Do zobaczenia w kolejnym artykule.
Poprzedni artykuł - Za Stary Na Programowanie? Czy Można Zostać Programistą Po 30-stce?