Aplikacje mobilne · Listy

Wyświetlanie list danych: CollectionView, wiązanie i ItemTemplate

Wiesz już, że CollectionView służy do list. Teraz zobaczysz, jak używa się jej naprawdę: skąd bierze dane, jak wygląda jeden wiersz i jak sprawić, żeby lista odświeżała się sama, także gdy zmieni się pojedyncze pole obiektu i gdy użytkownik filtruje listę wyszukiwarką.

  • około 55 minut
  • .NET MAUI · CollectionView · ObservableCollection · INotifyPropertyChanged
  • INF.04.6.2

Po tej lekcji: połączyć CollectionView z kolekcją, opisać wiersz w ItemTemplate, zadbać o odświeżanie przez ObservableCollection i INotifyPropertyChanged oraz poprawnie filtrować listę.

Biblioteka z wyszukiwaniem i zmianą statusu

Lista nie jest fotografią danych. Użytkownik filtruje książki i je wypożycza, a interfejs musi reagować na zmianę obiektu, nie tylko na dodanie nowego wiersza.

Najpierw uruchomisz mały ekran. Potem połączysz go z kodem i sprawdzisz konkretne wyniki. Dodatki wykonuj dopiero, gdy wersja podstawowa działa. Quiz i oceniane zadania pozostają w biurku po zalogowaniu.

Laboratorium · ćwiczenie w przeglądarce, do którego odsyłają rozgrzewki na końcu lekcji

1 · Sprawdź w działaniu

Kiedy lista się odświeża, a kiedy nie

Kliknij „Wypożycz” przy trzech konfiguracjach modelu: bez powiadamiania, z powiadamianiem tylko własnej właściwości i z powiadamianiem także właściwości wyliczanych. Potem wyszukaj „o”, skasuj znak i porównaj dwa tryby filtrowania.

Najpierw przewidź, potem sprawdź: W modelu listy zmień status książki, potem wpisz filtr i go usuń. Zobacz, czy zmieniony obiekt nadal ma ten sam status po ponownym pokazaniu.

Ta interakcja wymaga włączonego JavaScriptu.

Model musi zgłosić każdą właściwość, od której zależy widok, także wyliczaną. Filtruj zawsze zapamiętaną listę oryginalną. Model pomaga zobaczyć mechanizm; właściwą aplikację uruchomisz z plików poniżej.

1. Przygotuj miejsce pracy

Co dodajesz, a co podmieniasz?

Stronę dodaj przez Projekt → Dodaj → Nowy element → .NET MAUI ContentPage (XAML). Powstaje para .xaml i .xaml.cs. Dla ContentView wybierz szablon ContentView; dla modeli i serwisów zwykłą klasę. Jeśli plik już istnieje, podmień wskazaną zawartość — nie twórz drugiej klasy o tej samej nazwie.

PlikRola w ćwiczeniu
Modele/Ksiazka.csLogika i obsługa danych
Logika/BibliotekaViewModel.csLogika i obsługa danych
MainPage.xamlWygląd strony lub kontrolki
MainPage.xaml.csLogika i obsługa danych
WierszUstawienia.xamlWygląd strony lub kontrolki
WierszUstawienia.xaml.csLogika i obsługa danych
UstawieniaPage.xamlWygląd strony lub kontrolki

Zanim uruchomisz: trzy połączenia

x:Class="MojaAplikacja.NazwaStrony" w XAML musi zgadzać się z namespace MojaAplikacja i nazwą klasy C#. InitializeComponent() tworzy kontrolki opisane w XAML.

x:Name="PoleImie" daje kodowi C# dostęp przez PoleImie.Text. Z kolei Clicked="Zapisz_Clicked" wskazuje metodę o tej nazwie w pliku .xaml.cs. Sam napis przycisku w Text niczego nie podpina.

Gdy dodajesz XAML z obsługą zdarzeń, od razu uzupełnij też odpowiadający C#. Uruchamiaj dopiero po skończeniu oznaczonej pary plików; brakujący handler nie jest błędem środowiska.

2. Uruchom sam wygląd — jeszcze bez logiki

W świeżym projekcie zastąp MainPage.xaml poniższym ekranem. W MainPage.xaml.cs zachowaj konstruktor z InitializeComponent(); usuń szablonowy licznik i OnCounterClicked, bo nie ma ich już w XAML. Na tym etapie App i AppShell pozostają z szablonu. To pełny, tymczasowy XAML — później świadomie go podmienisz, nie dokleisz do drugiego ContentPage.

