Wprowadzenie
Wyobraź sobie, że tworzysz aplikację, w której chcesz wyświetlić listę rzeczy: książek, produktów, uczniów albo zadań do wykonania.
Nie chcesz ich tworzyć po jednej kontrolce dla każdego wiersza – chcesz, żeby program sam wypełnił listę z danych.
Do tego właśnie służą listy danych i kontrolki takie jak CollectionView (w .NET MAUI) oraz ListView (w WPF).
To, co ważne:
- dane trzymamy w specjalnej kolekcji (np.
ObservableCollection), - każda pozycja ma swój model danych,
- wygląd pojedynczego elementu opisuje DataTemplate,
- całość łączy mechanizm Bindingu (czyli automatycznego powiązania danych z interfejsem).
Zacznijmy od podstaw.
Co to jest lista i kolekcja
Lista to po prostu zbiór elementów, np. książek.
W języku C# często używamy:
List<string> owoce = new List<string> { "Jabłko", "Gruszka", "Pomarańcza" };
Ale zwykła List<T> nie odświeża się automatycznie w interfejsie, gdy coś się zmieni.
Dlatego w aplikacjach graficznych używamy ObservableCollection, która informuje interfejs o każdej zmianie (dodaniu, usunięciu, edycji).
Zasada działania
- Model – klasa z danymi (np.
Ksiazkaz tytułem, autorem, statusem). - Kolekcja – lista obiektów
ObservableCollection<Ksiazka>. - Binding – połączenie danych z interfejsem (
ItemsSource+DataTemplate). - Zdarzenia – reagowanie na kliknięcia, zmiany, filtrowanie.
Foldery projektu (ten sam układ w obu technologiach)
BibliotekaApp/
│
├── Modele/
│ └── Ksiazka.cs
│
├── Logika/
│ └── BibliotekaViewModel.cs
│
├── Walidacja/
│ └── WalidatorKsiazki.cs
│
├── MainPage.xaml / MainPage.xaml.cs (.NET MAUI)
└── MainWindow.xaml / MainWindow.xaml.cs (WPF)
SEKCJA A – .NET MAUI
1. Model danych
Modele/Ksiazka.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace BibliotekaApp.Modele
{
public class Ksiazka : INotifyPropertyChanged
{
private string _tytul = "";
private string _autor = "";
private bool _wypozyczona;
public string Tytul
{
get => _tytul;
set { _tytul = value; OnPropertyChanged(); }
}
public string Autor
{
get => _autor;
set { _autor = value; OnPropertyChanged(); }
}
public bool Wypozyczona
{
get => _wypozyczona;
set
{
_wypozyczona = value;
OnPropertyChanged();
OnPropertyChanged(nameof(StatusOpis));
OnPropertyChanged(nameof(AkcjaTekst));
}
}
public string StatusOpis => Wypozyczona ? "Wypożyczona" : "Dostępna";
public string AkcjaTekst => Wypozyczona ? "Zwróć" : "Wypożycz";
public event PropertyChangedEventHandler? PropertyChanged;
void OnPropertyChanged([CallerMemberName] string? nazwa = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nazwa));
}
}
2. Logika (ViewModel)
Logika/BibliotekaViewModel.cs
using System.Collections.ObjectModel;
using BibliotekaApp.Modele;
namespace BibliotekaApp.Logika
{
public class BibliotekaViewModel
{
public ObservableCollection<Ksiazka> Ksiazki { get; set; }
public BibliotekaViewModel()
{
Ksiazki = new ObservableCollection<Ksiazka>
{
new Ksiazka { Tytul = "Hobbit", Autor = "J.R.R. Tolkien" },
new Ksiazka { Tytul = "Dune", Autor = "Frank Herbert", Wypozyczona = true },
new Ksiazka { Tytul = "1984", Autor = "George Orwell" }
};
}
}
}
3. Widok – lista książek
MainPage.xaml
<ContentPage
x:Class="BibliotekaApp.MainPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:models="clr-namespace:BibliotekaApp.Modele"
Title="Lista książek">
<VerticalStackLayout Padding="20">
<SearchBar Placeholder="Szukaj książki..." TextChanged="Szukaj_TextChanged" />
<CollectionView x:Name="cvKsiazki">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Ksiazka">
<Grid ColumnDefinitions="*,Auto" Padding="12" Margin="0,4">
<VerticalStackLayout>
<Label Text="{Binding Tytul}" FontAttributes="Bold" />
<Label Text="{Binding Autor}" />
<Label Text="{Binding StatusOpis}" />
</VerticalStackLayout>
<Button Grid.Column="1"
Text="{Binding AkcjaTekst}"
Clicked="ZmienStatus_Click" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
</ContentPage>
MainPage.xaml.cs
using BibliotekaApp.Logika;
using BibliotekaApp.Modele;
using System.Collections.ObjectModel;
namespace BibliotekaApp
{
public partial class MainPage : ContentPage
{
private BibliotekaViewModel vm;
private ObservableCollection<Ksiazka> oryginalnaLista;
public MainPage()
{
InitializeComponent();
vm = new BibliotekaViewModel();
oryginalnaLista = vm.Ksiazki;
cvKsiazki.ItemsSource = vm.Ksiazki;
}
private void ZmienStatus_Click(object sender, EventArgs e)
{
var ks = (sender as BindableObject)?.BindingContext as Ksiazka;
if (ks != null) ks.Wypozyczona = !ks.Wypozyczona;
}
private void Szukaj_TextChanged(object sender, TextChangedEventArgs e)
{
string filtr = e.NewTextValue?.ToLower() ?? "";
if (string.IsNullOrWhiteSpace(filtr))
{
cvKsiazki.ItemsSource = oryginalnaLista;
return;
}
var wyniki = new ObservableCollection<Ksiazka>(
oryginalnaLista.Where(k =>
k.Tytul.ToLower().Contains(filtr) ||
k.Autor.ToLower().Contains(filtr)));
cvKsiazki.ItemsSource = wyniki;
}
}
}
SEKCJA B – WPF
Zasada jest identyczna, tylko kontrolka i zdarzenia mają inne nazwy.
W WPF używamy ListView, DataContext i Click.
MainWindow.xaml
<Window x:Class="BibliotekaApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:models="clr-namespace:BibliotekaApp.Modele"
Title="Lista książek" Height="400" Width="600">
<Grid Margin="10" RowDefinitions="Auto,*" RowSpacing="6">
<TextBox x:Name="txtSzukaj" Height="28" TextChanged="txtSzukaj_TextChanged"
PlaceholderText="Szukaj książki..." />
<ListView x:Name="lvKsiazki" Grid.Row="1" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type models:Ksiazka}">
<Grid ColumnDefinitions="*,Auto" Margin="4">
<StackPanel>
<TextBlock Text="{Binding Tytul}" FontWeight="Bold" />
<TextBlock Text="{Binding Autor}" />
<TextBlock Text="{Binding StatusOpis}" />
</StackPanel>
<Button Grid.Column="1"
Content="{Binding AkcjaTekst}"
Click="btnZmienStatus_Click" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
MainWindow.xaml.cs
using BibliotekaApp.Logika;
using BibliotekaApp.Modele;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace BibliotekaApp
{
public partial class MainWindow : Window
{
private BibliotekaViewModel vm;
private ObservableCollection<Ksiazka> oryginalnaLista;
public MainWindow()
{
InitializeComponent();
vm = new BibliotekaViewModel();
oryginalnaLista = vm.Ksiazki;
lvKsiazki.ItemsSource = vm.Ksiazki;
}
private void btnZmienStatus_Click(object sender, RoutedEventArgs e)
{
var ks = (sender as FrameworkElement)?.DataContext as Ksiazka;
if (ks != null) ks.Wypozyczona = !ks.Wypozyczona;
}
private void txtSzukaj_TextChanged(object sender, TextChangedEventArgs e)
{
string filtr = txtSzukaj.Text.ToLower();
if (string.IsNullOrWhiteSpace(filtr))
{
lvKsiazki.ItemsSource = oryginalnaLista;
return;
}
var wyniki = new ObservableCollection<Ksiazka>(
oryginalnaLista.Where(k =>
k.Tytul.ToLower().Contains(filtr) ||
k.Autor.ToLower().Contains(filtr)));
lvKsiazki.ItemsSource = wyniki;
}
}
}
Co jest takie samo, a co inne?
| Mechanizm | .NET MAUI | WPF |
|---|---|---|
| Lista danych | CollectionView | ListView |
| Kontekst danych | BindingContext | DataContext |
| Zdarzenie przycisku | Clicked | Click |
| Filtrowanie | SearchBar.TextChanged | TextBox.TextChanged |
| Kolekcja danych | ObservableCollection | ObservableCollection |
Model z INotifyPropertyChanged | wspólny | wspólny |
Zadania dla ucznia (pozostają takie same)
- Dodaj do modelu pole
RokWydaniai wyświetl je w liście. - Dodaj filtr „Pokaż tylko wypożyczone książki”.
- Dodaj przycisk „Dodaj książkę”, który otwiera nowe okno / stronę z formularzem dodawania książki.
Podsumowanie
- Mechanizm listy danych jest taki sam w .NET MAUI i WPF – różni się tylko nazwami kontrolek.
- Najważniejsze jest zrozumienie trzech warstw: Modele, Logika, Widok.
ObservableCollectionodświeża interfejs automatycznie.INotifyPropertyChangedinformuje o zmianach danych.DataTemplatepozwala zdefiniować wygląd każdego elementu listy.
Gdy to zrozumiesz, umiesz już stworzyć każdą listę danych — w aplikacji desktopowej, webowej czy mobilnej.
###################################################################
Jeśli dotarłeś do tej części bez przewijania i ślepego wklejania mojego kodu, to zauważyłeś, że nie ma tu walidacji !!! OMG. jak to możliwe, że ten artykuł to nie 100% SIGMA 🙂
Uzupełnienie: Walidacja danych i formularz „Dodaj książkę” (MAUI + WPF)
Po co walidacja?
Walidacja to sprawdzanie, czy dane wpisane przez użytkownika mają sens, zanim trafią do listy. Dzięki temu:
- unikamy głupich błędów (puste tytuły, literówki zamiast roku),
- uczeń widzi jasny komunikat co poprawić,
- aplikacja jest stabilniejsza.
Zasada: sprawdź → jeśli OK, zbuduj model → dodaj do kolekcji.
Wspólny rdzeń (działa i w MAUI, i w WPF)
Modele/Ksiazka.cs
(jeśli już masz, tylko uzupełnij o RokWydania jako opcjonalne; zostaw niewymagane, bo część klas już robi zad. 1 „dodaj rok”)
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace BibliotekaApp.Modele
{
public class Ksiazka : INotifyPropertyChanged
{
private string _tytul = "";
private string _autor = "";
private int? _rokWydania; // opcjonalny (zad. 1 go wykorzysta)
private bool _wypozyczona;
public string Tytul
{
get => _tytul;
set { if (_tytul != value) { _tytul = value; OnPropertyChanged(); } }
}
public string Autor
{
get => _autor;
set { if (_autor != value) { _autor = value; OnPropertyChanged(); } }
}
public int? RokWydania
{
get => _rokWydania;
set { if (_rokWydania != value) { _rokWydania = value; OnPropertyChanged(); } }
}
public bool Wypozyczona
{
get => _wypozyczona;
set
{
if (_wypozyczona != value)
{
_wypozyczona = value;
OnPropertyChanged();
OnPropertyChanged(nameof(StatusOpis));
OnPropertyChanged(nameof(AkcjaTekst));
}
}
}
public string StatusOpis => Wypozyczona ? "Wypożyczona" : "Dostępna";
public string AkcjaTekst => Wypozyczona ? "Zwróć" : "Wypożycz";
public event PropertyChangedEventHandler? PropertyChanged;
void OnPropertyChanged([CallerMemberName] string? nazwa = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nazwa));
}
}
Logika/BibliotekaViewModel.cs
(bez zmian — lista książek w ObservableCollection)
using System.Collections.ObjectModel;
using BibliotekaApp.Modele;
namespace BibliotekaApp.Logika
{
public class BibliotekaViewModel
{
public ObservableCollection<Ksiazka> Ksiazki { get; }
public BibliotekaViewModel()
{
Ksiazki = new ObservableCollection<Ksiazka>
{
new Ksiazka { Tytul = "Hobbit", Autor = "J.R.R. Tolkien" },
new Ksiazka { Tytul = "Dune", Autor = "Frank Herbert", Wypozyczona = true, RokWydania = 1965 },
new Ksiazka { Tytul = "1984", Autor = "George Orwell", RokWydania = 1949 }
};
}
}
}
Walidacja/Walidacje.cs
(małe, wielorazowe helpery)
namespace BibliotekaApp.Walidacja
{
public static class Walidacje
{
public static bool NiePuste(string? txt) =>
!string.IsNullOrWhiteSpace(txt);
public static int? SprobujInt(string? txt)
{
if (string.IsNullOrWhiteSpace(txt)) return null;
return int.TryParse(txt.Trim(), out int val) ? val : null;
}
public static bool WZakresie(int? val, int min, int max) =>
val is not null && val >= min && val <= max;
}
}
Walidacja/WalidatorKsiazki.cs
(jedna metoda: przyjmuje teksty z formularza, mówi co źle, albo zwraca gotowy model)
using System.Collections.Generic;
using BibliotekaApp.Modele;
namespace BibliotekaApp.Walidacja
{
public static class WalidatorKsiazki
{
// rokWydaniaTxt może być pusty (opcjonalny) – jeśli klasa robi już Zadanie 1,
// to to pole będzie wymagane wg ustaleń nauczyciela.
public static (bool ok, List<string> bledy, Ksiazka? model)
SprawdzIZbuduj(string? tytulTxt, string? autorTxt, string? rokWydaniaTxt, bool wypozyczona)
{
var bledy = new List<string>();
// 1) Tytuł
if (!Walidacje.NiePuste(tytulTxt))
bledy.Add("Tytuł nie może być pusty.");
else if (tytulTxt!.Trim().Length < 2)
bledy.Add("Tytuł musi mieć co najmniej 2 znaki.");
// 2) Autor
if (!Walidacje.NiePuste(autorTxt))
bledy.Add("Autor nie może być pusty.");
else if (autorTxt!.Trim().Length < 2)
bledy.Add("Autor musi mieć co najmniej 2 znaki.");
// 3) Rok wydania (opcjonalny; jeśli uczeń robi Zad. 1 – zrób „wymagany”)
int? rok = Walidacje.SprobujInt(rokWydaniaTxt);
if (!string.IsNullOrWhiteSpace(rokWydaniaTxt))
{
if (rok is null) bledy.Add("Rok wydania musi być liczbą całkowitą.");
else if (!Walidacje.WZakresie(rok, 1450, 2100))
bledy.Add("Rok wydania musi być w zakresie 1450–2100.");
}
if (bledy.Count > 0)
return (false, bledy, null);
var model = new Ksiazka
{
Tytul = tytulTxt!.Trim(),
Autor = autorTxt!.Trim(),
RokWydania = rok,
Wypozyczona = wypozyczona
};
return (true, bledy, model);
}
}
}
SEKCJA A – .NET MAUI: Formularz „Dodaj książkę” z walidacją
Dodaj przycisk „Dodaj książkę” na liście i prostą stronę formularza.
1) Dopisz przycisk w istniejącej liście
MainPage.xaml – nad CollectionView:
<Button Text="Dodaj książkę" Clicked="DodajKsiazke_Click" />
MainPage.xaml.cs – obsługa:
private async void DodajKsiazke_Click(object sender, EventArgs e)
{
await Navigation.PushAsync(new DodajKsiazkePage(vm)); // przekaż ViewModel
}
Upewnij się, że w App.xaml.cs masz
MainPage = new NavigationPage(new MainPage());żeby działała nawigacja.
2) Nowa strona formularza
DodajKsiazkePage.xaml
<ContentPage
x:Class="BibliotekaApp.DodajKsiazkePage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
Title="Dodaj książkę">
<ScrollView>
<VerticalStackLayout Padding="20" Spacing="10">
<Entry x:Name="entTytul" Placeholder="Tytuł" />
<Entry x:Name="entAutor" Placeholder="Autor" />
<Entry x:Name="entRok" Placeholder="Rok wydania (opcjonalnie)" Keyboard="Numeric" />
<HorizontalStackLayout>
<Label Text="Wypożyczona:" VerticalOptions="Center" />
<Switch x:Name="swWypozyczona" />
</HorizontalStackLayout>
<Button Text="Zapisz" Clicked="Zapisz_Click" />
<Label x:Name="lblBledy" TextColor="Red" />
</VerticalStackLayout>
</ScrollView>
</ContentPage>
DodajKsiazkePage.xaml.cs
using BibliotekaApp.Logika;
using BibliotekaApp.Walidacja;
namespace BibliotekaApp
{
public partial class DodajKsiazkePage : ContentPage
{
private readonly BibliotekaViewModel _vm;
public DodajKsiazkePage(BibliotekaViewModel vm)
{
InitializeComponent();
_vm = vm;
}
private async void Zapisz_Click(object sender, EventArgs e)
{
var (ok, bledy, model) = WalidatorKsiazki.SprawdzIZbuduj(
entTytul.Text, entAutor.Text, entRok.Text, swWypozyczona.IsToggled);
if (!ok)
{
lblBledy.Text = "Popraw dane:\n- " + string.Join("\n- ", bledy);
return;
}
_vm.Ksiazki.Add(model!);
await DisplayAlert("Sukces", "Książka dodana.", "OK");
await Navigation.PopAsync();
}
}
}
Efekt: jeśli pola są błędne – czerwone komunikaty; jeśli OK – książka trafia do listy i wracasz do ekranu głównego.
SEKCJA B – WPF: Formularz „Dodaj książkę” z walidacją
Analogicznie: przycisk „Dodaj książkę” w MainWindow + nowe okno dialogowe.
1) Przycisk „Dodaj książkę”
MainWindow.xaml – nad ListView:
<Button Content="Dodaj książkę" Click="DodajKsiazke_Click" Height="28" Width="120" Margin="0,0,0,6" />
MainWindow.xaml.cs – obsługa:
private void DodajKsiazke_Click(object sender, RoutedEventArgs e)
{
var okno = new DodajKsiazkeWindow();
if (okno.ShowDialog() == true && okno.Model != null)
{
vm.Ksiazki.Add(okno.Model);
}
}
2) Nowe okno dialogowe
DodajKsiazkeWindow.xaml
<Window x:Class="BibliotekaApp.DodajKsiazkeWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Dodaj książkę" Height="260" Width="360" WindowStartupLocation="CenterOwner">
<Grid Margin="16" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto" RowSpacing="8">
<TextBlock Text="Tytuł:" />
<TextBox x:Name="txtTytul" Grid.Row="1" />
<TextBlock Text="Autor:" Grid.Row="2" />
<TextBox x:Name="txtAutor" Grid.Row="3" />
<TextBlock Text="Rok wydania (opcjonalnie):" Grid.Row="4" />
<StackPanel Grid.Row="5" Orientation="Horizontal" Spacing="8">
<TextBox x:Name="txtRok" Width="120" />
<CheckBox x:Name="chkWypozyczona" Content="Wypożyczona" VerticalAlignment="Center" />
<Button Content="Zapisz" Width="80" Click="Zapisz_Click" />
</StackPanel>
<TextBlock x:Name="txtBledy" Grid.Row="6" Foreground="Red" TextWrapping="Wrap" />
</Grid>
</Window>
DodajKsiazkeWindow.xaml.cs
using System.Windows;
using BibliotekaApp.Modele;
using BibliotekaApp.Walidacja;
namespace BibliotekaApp
{
public partial class DodajKsiazkeWindow : Window
{
public Ksiazka? Model { get; private set; }
public DodajKsiazkeWindow()
{
InitializeComponent();
}
private void Zapisz_Click(object sender, RoutedEventArgs e)
{
var (ok, bledy, model) = WalidatorKsiazki.SprawdzIZbuduj(
txtTytul.Text, txtAutor.Text, txtRok.Text, chkWypozyczona.IsChecked == true);
if (!ok)
{
txtBledy.Text = "Popraw dane:\n- " + string.Join("\n- ", bledy);
return;
}
Model = model;
DialogResult = true;
Close();
}
}
}
Efekt: w razie błędu – komunikaty w TextBlock; jeśli OK – okno zwraca DialogResult=true, a MainWindow dodaje książkę do kolekcji.
###########################
Schemat działania: Model – Binding – ListView w WPF
┌──────────────────────────────────────────────────────────┐
(1) │ MODELE/Ksiazka.cs │
│ Klasa z właściwościami: Tytul, Autor, Wypozyczona, ... │
│ Implementuje INotifyPropertyChanged │
└──────────────┬──────────────────────────────────────────┘
│ (tworzy obiekty książek)
▼
┌──────────────────────────────────────────────────────────┐
(2) │ LOGIKA/BibliotekaViewModel.cs │
│ Zawiera ObservableCollection<Ksiazka> │
│ = dynamiczna lista obiektów (Ksiazki) │
│ Dodaje, usuwa, przechowuje książki │
└──────────────┬──────────────────────────────────────────┘
│ (przekazanie kolekcji do UI)
▼
┌──────────────────────────────────────────────────────────┐
(3) │ MAINWINDOW.XAML.CS │
│ Tworzy obiekt BibliotekaViewModel │
│ DataContext = vm → powiązanie całego okna z danymi │
└──────────────┬──────────────────────────────────────────┘
│ (dane powiązane z UI)
▼
┌──────────────────────────────────────────────────────────┐
(4) │ MAINWINDOW.XAML │
│ ListView ItemsSource="{Binding Ksiazki}" │
│ ItemTemplate → wzór jednego wiersza │
│ Każdy wiersz ma DataContext = jedna książka │
└──────────────┬──────────────────────────────────────────┘
│ (binding do właściwości książki)
▼
┌──────────────────────────────────────────────────────────┐
(5) │ BINDING ENGINE │
│ Łączy pola tekstowe, przyciski, etykiety z właściwościami│
│ np. Text="{Binding Tytul}", Content="{Binding AkcjaTekst}"│
│ Działa w obie strony (TwoWay) │
└──────────────┬──────────────────────────────────────────┘
│ (zmiana w modelu odświeża UI)
▼
┌──────────────────────────────────────────────────────────┐
(6) │ UŻYTKOWNIK │
│ Klika przycisk w wierszu ListView │
│ sender.DataContext → konkretny obiekt Ksiazka │
│ Kod: k.Wypozyczona = !k.Wypozyczona; │
└──────────────┬──────────────────────────────────────────┘
│ (zgłoszenie zmiany)
▼
┌──────────────────────────────────────────────────────────┐
(7) │ INotifyPropertyChanged │
│ Wywołuje OnPropertyChanged("Wypozyczona") │
│ Binding Engine aktualizuje tylko ten wiersz │
│ Lista odświeża się automatycznie │
└──────────────────────────────────────────────────────────┘
Opis poszczególnych kroków
(1) Model – Ksiazka.cs
Zawiera pola opisujące książkę i zgłasza zmiany.
Gdy właściwość się zmienia, wywołuje OnPropertyChanged, co informuje WPF, że trzeba odświeżyć UI.
(2) Logika – BibliotekaViewModel.cs
Tworzy kolekcję ObservableCollection<Ksiazka>.
Ta kolekcja potrafi sama powiadomić interfejs o dodaniu, usunięciu lub zmianie elementu.
(3) Połączenie danych – MainWindow.xaml.cs
W konstruktorze okna ustawiasz:
DataContext = new BibliotekaViewModel();
To jakby powiedzieć:
„Całe to okno ma patrzeć na dane z tej klasy”.
(4) Widok – MainWindow.xaml
ListView ma:
<ListView ItemsSource="{Binding Ksiazki}">
czyli automatycznie pobiera listę książek z ViewModelu (DataContext.Ksiazki),
a ItemTemplate mówi, jak wygląda każdy wiersz.
WPF tworzy po jednym „kafelku” (DataTemplate) dla każdej książki.
(5) Silnik bindingu
Łączy dane (Tytul, Autor, StatusOpis) z elementami interfejsu (TextBlock, Button).
Jeśli Binding jest dwukierunkowy (Mode=TwoWay), zmiana w formularzu zmienia dane w obiekcie i odwrotnie.
(6) Reakcja użytkownika
Użytkownik klika przycisk „Wypożycz / Zwróć”.
Kod:
if ((sender as FrameworkElement)?.DataContext is Ksiazka k)
k.Wypozyczona = !k.Wypozyczona;
Każdy przycisk ma własny DataContext, więc dokładnie wie, której książki dotyczy.
(7) Aktualizacja interfejsu
Model zgłasza PropertyChanged.
Silnik bindingu odbiera to i automatycznie odświeża tylko ten element listy,
bez ręcznego odmalowywania UI.
W skrócie…
| Etap | Co się dzieje | Mechanizm |
|---|---|---|
| 1 | Tworzymy klasę Ksiazka | INotifyPropertyChanged |
| 2 | Tworzymy listę książek | ObservableCollection |
| 3 | Podpinamy dane do okna | DataContext |
| 4 | Ustawiamy ItemsSource listy | Binding |
| 5 | WPF łączy dane z kontrolkami | Binding Engine |
| 6 | Klik w przycisk → zmiana danych | DataContext (wiersza) |
| 7 | Model wysyła powiadomienie → UI się odświeża | PropertyChanged |
###########################
***
Model z polem okładki
Modele/Ksiazka.cs
(dodaj jedną właściwość; ścieżka względna do pliku w projekcie albo pełna ścieżka na dysku)
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace BibliotekaApp.Modele
{
public class Ksiazka : INotifyPropertyChanged
{
private string _tytul = "";
private string _autor = "";
private bool _wypozyczona;
private string? _okladka; // np. "Assets/Okladki/hobbit.jpg" albo pełna ścieżka
public string Tytul { get => _tytul; set { _tytul = value; OnPropertyChanged(); } }
public string Autor { get => _autor; set { _autor = value; OnPropertyChanged(); } }
public bool Wypozyczona
{
get => _wypozyczona;
set { _wypozyczona = value; OnPropertyChanged(); OnPropertyChanged(nameof(StatusOpis)); OnPropertyChanged(nameof(AkcjaTekst)); }
}
public string? Okladka { get => _okladka; set { _okladka = value; OnPropertyChanged(); } }
public string StatusOpis => Wypozyczona ? "Wypożyczona" : "Dostępna";
public string AkcjaTekst => Wypozyczona ? "Zwróć" : "Wypożycz";
public event PropertyChangedEventHandler? PropertyChanged;
void OnPropertyChanged([CallerMemberName] string? n = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n));
}
}
2) Gdzie trzymać pliki graficzne
Najprościej: folder w projekcie np. Assets/Okladki.
- wrzuć tam pliki JPG/PNG,
- zaznacz pliki → Właściwości:
- Build Action:
Resource - (opcjonalnie) Copy to Output Directory:
Do not copy– nie trzeba, boResourcepakuje do EXE.
- Build Action:
Wtedy możesz używać krótkich ścieżek (bez pack://...), WPF sam je znajdzie.
3) Przykładowe dane z okładką
Logika/BibliotekaViewModel.cs (fragment)
Ksiazki = new ObservableCollection<Ksiazka>
{
new Ksiazka { Tytul="Hobbit", Autor="J.R.R. Tolkien", Okladka="Assets/Okladki/hobbit.jpg" },
new Ksiazka { Tytul="Dune", Autor="Frank Herbert", Wypozyczona=true, Okladka="Assets/Okladki/dune.jpg" },
new Ksiazka { Tytul="1984", Autor="George Orwell", Okladka="Assets/Okladki/1984.jpg" }
};
4) Szablon wiersza listy: okładka po lewej, tekst po prawej
MainWindow.xaml – ListView.ItemTemplate
<ListView x:Name="lvKsiazki"
ScrollViewer.CanContentScroll="True"
VirtualizingPanel.IsVirtualizing="True">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type models:Ksiazka}">
<Grid Margin="0,0,0,8" ColumnDefinitions="Auto,* ,Auto">
<!-- OKŁADKA PO LEWEJ -->
<Border Width="64" Height="96" Margin="0,0,12,0" Background="#EEE" CornerRadius="4">
<Image Source="{Binding Okladka, TargetNullValue={StaticResource PlaceholderImg}}"
Stretch="UniformToFill"
Width="64" Height="96"/>
</Border>
<!-- TEKSTY -->
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock Text="{Binding Tytul}" FontWeight="Bold" FontSize="14"/>
<TextBlock Text="{Binding Autor}" Foreground="#666"/>
<TextBlock Text="{Binding StatusOpis}"/>
</StackPanel>
<!-- PRZYCISK AKCJI PO PRAWEJ -->
<Button Grid.Column="2"
Content="{Binding AkcjaTekst}"
Padding="10,4" Margin="12,0,0,0"
Click="btnAkcja_Click"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
oraz u góry pliku (w zasobach okna) możesz dodać placeholder na brak okładki:
<Window.Resources>
<BitmapImage x:Key="PlaceholderImg" UriSource="Assets/Okladki/placeholder.png"/>
</Window.Resources>
jeśli nie chcesz placeholdera, usuń
TargetNullValue=.... GdyOkladkajest pusta,Imagebędzie po prostu „szare tło” zBorder.
dlaczego to działa z listą?
tak jak przy przycisku: każdy wiersz ma własny DataContext → konkretny obiekt Ksiazka.Image.Source="{Binding Okladka}" w wierszu 1 binduje do Ksiazka#1.Okladka, w wierszu 2 do Ksiazka#2.Okladka, itd.
WPF sam tworzy wizualny element dla każdej pozycji i wiąże go z odpowiednim obiektem z ItemsSource.

