Czyli jak połączyć okno, logikę programu i prawdziwą bazę danych.
1. Dlaczego w aplikacjach potrzebna jest baza danych?
W praktycznie każdej aplikacji trudno obyć się bez przechowywania danych.
Na przykład:
- lista produktów,
- lista klientów,
- zamówienia, ceny, stany magazynowe,
- historia działań.
Gdyby program trzymał wszystko w pamięci RAM, po zamknięciu aplikacji wszystko by znikało.
Dlatego używa się baz danych trwałych zapisów na dysku.
Na egzaminie INF.04 świetnie sprawdza się SQLite, bo:
- działa bez internetu,
- nie trzeba instalować serwera,
- cała baza to jeden plik
.db, - WPF i WinForms współpracują z nim bez problemu.
Dzisiaj zobaczysz pełną aplikację: dodawanie, edycję, usuwanie, wyświetlanie i wyszukiwanie danych.
2. Zanim zaczniemy pisać — jak to w ogóle działa?
Najpierw warto zrozumieć cały „łańcuch”, bo wtedy kod stanie się prosty:
[UI – XAML] → [Logika okna – XAML.cs] → [Logika bazy – BazaDanych.cs] → [plik sklep.db]
Każda część ma swoją funkcję:
XAML
Tu jest wygląd aplikacji: pola tekstowe, przyciski, tabela.
Nic nie liczy — tylko pokazuje.
XAML.cs
Tu jest logika zachowania przycisków:
- klik „Dodaj” → wyślij nowe dane do bazy
- klik „Usuń” → usuń rekord
- odśwież widok po każdej operacji
XAML.cs nie zna SQL – tylko wywołuje metody z klasy BazaDanych.
BazaDanych.cs
Tu jest prawdziwa praca:
- połączenie z plikiem
.db, - tworzenie tabeli, jeśli nie istnieje,
- wykonywanie SQL-a: SELECT, INSERT, UPDATE, DELETE.
sklep.db
Po prostu plik na dysku, który przechowuje dane.
3. Podstawowe pojęcia (tak, żeby rozumieć kod SQL)
| Pojęcie | Wyjaśnienie |
|---|---|
| Baza danych | Plik, w którym zapisane są informacje. |
| Tabela | Podział danych na grupy (Produkty, Klienci…). |
| Rekord | Jeden wiersz tabeli, np. jeden produkt. |
| Pole (kolumna) | nazwa, cena, id… |
| PRIMARY KEY | unikalne ID każdego rekordu. |
| AUTOINCREMENT | samodzielne nadawanie ID. |
4. Najważniejsze polecenia SQL — te trzeba znać
1. SELECT – pobieranie danych
SELECT * FROM Produkty;
SELECT nazwa, cena FROM Produkty;
SELECT * FROM Produkty WHERE cena > 5;
SELECT * FROM Produkty WHERE cena < 10;
SELECT * FROM Produkty WHERE cena BETWEEN 4 AND 8;
-- wyszukiwanie po tekście
SELECT * FROM Produkty WHERE nazwa LIKE '%cola%';
SELECT * FROM Produkty WHERE nazwa LIKE 'C%'; -- zaczyna się na C
SELECT * FROM Produkty WHERE nazwa LIKE '%a'; -- kończy się na a
SELECT * FROM Produkty WHERE nazwa LIKE '%or%'; -- zawiera „or”
-- sortowanie
SELECT * FROM Produkty ORDER BY cena ASC;
SELECT * FROM Produkty ORDER BY cena DESC;
SELECT * FROM Produkty ORDER BY cena ASC, nazwa ASC;
-- warunki z AND/OR
SELECT * FROM Produkty WHERE cena > 5 AND nazwa LIKE 'C%';
SELECT * FROM Produkty WHERE cena = 4 OR cena = 6;
-- statystyki
SELECT COUNT(*) FROM Produkty;
SELECT AVG(cena) FROM Produkty;
SELECT MIN(cena) FROM Produkty;
SELECT MAX(cena) FROM Produkty;
SELECT SUM(cena) FROM Produkty;
-- dodatkowe
SELECT ROUND(cena, 2) FROM Produkty;
SELECT UPPER(nazwa) FROM Produkty;
SELECT LOWER(nazwa) FROM Produkty;
-- informacje o tabeli
PRAGMA table_info(Produkty);
SELECT name FROM sqlite_master WHERE type='table';
2. INSERT – dodawanie danych
INSERT INTO Produkty(nazwa, cena) VALUES ('Chleb', 4.50);
3. UPDATE – edycja danych
UPDATE Produkty SET cena = 3.00 WHERE id = 1;
UPDATE Produkty SET cena = cena + 1;
UPDATE Produkty SET cena = cena - 0.5 WHERE id = 3;
4. DELETE – usuwanie danych
DELETE FROM Produkty WHERE id = 5;
DELETE FROM Produkty WHERE cena < 1;
5. Tworzenie projektu WPF
- Otwórz Visual Studio
- Nowy projekt → WPF App (.NET)
- Nazwa projektu: BazaDanychWPF
6. Dodanie pliku bazy danych sklep.db
Na egzaminie nie można ściągać nic z internetu, więc baza musi być dodana ręcznie.
- Pobierz plik
sklep.dblub utwórz wcześniej swoją wersję - Wklej go do projektu:
Dodaj → Istniejący element → sklep.db - Ustaw właściwości pliku:
- Build Action → None
- Copy to Output Directory → Copy always
Dzięki temu plik trafi do folderu aplikacji i program go znajdzie.
Dodajemy paczkę obsługi SQLite z paczka pakietów NuGet->
System.Data.SQLite.Core
7. Budujemy interfejs — MainWindow.xaml
Najpierw wytłumaczenie:
- Pierwszy panel to pola tekstowe (nazwa, cena, id) → dane wejściowe.
- Drugi panel to wyszukiwarka → filtruje tabelę.
- Na dole jest DataGrid — automatyczna tabela WPF, która sama wyświetli dane z DataTable.
- Przycisk ma np.
Click="btnDodaj_Click"— czyli metoda w XAML.cs.
PEŁNY KOD MainWindow.xaml
<Window x:Class="BazaDanychWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Baza Danych WPF - Produkty" Height="500" Width="750">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Górny panel -->
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
<!-- Panel danych -->
<StackPanel Width="220">
<TextBlock Text="Nazwa:"/>
<TextBox x:Name="txtNazwa"/>
<TextBlock Text="Cena:" Margin="0,10,0,0"/>
<TextBox x:Name="txtCena"/>
<TextBlock Text="ID (edycja/usuwanie):" Margin="0,10,0,0"/>
<TextBox x:Name="txtID"/>
<Button Content="Dodaj" Margin="0,10,0,0" Click="btnDodaj_Click"/>
<Button Content="Zapisz zmiany" Click="btnZapisz_Click"/>
<Button Content="Usuń" Click="btnUsun_Click"/>
</StackPanel>
<!-- Panel wyszukiwarki -->
<StackPanel Margin="20,0,0,0" Width="200">
<TextBlock Text="Szukaj nazwy:"/>
<TextBox x:Name="txtSzukaj"/>
<Button Content="Szukaj" Margin="0,10,0,0" Click="btnSzukaj_Click"/>
<Button Content="Odśwież" Click="btnOdswiez_Click"/>
</StackPanel>
</StackPanel>
<!-- Tabela -->
<DataGrid x:Name="gridProdukty"
Grid.Row="1"
AutoGenerateColumns="True"
SelectionChanged="gridProdukty_SelectionChanged"/>
</Grid>
</Window>
8. Logika okna — MainWindow.xaml.cs
Wytłumaczenie przed kodem
- Tworzymy obiekt
BazaDanych, żeby mieć dostęp do SQL. WyswietlProdukty()ładuje dane do DataGrid.- Przyciski wywołują odpowiednie metody SQL (Dodaj, Edytuj, Usuń).
gridProdukty_SelectionChangedpozwala kliknąć rekord i wypełnić pola edycji.
PEŁNY KOD MainWindow.xaml.cs
using System;
using System.Data;
using System.Windows;
using System.Windows.Controls;
namespace BazaDanychWPF
{
public partial class MainWindow : Window
{
// Obiekt odpowiedzialny za pracę z bazą danych
BazaDanych baza = new BazaDanych();
public MainWindow()
{
InitializeComponent();
// Po uruchomieniu okna pobieramy dane z bazy
WyswietlProdukty();
}
// Metoda wczytuje dane z bazy i ustawia je jako źródło dla tabeli
private void WyswietlProdukty(string filtr = "")
{
gridProdukty.ItemsSource = baza.PobierzProdukty(filtr).DefaultView;
}
// Przycisk DODAJ – dodaje nowy rekord do bazy
private void btnDodaj_Click(object sender, RoutedEventArgs e)
{
if (string.IsNullOrWhiteSpace(txtNazwa.Text))
{
MessageBox.Show("Podaj nazwę!");
return;
}
// Sprawdzamy, czy cena jest poprawną liczbą
if (!double.TryParse(txtCena.Text, out double cena))
{
MessageBox.Show("Cena musi być liczbą.");
return;
}
// Wywołanie metody z klasy BazaDanych
baza.DodajProdukt(txtNazwa.Text, cena);
// Odświeżenie tabeli
WyswietlProdukty();
}
// Przycisk USUŃ – usuwa rekord na podstawie ID
private void btnUsun_Click(object sender, RoutedEventArgs e)
{
if (!int.TryParse(txtID.Text, out int id))
{
MessageBox.Show("Podaj poprawne ID!");
return;
}
baza.UsunProdukt(id);
WyswietlProdukty();
}
// Przycisk ZAPISZ – zapisuje nowe wartości do wybranego rekordu
private void btnZapisz_Click(object sender, RoutedEventArgs e)
{
if (!int.TryParse(txtID.Text, out int id))
{
MessageBox.Show("Podaj ID!");
return;
}
if (!double.TryParse(txtCena.Text, out double cena))
{
MessageBox.Show("Cena musi być liczbą.");
return;
}
baza.EdytujProdukt(id, txtNazwa.Text, cena);
WyswietlProdukty();
}
// Po kliknięciu rekordu w tabeli wypełniamy pola edycji
private void gridProdukty_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (gridProdukty.SelectedItem is DataRowView row)
{
txtID.Text = row["id"].ToString();
txtNazwa.Text = row["nazwa"].ToString();
txtCena.Text = row["cena"].ToString();
}
}
// Wyszukiwanie po fragmencie nazwy
private void btnSzukaj_Click(object sender, RoutedEventArgs e)
{
WyswietlProdukty(txtSzukaj.Text);
}
// Przywrócenie pełnej listy produktów
private void btnOdswiez_Click(object sender, RoutedEventArgs e)
{
WyswietlProdukty();
}
}
}
9. Logika bazy — BazaDanych.cs
Tutaj dzieją się trzy ważne rzeczy:
1. Tworzenie połączenia
Każda operacja na bazie musi mieć:
using var con = new SQLiteConnection(sciezka);
con.Open();
using sprawia, że połączenie zamknie się automatycznie.
2. Tworzenie tabeli na starcie
Program sam tworzy tabelę, jeśli jej nie ma — idealne rozwiązanie egzaminacyjne.
3. Wykonywanie SQL
INSERT / DELETE / UPDATE używają:
cmd.ExecuteNonQuery();
SELECT używa:
cmd.ExecuteReader();
a dane są ładowane do DataTable.
PEŁNY KOD BazaDanych.cs
using System;
using System.Data;
using System.Data.SQLite;
namespace BazaDanychWPF
{
public class BazaDanych
{
// Ścieżka do pliku bazy danych (w katalogu aplikacji)
private string sciezka =
$"Data Source={AppDomain.CurrentDomain.BaseDirectory}sklep.db;Version=3;";
//***
public BazaDanych()
{
// Przy starcie programu tworzymy tabelę, jeśli jej nie ma
UtworzTabeleJesliNieIstnieje();
}
// Tworzenie tabeli w bazie – tylko jeśli nie istnieje
private void UtworzTabeleJesliNieIstnieje()
{
using var con = new SQLiteConnection(sciezka);
con.Open();
string sql =
"CREATE TABLE IF NOT EXISTS Produkty (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"nazwa TEXT NOT NULL, " +
"cena REAL NOT NULL);";
using var cmd = new SQLiteCommand(sql, con);
cmd.ExecuteNonQuery(); // wykonanie polecenia SQL
}
// Pobieranie danych z bazy (SELECT)
public DataTable PobierzProdukty(string filtr = "")
{
using var con = new SQLiteConnection(sciezka);
con.Open();
string sql = "SELECT * FROM Produkty";
// Filtr wyszukiwania
if (!string.IsNullOrWhiteSpace(filtr))
sql += " WHERE nazwa LIKE @f";
using var cmd = new SQLiteCommand(sql, con);
if (!string.IsNullOrWhiteSpace(filtr))
cmd.Parameters.AddWithValue("@f", "%" + filtr + "%");
using SQLiteDataReader r = cmd.ExecuteReader();
// Wczytanie wyników SELECT do tabeli w pamięci
DataTable dt = new DataTable();
dt.Load(r);
return dt;
}
// Dodawanie rekordu
public void DodajProdukt(string nazwa, double cena)
{
using var con = new SQLiteConnection(sciezka);
con.Open();
string sql = "INSERT INTO Produkty(nazwa, cena) VALUES (@n,@c)";
using var cmd = new SQLiteCommand(sql, con);
cmd.Parameters.AddWithValue("@n", nazwa);
cmd.Parameters.AddWithValue("@c", cena);
cmd.ExecuteNonQuery();
}
// Usuwanie rekordu po ID
public void UsunProdukt(int id)
{
using var con = new SQLiteConnection(sciezka);
con.Open();
string sql = "DELETE FROM Produkty WHERE id=@id";
using var cmd = new SQLiteCommand(sql, con);
cmd.Parameters.AddWithValue("@id", id);
cmd.ExecuteNonQuery();
}
// Edycja rekordu
public void EdytujProdukt(int id, string nazwa, double cena)
{
using var con = new SQLiteConnection(sciezka);
con.Open();
string sql = "UPDATE Produkty SET nazwa=@n, cena=@c WHERE id=@id";
using var cmd = new SQLiteCommand(sql, con);
cmd.Parameters.AddWithValue("@n", nazwa);
cmd.Parameters.AddWithValue("@c", cena);
cmd.Parameters.AddWithValue("@id", id);
cmd.ExecuteNonQuery();
}
}
}
*** „Na egzaminie zamiast własnego sklep.db ustaw w connection stringu ścieżkę do pliku bazy podanej w treści zadania (np. Data Source=C:\\Egzamin\\baza\\egzamin.db).”
10. Najpotrzebniejsze komendy C# i SQL
| Cel | Komenda |
|---|---|
| Połączenie z bazą | new SQLiteConnection(...) |
| Otwieranie | con.Open() |
| Wykonywanie SELECT | cmd.ExecuteReader() |
| Wykonywanie INSERT/DELETE/UPDATE | cmd.ExecuteNonQuery() |
| Parametry SQL | cmd.Parameters.AddWithValue("@x", val) |
| Ładowanie wyników | dt.Load(reader) |
| Autoużycie połączeń | using var con = ... |
Hmmmmmm……
11. Rozbudowa bazy danych – co by było, gdybyśmy chcieli dodać koszyk zamówień?
Do tej pory aplikacja obsługiwała jedną tabelę – Produkty.
Pozwalała ona dodawać, edytować, usuwać i wyszukiwać produkty w bazie danych.
Co by było jednak, gdybyśmy chcieli stworzyć prosty koszyk zamówień, podobny do tego, który znamy ze sklepów internetowych?
W takiej sytuacji jedna tabela już nie wystarczy.
11.1. Nowa koncepcja – druga tabela w bazie danych
Aby stworzyć koszyk, potrzebujemy informacji:
- jaki produkt został dodany,
- w jakiej ilości.
Nie chcemy jednak powielać danych (nazwy i ceny produktów), bo:
- dane już istnieją w tabeli
Produkty, - zmiana ceny produktu powinna być widoczna wszędzie.
👉 Dlatego tworzymy drugą tabelę, która odwołuje się do produktów przez ich ID.
11.2. Struktura nowej tabeli – Koszyk
Dodajemy tabelę Koszyk o następującej strukturze:
| Kolumna | Opis |
|---|---|
| id | unikalny identyfikator wpisu w koszyku |
| produkt_id | ID produktu z tabeli Produkty |
| ilosc | ilość danego produktu |
📌 Tabela Koszyk nie przechowuje nazwy ani ceny produktu.
📌 Przechowuje tylko informację, który produkt i w jakiej ilości znajduje się w koszyku.
To jest najprostszy i poprawny model relacji danych na poziomie INF.04.
11.3. Rozbudowa bazy – tworzenie drugiej tabeli
Rozbudowę zaczynamy od warstwy bazy danych, czyli pliku BazaDanych.cs.
📄 Plik: BazaDanych.cs
W metodzie UtworzTabeleJesliNieIstnieje() dopiszemy tworzenie drugiej tabeli:
// Tworzenie tabeli koszyka – tylko jeśli nie istnieje
string sqlKoszyk =
"CREATE TABLE IF NOT EXISTS Koszyk (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"produkt_id INTEGER NOT NULL, " +
"ilosc INTEGER NOT NULL);";
using var cmd2 = new SQLiteCommand(sqlKoszyk, con);
cmd2.ExecuteNonQuery();
Wyjaśnienie:
CREATE TABLE IF NOT EXISTS– tabela zostanie utworzona tylko raz,produkt_id– przechowuje ID produktu z tabeliProdukty,- brak kluczy obcych – relację realizujemy w kodzie aplikacji.
11.4. Dodawanie produktu do koszyka
Następnie dodajemy metodę umożliwiającą zapis produktu do koszyka.
📄 Plik: BazaDanych.cs
// Dodaje wybrany produkt do koszyka
public void DodajDoKoszyka(int produktId, int ilosc)
{
using var con = new SQLiteConnection(sciezka);
con.Open();
string sql =
"INSERT INTO Koszyk (produkt_id, ilosc) " +
"VALUES (@pid, @ilosc)";
using var cmd = new SQLiteCommand(sql, con);
cmd.Parameters.AddWithValue("@pid", produktId);
cmd.Parameters.AddWithValue("@ilosc", ilosc);
cmd.ExecuteNonQuery();
}
Wyjaśnienie:
- zapisujemy ID produktu, a nie jego nazwę,
- ilość jest przechowywana jako liczba,
- używamy parametrów SQL (bezpieczne rozwiązanie).
11.5. Pobieranie koszyka – połączenie danych z dwóch tabel
Aby wyświetlić koszyk w aplikacji, potrzebujemy:
- nazwy produktu,
- ceny,
- ilości,
- wartości pozycji (cena × ilość).
W tym miejscu łączymy dane z dwóch tabel.
📄 Plik: BazaDanych.cs
// Pobiera zawartość koszyka wraz z danymi produktu
public DataTable PobierzKoszyk()
{
using var con = new SQLiteConnection(sciezka);
con.Open();
string sql =
"SELECT Koszyk.id, Produkty.nazwa, Produkty.cena, Koszyk.ilosc, " +
"(Produkty.cena * Koszyk.ilosc) AS suma " +
"FROM Koszyk " +
"JOIN Produkty ON Koszyk.produkt_id = Produkty.id";
using var cmd = new SQLiteCommand(sql, con);
using SQLiteDataReader r = cmd.ExecuteReader();
DataTable dt = new DataTable();
dt.Load(r);
return dt;
}
Wyjaśnienie:
JOINłączy dane z tabelKoszykiProdukty,- łączenie odbywa się przez
produkt_id, - kolumna
sumaobliczana jest bezpośrednio w SQL.
📌 To jedyny moment, w którym używamy JOIN.
11.6. Podsumowanie rozbudowy bazy danych
Po tej modyfikacji aplikacja:
- posiada dwie tabele w bazie danych,
- wykorzystuje relację między tabelami,
- tworzy koszyk zamówień,
- nie powiela danych produktów,
- działa całkowicie offline.
A TERAZ WYGLĄD i LOGIKA
11.7. Rozbudowa interfejsu – dodanie koszyka zamówień
Skoro baza danych została rozbudowana o drugą tabelę (Koszyk),
musimy umożliwić użytkownikowi:
- dodanie produktu do koszyka,
- wyświetlenie zawartości koszyka.
Zrobimy to bez tworzenia nowego okna, tylko przez rozbudowę istniejącego interfejsu.
11.7.1. Zmiany w pliku MainWindow.xaml
Dodajemy:
- pole do wpisania ilości,
- przycisk „Dodaj do koszyka”,
- drugi
DataGriddo wyświetlenia koszyka.
Nie usuwamy nic z poprzedniego interfejsu – tylko dopisujemy.
Fragment do dopisania w panelu danych (pod polem ID)
<TextBlock Text="Ilość:" Margin="0,10,0,0"/>
<TextBox x:Name="txtIlosc"/>
<Button Content="Dodaj do koszyka"
Margin="0,10,0,0"
Click="btnDodajDoKoszyka_Click"/>
Zmiana układu siatki (na samej górze pliku)
Zmieniamy definicję wierszy:
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
Drugi DataGrid – koszyk (na dole okna)
<DataGrid x:Name="gridKoszyk"
Grid.Row="2"
Margin="0,10,0,0"
AutoGenerateColumns="True"/>
Wyjaśnienie dla ucznia:
- pierwszy
DataGrid→ lista produktów, - drugi
DataGrid→ koszyk, - koszyk nie jest edytowany ręcznie – tylko przez przycisk.
11.8. Logika koszyka – MainWindow.xaml.cs
Teraz musimy:
- obsłużyć kliknięcie przycisku,
- odświeżyć widok koszyka.
11.8.1. Metoda wyświetlania koszyka
Plik: MainWindow.xaml.cs
// Wyświetlenie zawartości koszyka
private void WyswietlKoszyk()
{
gridKoszyk.ItemsSource = baza.PobierzKoszyk().DefaultView;
}
Analogicznie do WyswietlProdukty().
11.8.2. Dodawanie produktu do koszyka
private void btnDodajDoKoszyka_Click(object sender, RoutedEventArgs e)
{
// Sprawdzenie, czy wybrano produkt
if (!int.TryParse(txtID.Text, out int produktId))
{
MessageBox.Show("Wybierz produkt z listy.");
return;
}
// Sprawdzenie ilości
if (!int.TryParse(txtIlosc.Text, out int ilosc))
{
MessageBox.Show("Podaj poprawną ilość.");
return;
}
// Dodanie produktu do koszyka
baza.DodajDoKoszyka(produktId, ilosc);
// Odświeżenie widoku koszyka
WyswietlKoszyk();
}
CAŁOŚĆ XAML
<Window x:Class="BazaDanychWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Baza Danych WPF - Produkty i Koszyk"
Height="650" Width="750">
<Grid Margin="10">
<!-- ZMIANA: dodajemy trzeci wiersz na koszyk -->
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<!-- NOWE -->
</Grid.RowDefinitions>
<!-- GÓRNY PANEL -->
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
<!-- LEWY PANEL: formularz produktu -->
<StackPanel Width="220">
<TextBlock Text="Nazwa:"/>
<TextBox x:Name="txtNazwa"/>
<TextBlock Text="Cena:" Margin="0,10,0,0"/>
<TextBox x:Name="txtCena"/>
<TextBlock Text="ID (edycja/usuwanie):" Margin="0,10,0,0"/>
<TextBox x:Name="txtID"/>
<Button Content="Dodaj"
Margin="0,10,0,0"
Click="btnDodaj_Click"/>
<Button Content="Zapisz zmiany"
Click="btnZapisz_Click"/>
<Button Content="Usuń"
Click="btnUsun_Click"/>
<!-- ============================== -->
<!-- NOWE ELEMENTY – KOSZYK -->
<!-- ============================== -->
<TextBlock Text="Ilość:"
Margin="0,20,0,0"/>
<!-- NOWE -->
<TextBox x:Name="txtIlosc"/>
<!-- NOWE -->
<Button Content="Dodaj do koszyka"
Margin="0,10,0,0"
Click="btnDodajDoKoszyka_Click"/>
<!-- NOWE -->
</StackPanel>
<!-- PRAWY PANEL: wyszukiwanie -->
<StackPanel Margin="20,0,0,0" Width="200">
<TextBlock Text="Szukaj nazwy:"/>
<TextBox x:Name="txtSzukaj"/>
<Button Content="Szukaj"
Margin="0,10,0,0"
Click="btnSzukaj_Click"/>
<Button Content="Odśwież"
Click="btnOdswiez_Click"/>
</StackPanel>
</StackPanel>
<!-- TABELA PRODUKTÓW -->
<DataGrid x:Name="gridProdukty"
Grid.Row="1"
Margin="0,0,0,10"
AutoGenerateColumns="True"
SelectionChanged="gridProdukty_SelectionChanged"/>
<!-- NOWE: TABELA KOSZYKA -->
<DataGrid x:Name="gridKoszyk"
Grid.Row="2"
AutoGenerateColumns="True"/>
<!-- NOWE -->
</Grid>
</Window>