MainPage.xaml — etap tymczasowyxaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MojaAplikacja.MainPage">
    <VerticalStackLayout Padding="24" Spacing="14">
        <Label Text="Moja biblioteka" FontSize="24" />
        <SearchBar Placeholder="Szukaj książki" />
        <Label Text="Tutaj za chwilę pojawią się dane z modelu." />
    </VerticalStackLayout>
</ContentPage>
  1. Pierwsza próba Uruchom. Zobaczysz kontrolki, ale przyciski bez Clicked jeszcze nic nie robią. Sprawdź rozmiar i kolejność elementów. Zatrzymaj aplikację przed następnym etapem.

Zacznijmy od biblioteki

Program ma sam wypełnić wiersze

Chcesz pokazać listę książek, produktów albo zadań. Nie tworzysz osobnej kontrolki Label dla każdej pozycji. Opisujesz jeden wiersz, a program sam powtarza go dla każdego elementu kolekcji.

Trzy rzeczy muszą współgrać: kolekcja (ObservableCollection<T>), model z powiadamianiem o zmianach (INotifyPropertyChanged) i szablon wiersza (ItemTemplate).

2 · Dwa rodzaje powiadomień

ObservableCollection i INotifyPropertyChanged

MechanizmO czym powiadamiaEfekt braku
ObservableCollection<T>Dodanie i usunięcie elementu kolekcji.Z List<T> nowa pozycja nie pojawia się na ekranie.
INotifyPropertyChangedZmianę jednej właściwości istniejącego obiektu.Zmiana statusu nie odświeża wiersza, lista wygląda na „zamrożoną”.
Atrybut `[CallerMemberName]` sam podstawia nazwę właściwości, z której wywołano metodę, więc nie wpisujesz jej ręcznie i nie ryzykujesz literówki.

Dla chętnych: własna kontrolka z BindableProperty

Po poznaniu Binding i INotifyPropertyChanged wróć do ContentView z lekcji5. Poniższe trzy pliki to wariant zamienny, nie druga klasa o tej samej nazwie do dodania obok. BindableProperty pozwala ustawiać Tytul/Opis również przez {Binding ...}. Wiersz jest zwykłym widokiem, nie komórką CollectionView.

3. Wskaż stronę startową ćwiczenia

Po dodaniu docelowych stron podmień App.xaml.cs na poniższy. App.xaml zostaje z szablonu. Od tej chwili startuje MainPage, a nie wcześniejszy tymczasowy MainPage z AppShell. Uzupełnij jej pliki w kolejnych etapach, zanim ponownie uruchomisz projekt.

App.xaml.cscsharp
namespace MojaAplikacja;

public partial class App : Application
{
    public App() => InitializeComponent();
    protected override Window CreateWindow(IActivationState? activationState)
        => new Window(new NavigationPage(new MainPage()));
}
  1. CreateWindow Tworzy okno aplikacji. NavigationPage jest kontenerem zwykłych stron i daje przycisk powrotu; nie łączymy go z Shell.
  2. new MainPage() Tworzy instancję docelowej strony. Aby testować drugi niezależny przykład, zatrzymaj program, zmień tylko ten typ i uruchom ponownie.

4. Połącz wygląd z działaniem

Teraz zbuduj docelowy przykład. Pełne pliki poniżej zastępują swoje wcześniejsze wersje. Przeczytaj opis nad kodem i objaśnienia pod nim; potem sprawdź przepływ danych, nie tylko brak czerwonych podkreśleń.

Gdy przykład ma kilka plików zależnych od siebie, najpierw dodaj wszystkie wskazane typy, a potem uruchom całość. Ustaw breakpoint w obsłudze głównego przycisku i prześledź jedną próbę.

Budowa krok po kroku

Ksiazka.cs

Ksiazka implementuje INotifyPropertyChanged. Samo ustawienie pola nie powiadomi widoku; OnPropertyChanged informuje go, co należy odczytać ponownie.

Każda właściwość zgłasza zmianę w setterze. Dodatkowo Wypozyczona zgłasza dwie właściwości wyliczane, które od niej zależą.

