Aplikacje mobilne · Kontrolki

Kontrolki III: ProgressBar, ActivityIndicator, CollectionView i ListView

Dwie kontrolki informują, że coś trwa (ProgressBar, ActivityIndicator), a dwie wyświetlają listy (CollectionView, starsza ListView). To dopełnia pełny zestaw kontrolek UI i przygotowuje do lekcji o sieci, GPS i listach danych.

  • około 50 minut
  • .NET MAUI · ProgressBar · ActivityIndicator · CollectionView
  • INF.04.6.2

Po tej lekcji: pokazać postęp operacji (ProgressTo), użyć spinnera przy operacjach o nieznanym czasie, nie blokować interfejsu (Task.Delay) i wyświetlić listę w CollectionView z EmptyView.

Zobacz, zanim zaczniesz

Jak aplikacja mówi: pracuję

Użytkownik musi widzieć, że aplikacja żyje. Poniżej trzy sposoby, każdy z kodem XAML.

Ta interakcja wymaga włączonego JavaScriptu.

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

Zacznij od zabawy

Postęp, spinner i lista

Uruchom ProgressTo z różnym czasem. Potem „pobieranie” z Task.Delay i z Thread.Sleep, klikając w tym czasie licznik obok przycisku. Na końcu zmień układ listy i opróżnij ją, aby zobaczyć EmptyView.

Najpierw przewidź, potem sprawdź: Uruchom symulację postępu i porównaj 0.5 w programie z 50% dla człowieka. Spinner oznacza pracę, ale nie podaje procentu wykonania.

Ta interakcja wymaga włączonego JavaScriptu.

await Task.Delay oddaje sterowanie interfejsowi, a Thread.Sleep blokuje wątek UI, więc aplikacja „zamarza”. Model pomaga zobaczyć mechanizm; właściwą aplikację uruchomisz z plików poniżej.

Etap 1 z 4

ProgressBar: kiedy znasz postęp

ProgressBar pokazuje, ile już zrobiono. Właściwość Progress przyjmuje liczbę od 0.0 do 1.0, a nie od 0 do 100. Wartość 0.75 to 75%.

2 · Informowanie o operacji

ProgressBar i ActivityIndicator

KontrolkaWłaściwośćOpis
ProgressBarProgressWartość od 0.0 do 1.0: aktualny postęp.
ProgressBarProgressColorKolor paska, może nieść znaczenie (zielony: OK, czerwony: błąd).
ProgressBarProgressTo(wartość, czas, Easing)Metoda animująca płynną zmianę, zamiast skoku.
ActivityIndicatorIsRunningWłącza animację „kręcącego się kółka”.
ActivityIndicatorIsVisible, ColorPokazuje i ukrywa spinner, ustawia kolor.
`ProgressTo` zwraca `Task`, więc wywołujesz je z `await`.

Wyzwanie 1 · czytanie kodu

Ile pokaże pasek?

Ta interakcja wymaga włączonego JavaScriptu.

Wyzwanie 2 · pasek

Pasek postępu pobierania

Ta interakcja wymaga włączonego JavaScriptu.

Sprawdź, czy zapamiętałeś

Jedno szybkie pytanie

Ta interakcja wymaga włączonego JavaScriptu.

Etap 2 z 4

ActivityIndicator: gdy nie wiesz, ile to potrwa

Spinner pokazuje, że aplikacja pracuje, ale nie mówi, ile jeszcze. Włączasz go przez IsRunning="True", a ukrywasz przez IsVisible="False". Gdy postęp da się policzyć, lepszy jest ProgressBar.

Wyzwanie 3 · spinner

Spinner na czas łączenia

Ta interakcja wymaga włączonego JavaScriptu.

Sprawdź, czy zapamiętałeś

Jedno szybkie pytanie

Ta interakcja wymaga włączonego JavaScriptu.

Etap 3 z 4

CollectionView: lista z szablonem

CollectionView pokazuje kolekcję danych. Dane podajesz przez ItemsSource, a wygląd jednego elementu opisujesz w ItemTemplate. Gdy lista jest pusta, możesz pokazać EmptyView. W nowych projektach używasz CollectionView, a nie starszego ListView.

5 · Listy

CollectionView i ListView

