ProgressBar — pasek postępu
Pasek postępu pokazuje „ile już zrobiono”: wypełnienie formularza, zebraną kwotę, punkty życia w grze, zajęte miejsce na dysku. Działa jak suwak, którego użytkownik nie może przesunąć — wartość ustawia tylko program. W tej lekcji nauczysz się przeliczać dane na pasek, pokazywać procent na pasku i zmieniać jego kolor zależnie od stanu.
Czego się dziś nauczysz
- Dodasz pasek postępu z zakresem dopasowanym do danych (np. 0–5000 zł zamiast 0–100).
- Policzysz procent postępu i pokażesz go napisem na pasku.
- Zmienisz kolor paska zależnie od stanu.
- Włączysz pasek „nieokreślony” dla operacji o nieznanym czasie trwania.
- Wyjaśnisz, dlaczego pasek nie odświeża się w trakcie długiej pętli.
Pasek w oknie
<StackPanel Margin="20">
<TextBlock Text="Wypełnienie ankiety"/>
<ProgressBar x:Name="Postep" Minimum="0" Maximum="10" Value="3"
Height="20" Margin="0,4,0,0"/>
</StackPanel>
3 z 10 pytań — pasek wypełniony w 30%
Pasek ma tę samą „rodzinę” właściwości co suwak: Minimum, Maximum, Value, wszystkie typu double. Domyślny zakres to 0–100. W pionowym StackPanel pasek ma bardzo małą wysokość, dlatego zwykle dostaje Height. Orientation="Vertical" stawia go pionowo, jak termometr.
Wartość, zakres, procent
Nie trzeba przeliczać danych na procenty — ustaw Maximum na wartość docelową, a Value na bieżącą. Pasek sam wylicza, jaką część wypełnić. Procent do napisu liczysz osobno:
// 14 z 20 zadań zrobionych
Postep.Maximum = 20;
Postep.Value = 14;
double procent = Postep.Value / Postep.Maximum * 100; // 70
Opis.Text = $"{Postep.Value} z {Postep.Maximum} ({procent:F0}%)";
Jak w suwaku, wartość spoza zakresu jest przycinana: Value = 25 przy Maximum = 20 daje 20. Jeśli liczba może przekroczyć cel (np. zbiórka zebrała więcej), trzymaj ją we własnym polu klasy, a pasek traktuj tylko jako widok.
Kolor i napis na pasku
Kolor wypełnienia to Foreground, kolor tła — Background. Napis z procentem kładziesz na pasek tak, jak w lekcji 09 nakładały się kontrolki: oba elementy w jednej komórce Grid, napis niżej w pliku, więc na wierzchu:
<Grid Height="26">
<ProgressBar x:Name="Zycie" Maximum="100" Value="35" Foreground="IndianRed"/>
<TextBlock x:Name="ZycieOpis" Text="35 / 100 HP"
HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="Bold"/>
</Grid>
napis leży na pasku, bo stoi w tej samej komórce i jest później w pliku
Postęp nieokreślony
Gdy program nie wie, ile potrwa operacja (łączenie z serwerem, wyszukiwanie), pokazuje pasek z IsIndeterminate="True": zamiast wypełnienia przesuwa się po nim w kółko pasmo — „coś się dzieje, czekaj”. Value jest wtedy pomijana.
Pasek w długiej pętli się nie odświeża
Okno odświeża się dopiero wtedy, gdy metoda obsługi zdarzenia się zakończy. Jeśli w Click wykonasz pętlę liczącą 10 sekund i w środku ustawiasz Postep.Value, użytkownik zobaczy zamrożone okno, a na końcu od razu pełny pasek. Pasek, który rośnie w trakcie pracy, zrobisz z czasomierzem w lekcji 27 i pracą w tle w lekcji 51. Na razie pasek zmienia się po każdej akcji użytkownika.
Zbiórka na schronisko
Klasa zbiera 5000 zł na karmę dla schroniska. Wpłaty dodaje się przyciskami szybkich kwot albo wpisując własną. Pasek pokazuje postęp z procentem na środku, zmienia kolor z czerwonego przez pomarańczowy na zielony, a lista pod spodem zbiera historię wpłat — najnowsza na górze. Po osiągnięciu celu przyciski wpłat się wyłączają.
65% celu — pasek pomarańczowy
<Window x:Class="Lekcja20.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Zbiórka na schronisko" Height="460" Width="460"
MinWidth="400" MinHeight="400" WindowStartupLocation="CenterScreen">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Karma dla schroniska „Azorek”" FontSize="18" FontWeight="Bold"/>
<TextBlock x:Name="Stan" Grid.Row="1" Margin="0,4,0,8"/>
<!-- pasek i napis z procentem w jednej komórce: napis leży na pasku -->
<Grid Grid.Row="2" Height="28">
<ProgressBar x:Name="Pasek"/>
<TextBlock x:Name="Procent" HorizontalAlignment="Center" VerticalAlignment="Center"
FontWeight="Bold"/>
</Grid>
<!-- wpłaty: szybkie kwoty albo własna -->
<StackPanel Grid.Row="3" Margin="0,12,0,8">
<WrapPanel x:Name="Szybkie">
<Button Content="10 zł" Tag="10" Width="60" Margin="0,0,6,6" Click="Szybka_Click"/>
<Button Content="20 zł" Tag="20" Width="60" Margin="0,0,6,6" Click="Szybka_Click"/>
<Button Content="50 zł" Tag="50" Width="60" Margin="0,0,6,6" Click="Szybka_Click"/>
<Button Content="100 zł" Tag="100" Width="60" Margin="0,0,6,6" Click="Szybka_Click"/>
</WrapPanel>
<DockPanel>
<Button x:Name="Wplac" DockPanel.Dock="Right" Content="Wpłać" Width="80"
Margin="6,0,0,0" IsDefault="True" Click="Wplac_Click"/>
<TextBox x:Name="Kwota" VerticalContentAlignment="Center"/>
</DockPanel>
</StackPanel>
<ListBox x:Name="Historia" Grid.Row="4"/>
</Grid>
</Window>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Lekcja20;
public partial class MainWindow : Window
{
private const double Cel = 5000;
private double zebrano = 0;
public MainWindow()
{
InitializeComponent();
Pasek.Maximum = Cel; // cel zapisany w jednym miejscu — w stałej
Odswiez();
}
private void Szybka_Click(object sender, RoutedEventArgs e)
{
Button przycisk = (Button)sender;
Dodaj(double.Parse($"{przycisk.Tag}")); // kwoty wpisaliśmy sami w XAML
}
private void Wplac_Click(object sender, RoutedEventArgs e)
{
if (!double.TryParse(Kwota.Text, out double kwota) || kwota <= 0 || kwota > 1000)
{
MessageBox.Show("Wpisz kwotę od 0,01 do 1000 zł.", "Zbiórka",
MessageBoxButton.OK, MessageBoxImage.Warning);
Kwota.SelectAll();
Kwota.Focus();
return;
}
Dodaj(Math.Round(kwota, 2));
Kwota.Clear();
}
private void Dodaj(double kwota)
{
zebrano += kwota;
Pasek.Value = zebrano; // wartość powyżej Maximum pasek sam przytnie do 100%
Historia.Items.Insert(0, $"{DateTime.Now:HH:mm} +{kwota:F2} zł");
Odswiez();
if (zebrano >= Cel)
{
MessageBox.Show("Cel osiągnięty! Dziękujemy wszystkim darczyńcom.", "Zbiórka",
MessageBoxButton.OK, MessageBoxImage.Information);
}
}
private void Odswiez()
{
double procent = zebrano / Cel * 100;
double brakuje = Cel - zebrano;
Procent.Text = $"{procent:F0}%";
Stan.Text = brakuje > 0
? $"Zebrano {zebrano:F2} zł z {Cel:F0} zł — brakuje {brakuje:F2} zł"
: $"Zebrano {zebrano:F2} zł — cel osiągnięty!";
// kolor paska zależy od postępu
if (procent < 30)
{
Pasek.Foreground = Brushes.IndianRed;
}
else if (procent < 100)
{
Pasek.Foreground = Brushes.Orange;
}
else
{
Pasek.Foreground = Brushes.SeaGreen;
}
// po osiągnięciu celu kolejne wpłaty są wyłączone
bool otwarta = brakuje > 0;
Szybkie.IsEnabled = otwarta;
Wplac.IsEnabled = otwarta;
Kwota.IsEnabled = otwarta;
}
}
Co dzieje się po kolei
- Cel zbiórki jest w stałej
Cel; konstruktor ustawia z niejMaximumpaska. Zebraną kwotę pamięta polezebrano— pasek tylko ją pokazuje i nie zgubi nadwyżki powyżej celu. - Pasek i napis z procentem stoją w jednym
Grido wysokości 28 — napis, zapisany później, leży na pasku. - Szybkie kwoty mają wartość w
Tagi jedną metodęSzybka_Click. Własna kwota przechodzi przezTryParsei sprawdzenie zakresu. - Obie drogi kończą się w
Dodaj: dopisanie kwoty, przesunięcie paska, wpis na górę historii (Items.Insert(0, …)) i odświeżenie napisów. Odswiezliczy procent i brakującą kwotę z polazebrano, ustawia kolor paska (Foreground) i wyłącza wpłaty po osiągnięciu celu — cały panel szybkich kwot jednymIsEnabled.- Operator
? :wybiera jeden z dwóch napisów stanu.
Sprawdź trzy rzeczy
- Zmień w kodzie
Pasek.Value = zebrano;naPasek.Value += kwota;i licz procent zPasek.Value. Wpłać kwotę ponad cel — nadwyżka zniknie, bo pasek przyciął wartość. - Dodaj do paska
IsIndeterminate="True". Pasek zacznie przesuwać pasmo w kółko i przestanie pokazywać zebraną kwotę. - Zmień stałą
Celna 1000. Pasek, procent i napisy dopasują się same.
Zestawienie elementów
| Element | Typ | Co robi |
|---|---|---|
Minimum, Maximum | double | Zakres (domyślnie 0–100). |
Value | double | Bieżący postęp; spoza zakresu jest przycinany. |
Foreground | pędzel | Kolor wypełnienia. |
Background | pędzel | Kolor niewypełnionej części. |
IsIndeterminate | bool | Pasek nieokreślony — przesuwające się pasmo. |
Orientation | Horizontal, Vertical | Pasek poziomy / pionowy (wypełnia się od dołu). |
ValueChanged | zdarzenie | Jak w suwaku — rzadko potrzebne, bo wartość zmienia tylko kod. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
pasek w StackPanel bez Height | Cienka kreska — trudno ją zauważyć. |
Postep.Value = 14; przy domyślnym Maximum 100, gdy zadań jest 20 | Pasek pokazuje 14% zamiast 70%. Ustaw Maximum = 20. |
procent = zrobione / wszystkie * 100 na int | Dzielenie całkowite: 14 / 20 = 0, więc zawsze 0%. Licz na double albo mnóż przez 100 najpierw. |
suma przechowywana tylko w Value | Wartość powyżej Maximum ginie — przycięcie do maksimum. |
pętla z Value++ w jednym Click | Okno nie odświeży się w trakcie — zobaczysz od razu pełny pasek. |
| napis zapisany w XAML przed paskiem w tej samej komórce | Pasek zakrywa napis. |
Zadania
Każde zadanie wykonaj w nowym projekcie Aplikacja WPF. Paski zmieniaj po akcjach użytkownika — czasomierze poznasz w lekcji 27. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.
Formularz zgłoszenia na zawody: imię, nazwisko, klasa (lista rozwijana), dyscyplina (przyciski opcji, bez wyboru domyślnego), zgoda rodzica (pole wyboru). Nad formularzem pasek „Wypełniono 3 z 5” zmieniający się na żywo. Przy komplecie pasek jest zielony, a przycisk Wyślij się włącza.
pokaż wskazówkę
- Ustaw
Maximum="5"i licz wypełnione pola w jednej metodzie wywoływanej ze wszystkich zdarzeń zmian.
Panel postaci w grze: pasek HP 0–100 z napisem „65 / 100 HP”, przyciski Otrzymaj cios (−15), Mikstura (+25), Odpoczynek (+5). HP nie spada poniżej 0 i nie rośnie ponad 100. Kolor: zielony powyżej 60, żółty 30–60, czerwony poniżej 30. Przy 0 HP napis „Koniec gry”, przyciski są wyłączone, a Od nowa przywraca 100 HP. Licznik mikstur: na start 3, przy 0 przycisk mikstury jest nieaktywny.
pokaż wskazówkę
- Granice załatwi
Math.MiniMath.Max— albo sam pasek, jeśli HP trzymasz tylko wValue.
Kreator zakładania konta w czterech krokach: dane osobowe, adres, hasło, podsumowanie. Na górze pasek „Krok 2 z 4” i nazwy kroków, z których bieżąca jest pogrubiona. Na dole przyciski ◀ Wstecz (nieaktywny w kroku 1) i Dalej ▶ (w kroku 4 zmienia się na Zakończ). Przejście dalej jest możliwe tylko przy wypełnionych polach bieżącego kroku. Podsumowanie wyświetla wszystkie dane (hasło jako gwiazdki).
pokaż wskazówkę
- Cztery panele kroków w jednej komórce
Grid; widoczny ten, którego numer zgadza się z polemkrok. - Panele możesz zebrać w tablicę
StackPanel[]w konstruktorze — wtedy widoczność ustawisz w pętli.
Lista 10 plików z rozmiarami (np. „film.mp4 — 4,2 GB”) z wyborem wielu pozycji. Pasek pokazuje zajęte miejsce na pendrivie 16 GB: już zajęte 3,5 GB plus zaznaczone pliki, z napisem „12,8 / 16 GB”. Powyżej 90% pasek jest pomarańczowy, a gdy pliki się nie mieszczą — czerwony, napis mówi „Brakuje 1,3 GB”, a przycisk Kopiuj jest nieaktywny. Skopiowane pliki znikają z listy i zwiększają zajęte miejsce.
pokaż wskazówkę
- Nazwy i rozmiary w dwóch tablicach. Na liście trzymaj samą nazwę, rozmiar znajdziesz przez
Array.IndexOf. Maximumustaw na 16; wartość większą od pojemności i tak licz w zmiennej — pasek ją przytnie.
Siedem pionowych pasków (pon.–niedz.) obok siebie, każdy 0–60 minut, z podpisem dnia pod spodem i liczbą minut nad paskiem. Lista rozwijana dnia, pole minut i przycisk Dodaj dopisują czytanie do wybranego dnia. Pod wykresem poziomy pasek celu tygodniowego 300 minut: „Tydzień: 185 / 300 min”. Dzień z co najmniej 30 minutami ma zielony pasek. Po starcie wybrany jest dzisiejszy dzień.
pokaż wskazówkę
- Pionowe paski w
UniformGridz 7 kolumnami; minuty w tablicyint[7], a pasek dniaitoChildren[i]. - Paski pionowe potrzebują wysokości — np.
Height="150".
Lista tematów do powtórki z planowaną liczbą godzin (np. „XAML i układy — 6 h”, „Zdarzenia — 4 h”, „Formularze — 8 h”…, razem 40 h). Zaznaczenie tematu pokazuje jego pasek: „2,5 / 6 h”. Pole i przycisk Dodaj godziny zapisują naukę do zaznaczonego tematu (krok 0,5 h). Ukończony temat dostaje na liście znak „✓ ” przed nazwą. Na dole pasek ogólny „Razem: 17,5 / 40 h (44%)” oraz napis, ile godzin dziennie trzeba się uczyć, żeby zdążyć przed datą egzaminu zapisaną w stałej.
pokaż wskazówkę
- Nazwy, plan i godziny wykonane to trzy tablice. Pozycje listy budujesz z nich od nowa po każdej zmianie — pamiętaj, żeby potem przywrócić zaznaczenie.
- Liczba dni do egzaminu:
(dataEgzaminu - DateTime.Today).Days. Uważaj na dzielenie przez zero w dniu egzaminu.
Co trzeba zapamiętać
ProgressBarpokazuje postęp, którego użytkownik nie zmienia —Valueustawia program.- Ustaw
Maximumna wartość docelową — pasek sam przeliczy proporcję. Procent do napisu licz nadouble. - Wartość powyżej
Maximumjest przycinana; prawdziwą sumę trzymaj w polu klasy. - Napis na pasku = pasek i
TextBlockw jednej komórceGrid. Kolor wypełnienia toForeground. IsIndeterminate— „czekaj”, gdy czas nie jest znany. Pasek nie odświeża się w trakcie długiej pętli.
Dokumentacja: Microsoft Learn — klasa ProgressBar.