Modele/Ksiazka.cscsharp
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace MojaAplikacja.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));   // właściwości wyliczane też trzeba zgłosić
            OnPropertyChanged(nameof(AkcjaTekst));
        }
    }

    public string StatusOpis => Wypozyczona ? "Wypożyczona" : "Dostępna";
    public string AkcjaTekst => Wypozyczona ? "Zwróć" : "Wypożycz";

    public event PropertyChangedEventHandler? PropertyChanged;

    private void OnPropertyChanged([CallerMemberName] string? nazwa = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nazwa));
}
  1. OnPropertyChanged() w setterze Wywołanie bez argumentu podstawia nazwę właściwości dzięki [CallerMemberName].
  2. OnPropertyChanged(nameof(StatusOpis)) Etykieta ze statusem zależy od Wypozyczona, więc też trzeba ją zgłosić. Inaczej się nie odświeży.
  3. public string StatusOpis => Wypozyczona ? ... : ... Właściwość wyliczana, bez własnego pola.
  4. event PropertyChangedEventHandler? PropertyChanged Zdarzenie, które wiązania MAUI nasłuchują.

Budowa krok po kroku

BibliotekaViewModel.cs

ObservableCollection informuje o dodaniu lub usunięciu elementu, a sama Ksiazka o zmianie statusu wewnątrz wiersza. To dwa poziomy powiadomień.

Prosta klasa trzymająca kolekcję trzech książek.

Logika/BibliotekaViewModel.cscsharp
using System.Collections.ObjectModel;
using MojaAplikacja.Modele;

namespace MojaAplikacja.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 },
            new Ksiazka { Tytul = "1984", Autor = "George Orwell" }
        };
    }
}

Budowa krok po kroku

MainPage.xaml

Dodaj lub otwórz MainPage.xaml i podmień jego zawartość. Najpierw odszukaj kontener, potem nazwane kontrolki oraz wskazane zdarzenia. Pozostaw zgodne x:Class i nazwę klasy w pliku .xaml.cs.

DataTemplate opisuje jeden wiersz. MAUI powtórzy go dla każdej książki.

MainPage.xamlxaml
<?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"
             xmlns:models="clr-namespace:MojaAplikacja.Modele"
             x:Class="MojaAplikacja.MainPage"
             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>
  1. DataTemplate x:DataType="models:Ksiazka" Mówi kompilatorowi, jakiego typu jest element, więc literówki w {Binding} zostaną wykryte. Bez tego wiązania też działają, ale przez refleksję i wolniej.
  2. Grid ColumnDefinitions="*,Auto" Tekst po lewej, przycisk po prawej.
  3. {Binding StatusOpis}, {Binding AkcjaTekst} Etykieta i napis przycisku pochodzą z modelu.

Budowa krok po kroku

MainPage.xaml.cs

_oryginalnaLista przechowuje wszystkie obiekty. Filtr tworzy widok wyników; nie niszczy źródła. BindingContext klikniętego przycisku wskazuje książkę z tego wiersza.

Przycisk w wierszu zmienia status książki z tego wiersza, a wyszukiwarka filtruje oryginalną listę.

MainPage.xaml.cscsharp
using System.Collections.ObjectModel;
using MojaAplikacja.Logika;
using MojaAplikacja.Modele;

namespace MojaAplikacja;

public partial class MainPage : ContentPage
{
    private readonly BibliotekaViewModel _vm;
    private readonly ObservableCollection<Ksiazka> _oryginalnaLista;

    public MainPage()
    {
        InitializeComponent();
        _vm = new BibliotekaViewModel();
        _oryginalnaLista = _vm.Ksiazki;
        cvKsiazki.ItemsSource = _vm.Ksiazki;
    }

    private void ZmienStatus_Click(object sender, EventArgs e)
    {
        // BindingContext przycisku to książka z tego wiersza listy
        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;
        }

        // Zawsze filtrujemy ZAPAMIĘTANĄ listę oryginalną, a nie już zawężone wyniki
        var wyniki = new ObservableCollection<Ksiazka>(
            _oryginalnaLista.Where(k =>
                k.Tytul.ToLower().Contains(filtr) || k.Autor.ToLower().Contains(filtr)));
        cvKsiazki.ItemsSource = wyniki;
    }
}
  1. (sender as BindableObject)?.BindingContext as Ksiazka Przycisk w szablonie ma jako BindingContext książkę z danego wiersza.
  2. ks.Wypozyczona = !ks.Wypozyczona Zmiana jednej właściwości. Dzięki INotifyPropertyChanged widok sam się odświeży.
  3. e.NewTextValue?.ToLower() ?? "" Bezpieczny odczyt wpisanej frazy małymi literami.
  4. _oryginalnaLista.Where(...) LINQ filtruje zapamiętaną listę, a nie wyniki poprzedniego filtrowania.
  5. new ObservableCollection<Ksiazka>(...) Wynik filtrowania jako nowa kolekcja przypisana do ItemsSource.

Zatrzymaj się przed kolejnym wariantem