WłaściwośćOpis
ItemsSourceŹródło danych, zwykle kolekcja obiektów podpięta z kodu.
ItemTemplateWygląd pojedynczego elementu (DataTemplate).
ItemsLayoutVerticalList, HorizontalList albo siatka (GridItemsLayout).
SelectionModeNone, Single lub Multiple.
EmptyViewCo pokazać, gdy lista jest pusta.
ListViewStarsza kontrolka: SelectedItem, SeparatorVisibility, HasUnevenRows. W nowych projektach wybieraj CollectionView.
`CollectionView` jest wydajniejszy i elastyczniejszy niż `ListView`. Tę drugą rozpoznawaj w starszych materiałach.

Wyzwanie 4 · lista

Ułóż XAML listy

Ta interakcja wymaga włączonego JavaScriptu.

Wyzwanie 5 · cudzy kod

Lista, która nic nie pokazuje

Ta interakcja wymaga włączonego JavaScriptu.

Sprawdź, czy zapamiętałeś

Dwa szybkie pytania

Ta interakcja wymaga włączonego JavaScriptu.

Etap 4 z 4

Spinner w metodzie: nigdy nie blokuj ekranu

Metoda przycisku wykonuje się w wątku interfejsu. Gdy użyjesz Thread.Sleep, ekran zamarznie i spinner się nie poruszy. Dlatego czekasz przez await Task.Delay(...), a metodę oznaczasz async.

Wyzwanie 6 · logika

Ułóż metodę pobierania

Ta interakcja wymaga włączonego JavaScriptu.

Wyzwanie końcowe

Ekran pobierania

Ta interakcja wymaga włączonego JavaScriptu.

Sprawdź, czy zapamiętałeś

Cztery pytania z całej lekcji

Ta interakcja wymaga włączonego JavaScriptu.

9 · Sprawdź się

Pasek, spinner czy lista?

Cztery sytuacje.

Ta interakcja wymaga włączonego JavaScriptu.

Dla chętnych · po pierwszej stronie

Pracownia w Visual Studio: to samo w prawdziwym projekcie

Rozwiń dodatkowe warsztaty ↓

Wyzwania powyżej uczą w przeglądarce. Tu jest pełna instrukcja krok po kroku, jak zrobić to w Visual Studio i uruchomić na emulatorze, tak jak na egzaminie praktycznym. Otwórz, gdy będziesz gotowy do pracy z projektem.

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
PostepPage.xamlWygląd strony lub kontrolki
PostepPage.xaml.csLogika i obsługa danych
ListaPlikowPage.xamlWygląd strony lub kontrolki
ListaPlikowPage.xaml.csLogika i obsługa danych

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="Postęp: połowa" FontSize="24" />
        <ProgressBar Progress="0.5" />
        <ActivityIndicator IsRunning="True" />
    </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.

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 PostepPage, 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 PostepPage()));
}
  1. CreateWindow Tworzy okno aplikacji. NavigationPage jest kontenerem zwykłych stron i daje przycisk powrotu; nie łączymy go z Shell.
  2. new PostepPage() 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

PostepPage.xaml

Dodaj PostepPage. Progress to liczba0–1, nie0–100. Na tym ekranie oddzielnie testujemy znany postęp i oczekiwanie bez procentu.

Dwie sekcje: pasek postępu i spinner, każda z własnym przyciskiem.

PostepPage.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"
             x:Class="MojaAplikacja.PostepPage">
    <VerticalStackLayout Padding="20" Spacing="16">

        <Label Text="Wysyłanie pliku:" FontAttributes="Bold" />
        <ProgressBar x:Name="PasekPostepu" Progress="0" />
        <Button Text="Rozpocznij wysyłanie" Clicked="RozpocznijWysylanie_Clicked" />

        <BoxView HeightRequest="1" Color="LightGray" />

        <Label Text="Ładowanie danych z serwera:" FontAttributes="Bold" />
        <ActivityIndicator x:Name="Spinner" IsRunning="False" Color="RoyalBlue" />
        <Button Text="Symuluj pobieranie" Clicked="SymulujPobieranie_Clicked" />

    </VerticalStackLayout>
</ContentPage>
  1. ProgressBar Progress="0" Pasek startuje pusty.
  2. ActivityIndicator IsRunning="False" Spinner jest wyłączony, dopóki kod go nie uruchomi.
  3. BoxView HeightRequest="1" Cienka linia oddzielająca sekcje.

Budowa krok po kroku

PostepPage.xaml.cs

ProgressTo płynnie zmienia pasek, a Task.Delay tylko symuluje czas. Żaden z tych przycisków nie wysyła danych przez sieć. await pozwala wrócić do obsługi interfejsu.

