Wyświetlanie danych w liście (CollectionView)

Po co lista i jak to działa

Gdy masz wiele elementów (np. imiona, produkty), nie dodajesz 100 etykiet. Używasz listy – w MAUI najnowszą kontrolką jest CollectionView.
Jej mechanizm jest prosty:

  • ItemsSource – źródło danych (np. lista imion),
  • ItemTemplate – szablon jak narysować jeden element listy,
  • Binding – „połączenie” pól obiektu z kontrolkami w szablonie,
  • SelectionChanged – reakcja na zaznaczenie elementu.

Dla początkujących polecam ObservableCollection zamiast List. Gdy coś dodasz/usuńsz, UI odświeża się samo.


Co tworzymy w projekcie (VS 2022)

Na start zrobimy wersję podstawową (lista stringów – zero modeli).
Potem wersję + (opcjonalnie): lista obiektów z dwoma polami.

  1. Dodaj stronę widoku:
    Solution Explorer → prawy klik na projekt → Add → New Item… → .NET MAUI ContentPage (XAML)
    Nazwa: ListaPage.xaml
    To utworzy dwa pliki: ListaPage.xaml (UI) i ListaPage.xaml.cs (logika).
  2. (Wersja +) Jeśli chcesz listę obiektów z polami, dodamy prostą Class później.

Wersja podstawowa — lista stringów (najprostsza)

ListaPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="TwojProjekt.ListaPage"
             Title="Lista imion">

  <VerticalStackLayout Padding="16" Spacing="12">

    <!-- Dodawanie nowej pozycji -->
    <HorizontalStackLayout Spacing="8">
      <Entry x:Name="NoweImieEntry" Placeholder="Wpisz imię"/>
      <Button Text="Dodaj" Clicked="Dodaj_Click"/>
    </HorizontalStackLayout>

    <!-- Lista: gdy pusta, pokaż wiadomość -->
    <CollectionView x:Name="Lista"
                    SelectionMode="Single"
                    SelectionChanged="Lista_SelectionChanged">
      <CollectionView.EmptyView>
        <Label Text="Brak elementów. Dodaj coś powyżej." TextColor="Gray"/>
      </CollectionView.EmptyView>

      <!-- Szablon jednego elementu (tu: zwykły string) -->
      <CollectionView.ItemTemplate>
        <DataTemplate>
          <!-- {Binding} bez nazwy = cały string -->
          <Label Text="{Binding}" FontSize="18" Padding="6,10"/>
        </DataTemplate>
      </CollectionView.ItemTemplate>
    </CollectionView>

    <!-- Operacje na liście -->
    <HorizontalStackLayout Spacing="8">
      <Button Text="Usuń zaznaczone" Clicked="UsunZaznaczone_Click"/>
      <Button Text="Wyczyść listę"    Clicked="Wyczysc_Click"/>
    </HorizontalStackLayout>

    <!-- Informacje dla użytkownika -->
    <Label x:Name="Status" TextColor="Gray"/>

  </VerticalStackLayout>
</ContentPage>

ListaPage.xaml.cs

using System.Collections.ObjectModel;  // ObservableCollection

namespace TwojProjekt;

public partial class ListaPage : ContentPage
{
    // 1) Kolekcja, która "powiadamia" UI o zmianach (dodanie/usunięcie)
    private ObservableCollection<string> _imiona = new();

    public ListaPage()
    {
        InitializeComponent();          // 2) Tworzy kontrolki z XAML
        Lista.ItemsSource = _imiona;    // 3) Podpinamy źródło danych do listy
    }

    // Dodaj nowy element
    private void Dodaj_Click(object sender, EventArgs e)
    {
        if (!string.IsNullOrWhiteSpace(NoweImieEntry.Text))
        {
            _imiona.Add(NoweImieEntry.Text.Trim());  // 4) Dodanie do kolekcji automatycznie odświeży UI
            NoweImieEntry.Text = "";                 // 5) Wyczyść pole
            Status.Text = "Dodano element.";
        }
        else
        {
            Status.Text = "Wpisz imię przed dodaniem.";
        }
    }

    // Usuwanie zaznaczonego elementu
    private void UsunZaznaczone_Click(object sender, EventArgs e)
    {
        if (Lista.SelectedItem is string wybrane)
        {
            _imiona.Remove(wybrane);
            Status.Text = $"Usunięto: {wybrane}";
        }
        else
        {
            Status.Text = "Najpierw zaznacz element na liście.";
        }
    }

    // Czyści całą listę
    private void Wyczysc_Click(object sender, EventArgs e)
    {
        _imiona.Clear();
        Status.Text = "Wyczyszczono listę.";
    }

    // Reakcja na zaznaczenie w liście
    private void Lista_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.CurrentSelection.FirstOrDefault() is string wybrane)
            Status.Text = $"Zaznaczono: {wybrane}";
    }
}

Co się stało (wersja podstawowa)

  • Użyliśmy ObservableCollection – UI samo wie, że doszedł/odpadł element.
  • ItemsSource łączy listę danych z CollectionView.
  • ItemTemplate rysuje jeden element. Dla stringów Text="{Binding}" to cały tekst.
  • Zdarzenia:
    • Dodaj_Click – dopisuje do _imiona,
    • UsunZaznaczone_Click – usuwa zaznaczony,
    • Wyczysc_Click – czyści całość,
    • SelectionChanged – pokazuje, co zaznaczono.

To jest najprostszy schemat INF.04 na „lista + operacje na liście”.


Wersja + (opcjonalnie) — lista obiektów z polami

Chcesz pokazać dwa pola (np. nazwa i cena)? Potrzebny jest prosty model – zwykła klasa.