Nie zastępuj jeszcze działającej wersji. Wykonaj tę próbę i dopiero potem przejdź do dodatku.

Dopiero teraz: drugi wariant lub rozszerzenie

To następny etap tej lekcji, nie warunek pierwszego uruchomienia. Porównaj jego rolę z działającą bazą. Jeśli brakuje czasu, wróć do niego po sprawdzeniu podstawowej wersji.

Dodatek do działającej wersji

Dodatek po wiązaniu danych: WierszUstawienia: własny kontener

Dodatek: alternatywny ContentView po opanowaniu listy. Nie kopiuj jednocześnie prostej i wiązalnej wersji WierszUstawienia do tego samego projektu.

ContentView to wydzielony fragment interfejsu, który możesz użyć w wielu miejscach, jak osobny komponent. Tutaj wiersz ustawienia: tytuł i opis po lewej, przełącznik po prawej.

WierszUstawienia.xamlxaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MojaAplikacja.WierszUstawienia"
             x:Name="Wiersz">
    <Grid ColumnDefinitions="*,Auto" Padding="12">
        <VerticalStackLayout Grid.Column="0">
            <Label Text="{Binding Tytul, Source={x:Reference Wiersz}}" FontAttributes="Bold" />
            <Label Text="{Binding Opis, Source={x:Reference Wiersz}}" TextColor="Gray" FontSize="12" />
        </VerticalStackLayout>
        <Switch Grid.Column="1" VerticalOptions="Center" />
    </Grid>
</ContentView>
  1. <ContentView ... x:Name="Wiersz"> Nazwa pozwala odwołać się do własności tego kontenera w wiązaniach.
  2. Grid ColumnDefinitions="*,Auto" Skrócony zapis kolumn: lewa elastyczna, prawa dopasowana do przełącznika.
  3. {Binding Tytul, Source={x:Reference Wiersz}} Wiąże etykietę z własnością Tytul tego kontenera.

Dodatek do działającej wersji

Dodatek po wiązaniu danych: WierszUstawienia.xaml.cs

BindableProperty udostępnia właściwość systemowi wiązań MAUI. Publiczna właściwość C# przekazuje GetValue/SetValue do tego samego klucza.

Własności Tytul i Opis są BindableProperty, więc strona, która używa kontenera, może je ustawiać atrybutami XAML i wiązać.

WierszUstawienia.xaml.cscsharp
namespace MojaAplikacja;

public partial class WierszUstawienia : ContentView
{
    // Własności, które ustawia strona używająca tego kontenera
    public static readonly BindableProperty TytulProperty =
        BindableProperty.Create(nameof(Tytul), typeof(string), typeof(WierszUstawienia), "");

    public static readonly BindableProperty OpisProperty =
        BindableProperty.Create(nameof(Opis), typeof(string), typeof(WierszUstawienia), "");

    public string Tytul
    {
        get => (string)GetValue(TytulProperty);
        set => SetValue(TytulProperty, value);
    }

    public string Opis
    {
        get => (string)GetValue(OpisProperty);
        set => SetValue(OpisProperty, value);
    }

    public WierszUstawienia()
    {
        InitializeComponent();
    }
}
  1. BindableProperty.Create(...) Rejestruje własność w systemie wiązania MAUI.
  2. GetValue / SetValue Odczyt i zapis wartości własności.

Dodatek do działającej wersji

Dodatek po wiązaniu danych: UstawieniaPage.xaml

UstawieniaPage używa nowego kontenera. To dodatkowy ekran ćwiczeniowy; nie zastępuje listy książek.

Ten sam kontener użyty cztery razy z różnymi danymi, wewnątrz ScrollView. Każda zmiana wyglądu wiersza dotyczy wszystkich czterech naraz.

UstawieniaPage.xamlxaml
<?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"
             xmlns:local="clr-namespace:MojaAplikacja"
             x:Class="MojaAplikacja.UstawieniaPage">
    <ScrollView>
        <VerticalStackLayout>
            <local:WierszUstawienia Tytul="Powiadomienia" Opis="Pokazuj powiadomienia na ekranie blokady" />
            <local:WierszUstawienia Tytul="Tryb ciemny" Opis="Ciemny motyw aplikacji" />
            <local:WierszUstawienia Tytul="Lokalizacja" Opis="Zezwól na dostęp do położenia" />
            <local:WierszUstawienia Tytul="Synchronizacja" Opis="Wysyłaj dane w tle" />
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>
  1. xmlns:local="clr-namespace:MojaAplikacja" Przestrzeń nazw, dzięki której XAML zna Twój kontener.
  2. <local:WierszUstawienia Tytul="…" /> Użycie własnego kontenera jak zwykłej kontrolki.