Obie metody są async, bo korzystają z await.

PostepPage.xaml.cscsharp
namespace MojaAplikacja;

public partial class PostepPage : ContentPage
{
    public PostepPage()
    {
        InitializeComponent();
    }

    private async void RozpocznijWysylanie_Clicked(object sender, EventArgs e)
    {
        // ProgressTo animuje płynne przejście od aktualnej wartości do 1.0
        // w ciągu 2000 milisekund (2 sekundy)
        await PasekPostepu.ProgressTo(1.0, 2000, Easing.Linear);
    }

    private async void SymulujPobieranie_Clicked(object sender, EventArgs e)
    {
        Spinner.IsRunning = true;

        // Symulacja oczekiwania na odpowiedź serwera (bez blokowania UI)
        await Task.Delay(2000);

        Spinner.IsRunning = false;
        await DisplayAlertAsync("Gotowe", "Dane zostały pobrane.", "OK");
    }
}
  1. await PasekPostepu.ProgressTo(1.0, 2000, Easing.Linear) Płynnie zwiększa wartość przez 2 sekundy. Bez animacji pasek skoczyłby od razu do końca.
  2. Spinner.IsRunning = true Włącza spinner na czas operacji.
  3. await Task.Delay(2000) Sztuczne czekanie bez blokowania interfejsu. W prawdziwej aplikacji będzie tu await httpClient.GetAsync(...).
  4. Spinner.IsRunning = false Zawsze wyłącz spinner po zakończeniu. Wieczny spinner sugeruje zawieszenie aplikacji.

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.

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.

8 · Czego unikać

Cztery błędy z postępem i listami

`Thread.Sleep` zamiast `Task.Delay`
Tak nie
Thread.Sleep(2000);

Interfejs zamarza, a spinner się nie kręci.

Tak
await Task.Delay(2000);

await oddaje sterowanie wątkowi interfejsu na czas oczekiwania.

Spinner bez wyłączenia
Tak nie
Spinner.IsRunning = true;
await Task.Delay(2000);
// brak Spinner.IsRunning = false

Spinner kręci się w nieskończoność, jakby aplikacja się zawiesiła.

Tak
Spinner.IsRunning = false;

Każda operacja, która włącza spinner, musi go też wyłączyć (także przy błędzie).

`ListView` w nowym projekcie
Tak nie
<ListView ItemsSource="{Binding Pozycje}" />

Działa, ale to starsza kontrolka, mniej wydajna i elastyczna.

Tak
<CollectionView ItemsSource="{Binding Pozycje}" />

CollectionView jest zalecany w nowych projektach.

Brak `EmptyView`
Tak nie
<CollectionView ItemsSource="{Binding Wyniki}" />

Przy braku wyników widać pusty ekran, który wygląda jak błąd.

Tak
<CollectionView.EmptyView>
    <Label Text="Brak wyników" />
</CollectionView.EmptyView>

Użytkownik powinien wiedzieć, że lista jest pusta, a nie zepsuta.

Ćwiczenie z pomocą · samodzielna praca

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

Dodaj licznik wyświetlonych nazw plików pod listą.

Oczekiwany rezultat
Dodaj licznik wyświetlonych nazw plików pod listą.
Dotychczasowe działanie ma pozostać poprawne.
Potrzebujesz pomocy? Rozwiązuj krok po kroku.
Krok 1 — pokaż podpowiedź

Dodaj Label z nazwą LicznikLabel.

Krok 2 — pokaż podpowiedź

Po ustawieniu ItemsSource ustal liczbę elementów pobranej kolekcji.

Krok 3 — pokaż podpowiedź

Nie wpisuj na stałe liczby, jeśli w następnym kroku zmienisz dane.

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

Podsumowanie

Co warto zapamiętać

  1. ProgressBar dla znanego postępu (0.0 do 1.0), ProgressTo animuje zmianę. ActivityIndicator dla nieznanego czasu.
  2. Spinner zawsze wyłączaj po zakończeniu (IsRunning = false).
  3. await Task.Delay zamiast Thread.Sleep, bo nie blokuje wątku interfejsu.
  4. CollectionView: ItemsSource, ItemTemplate, ItemsLayout, SelectionMode, EmptyView.
  5. ListView rozpoznawaj w starszym kodzie, ale w nowych projektach wybieraj CollectionView.

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