Dodaj model (Class)

Solution Explorer → prawy klik na projekt → Add → Class → nazwa Produkt.cs
Wklej:

namespace TwojProjekt;

// Prosty model: 2 publiczne właściwości
public class Produkt
{
    public string Nazwa { get; set; } = "";
    public decimal Cena { get; set; } = 0m;
}

Zmień UI, by wyświetlać 2 pola

W ListaPage.xaml podmień część z polami i szablonem listy na taki kod:

<!-- Formularz dodawania produktu -->
<HorizontalStackLayout Spacing="8">
  <Entry x:Name="NazwaEntry" Placeholder="Nazwa produktu" WidthRequest="180"/>
  <Entry x:Name="CenaEntry"  Placeholder="Cena (np. 19,99)" Keyboard="Numeric" WidthRequest="120"/>
  <Button Text="Dodaj" Clicked="DodajProdukt_Click"/>
</HorizontalStackLayout>

<!-- Lista produktów z 2 kolumnami -->
<CollectionView x:Name="ListaProd"
                SelectionMode="Single"
                SelectionChanged="ListaProd_SelectionChanged">
  <CollectionView.EmptyView>
    <Label Text="Brak produktów." TextColor="Gray"/>
  </CollectionView.EmptyView>

  <CollectionView.ItemTemplate>
    <DataTemplate>
      <!-- Grid: 2 kolumny: nazwa i cena -->
      <Grid Padding="6,10" ColumnDefinitions="*,Auto">
        <Label Text="{Binding Nazwa}" Grid.Column="0" FontSize="18"/>
        <Label Text="{Binding Cena, StringFormat='{}{0:C}'}" Grid.Column="1" />
      </Grid>
    </DataTemplate>
  </CollectionView.ItemTemplate>
</CollectionView>

<!-- Operacje na produktach -->
<HorizontalStackLayout Spacing="8">
  <Button Text="Usuń zaznaczony" Clicked="UsunProdukt_Click"/>
  <Button Text="Wyczyść listę"    Clicked="WyczyscProdukty_Click"/>
</HorizontalStackLayout>

<Label x:Name="Status2" TextColor="Gray"/>

Logika dla produktów – ListaPage.xaml.cs

Dodaj drugi zestaw pól i metod (nie kasuj wersji podstawowej, możesz mieć obie sekcje na jednej stronie):

using System.Collections.ObjectModel;

public partial class ListaPage : ContentPage
{
    // ...
    private ObservableCollection<Produkt> _produkty = new();

    public ListaPage()
    {
        InitializeComponent();

        // ... (podstawowa lista stringów)
        Lista.ItemsSource = _imiona;

        // Lista produktów (dla wersji +)
        ListaProd.ItemsSource = _produkty;
    }

    private void DodajProdukt_Click(object sender, EventArgs e)
    {
        // Prosta walidacja i parsowanie ceny
        if (string.IsNullOrWhiteSpace(NazwaEntry.Text))
        {
            Status2.Text = "Podaj nazwę.";
            return;
        }
        if (!decimal.TryParse(CenaEntry.Text?.Replace(',', '.'), System.Globalization.NumberStyles.Any,
                              System.Globalization.CultureInfo.InvariantCulture, out var cena))
        {
            Status2.Text = "Podaj poprawną cenę (np. 19,99).";
            return;
        }

        _produkty.Add(new Produkt { Nazwa = NazwaEntry.Text.Trim(), Cena = cena });

        NazwaEntry.Text = ""; 
        CenaEntry.Text  = "";
        Status2.Text = "Dodano produkt.";
    }

    private void UsunProdukt_Click(object sender, EventArgs e)
    {
        if (ListaProd.SelectedItem is Produkt p)
        {
            _produkty.Remove(p);
            Status2.Text = $"Usunięto: {p.Nazwa}";
        }
        else
        {
            Status2.Text = "Zaznacz produkt na liście.";
        }
    }

    private void WyczyscProdukty_Click(object sender, EventArgs e)
    {
        _produkty.Clear();
        Status2.Text = "Wyczyszczono listę produktów.";
    }

    private void ListaProd_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.CurrentSelection.FirstOrDefault() is Produkt p)
            Status2.Text = $"Zaznaczono: {p.Nazwa} ({p.Cena:C})";
    }
}

Co się stało (wersja +)

  • Dodaliśmy model Produkt (zwykła Class) z dwiema właściwościami.
  • W ItemTemplate bindowaliśmy Nazwa i Cena do dwóch etykiet (w Gridzie).
  • Użyliśmy decimal.TryParse, żeby zamienić tekst z Entry na liczbę (prosta walidacja).
  • Cała reszta identyczna: ObservableCollection → ItemsSource → operacje.

Najczęstsze potknięcia i szybkie naprawy

  • Dodałem do List<>, a UI się nie odświeża → użyj ObservableCollection.
  • Klikam „Usuń”, ale nic się nie dzieje → sprawdź, czy coś jest zaznaczone (SelectedItem).
  • Błąd bindowania → nazwy właściwości w XAML muszą zgadzać się z nazwami w klasie (np. Nazwa, Cena).
  • Format cenyStringFormat='{}{0:C}' formatuje na walutę wg języka systemu.

Podsumowanie

  • Znasz CollectionView: ItemsSource, ItemTemplate, SelectionChanged.
  • Wiesz, że do automatycznego odświeżania UI najlepiej służy ObservableCollection.
  • Umiemy zobaczyć dwie wersje: lista stringów i lista obiektów z dwoma polami.
  • To pokrywa typowe wymagania INF.04 dotyczące list i operacji na danych w UI.