Plik .xaml.cs też musi istnieć

Szablon strony lub ContentView generuje poniższe klasy. Nie dodawaj ich drugi raz, jeśli Visual Studio już je utworzyło. W każdej parze musi pozostać InitializeComponent().

UstawieniaPage — minimalny kod za widokiem

UstawieniaPage.xaml.cscsharp
namespace MojaAplikacja;

public partial class UstawieniaPage : ContentPage
{
    public UstawieniaPage() => InitializeComponent();
}
  1. partial Druga część klasy powstaje z XAML. Nie twórz ręcznie pól kontrolek ani drugiej metody InitializeComponent.

5. Odbiór: czy naprawdę działa?

Wykonaj próby w swojej aplikacji, nie tylko w modelu na stronie. Zaznaczaj dopiero po sprawdzeniu wyniku. Funkcje urządzenia wymagają testu na właściwej platformie.

7 · Czego unikać

Cztery błędy z listami

Model bez `INotifyPropertyChanged`
Tak nie
public bool Wypozyczona { get; set; }

Zmiana statusu nie odświeża widoku, lista wygląda na „zamrożoną”.

Tak
set { _wypozyczona = value; OnPropertyChanged(); }

Wiązania odświeżają się tylko po powiadomieniu.

Brak powiadomienia o właściwości wyliczanej
Tak nie
set { _wypozyczona = value; OnPropertyChanged(); }   // bez StatusOpis

Etykieta „Dostępna / Wypożyczona” nie zmienia się, choć dane się zmieniły.

Tak
OnPropertyChanged(nameof(StatusOpis));

Każda właściwość, od której zależy widok, musi być zgłoszona.

Filtrowanie już zawężonego `ItemsSource`
Tak nie
var wyniki = cvKsiazki.ItemsSource.Cast<Ksiazka>().Where(...);

Po skasowaniu znaku wyniki nie wracają, bo każde filtrowanie zawęża poprzednie.

Tak
var wyniki = _oryginalnaLista.Where(...);

Zawsze filtruj zapamiętaną listę oryginalną.

Brak `x:DataType` w szablonie
Tak nie
<DataTemplate>
    <Label Text="{Binding Tytl}" />

Literówka w nazwie właściwości nie zostanie wykryta w czasie kompilacji.

Tak
<DataTemplate x:DataType="models:Ksiazka">

Skompilowane wiązania wychwytują błędy wcześniej i działają szybciej.

Ćwiczenie z pomocą · samodzielna praca

6. Mała zmiana, którą zrobisz samodzielnie

Dodaj czwartą książkę i sprawdź wyszukiwanie po autorze.

Oczekiwany rezultat
Dodaj czwartą książkę i sprawdź wyszukiwanie po autorze.
Dotychczasowe działanie ma pozostać poprawne.
Potrzebujesz pomocy? Rozwiązuj krok po kroku.
Krok 1 — pokaż podpowiedź

Dopisz Ksiazka do kolekcji tworzonej w BibliotekaViewModel.

Krok 2 — pokaż podpowiedź

Nie wpisuj dodatkowego wiersza na sztywno w XAML — wiersze tworzy DataTemplate.

Krok 3 — pokaż podpowiedź

Sprawdź autora z różną wielkością liter oraz powrót po usunięciu filtra.

Otwórz tylko tyle podpowiedzi, ile potrzebujesz. Po każdym kroku wróć do zadania i sprawdź wynik.

8 · Sprawdź się

Co odświeży listę?

Cztery sytuacje.

Ta interakcja wymaga włączonego JavaScriptu.

Podsumowanie

Co warto zapamiętać

  1. CollectionView pokazuje kolekcję przez ItemsSource i wiersz opisany w ItemTemplate.
  2. ObservableCollection<T> zgłasza dodanie i usunięcie elementów, a INotifyPropertyChanged zmianę właściwości obiektu.
  3. Zgłaszaj także właściwości wyliczane (OnPropertyChanged(nameof(...))).
  4. Filtruj zawsze oryginalną listę, a wynik przypisuj do ItemsSource.
  5. x:DataType w DataTemplate daje sprawdzanie wiązań przy kompilacji.

Dalej na swoim koncie

Rozgrzewki, quiz i zadania są w biurku ucznia.

Teoria jest otwarta dla wszystkich. Żeby rozwiązywać zadania, zapisywać szkice i widzieć swój postęp, zaloguj się.

Zaloguj się →
Źródła i dalsza nauka