Button i zdarzenie Click
Dotąd okno tylko wyświetlało to, co przygotował konstruktor. Teraz zacznie reagować na użytkownika. Przycisk wysyła sygnał „kliknięto mnie”, a Ty piszesz metodę, która na ten sygnał odpowiada. Nauczysz się podpinać zdarzenie, rozumieć parametry sender i e, pamiętać stan programu między kliknięciami i obsługiwać wiele przycisków jedną metodą.
Czego się dziś nauczysz
- Wyjaśnisz, czym jest zdarzenie i metoda jego obsługi.
- Dodasz przycisk, podepniesz zdarzenie
Clickw XAML i utworzysz metodę w Visual Studio. - Wyjaśnisz znaczenie parametrów
senderie. - Przechowasz stan programu w polu klasy i odświeżysz napisy jedną metodą.
- Obsłużysz kilka przycisków jedną metodą, korzystając z
ContentiTag. - Utworzysz przyciski w kodzie i podepniesz im zdarzenie operatorem
+=.
Okno, które reaguje
Konstruktor wykonuje się raz, przy otwarciu okna. Wszystko, co ma się dziać później — po kliknięciu — musi być w osobnej metodzie, którą WPF wywoła we właściwym momencie. Taki sygnał od kontrolki to zdarzenie (ang. event), a Twoja metoda to metoda obsługi zdarzenia (ang. event handler).
| Kod w… | Kiedy się wykonuje | Przykład |
|---|---|---|
| konstruktorze | raz, przy otwarciu okna | ustawienie daty w tytule (lekcja 01) |
metodzie obsługi Click | przy każdym kliknięciu przycisku | dodanie głosu, przeliczenie wyniku |
Przycisk i zdarzenie Click
<StackPanel Margin="15">
<TextBlock x:Name="Komunikat" Text="Gotowy?" FontSize="18" Margin="0,0,0,10"/>
<Button Content="Start" Width="120" Height="32" Click="Start_Click"/>
<!-- ↑ zdarzenie ↑ metoda w pliku .cs -->
</StackPanel>
private void Start_Click(object sender, RoutedEventArgs e)
{
Komunikat.Text = "Gra rozpoczęta!";
Title = "Gra — trwa";
}
| Właściwość przycisku | Co robi |
|---|---|
Content | Napis na przycisku. Uwaga: to Content, a nie Text jak w napisie. |
Width, Height | Rozmiar. W pionowym StackPanel przycisk bez Width rozciąga się na całą szerokość. |
Click | Zdarzenie: nazwa metody wykonywanej po kliknięciu. |
IsDefault="True" | Przycisk „klika się” klawiszem Enter. |
IsCancel="True" | Przycisk „klika się” klawiszem Esc. |
Jak utworzyć metodę bez przepisywania
W XAML wpisz Click=" i wybierz z podpowiedzi <Nowa procedura obsługi zdarzeń>. Visual Studio utworzy w pliku .cs pustą metodę z poprawnymi parametrami. Do kodu przejdziesz klawiszem F7, z powrotem do projektanta — Shift + F7.
Dwuklik w projektancie
Dwukrotne kliknięcie przycisku w projektancie też tworzy metodę Click. Łatwo jednak zrobić to przez przypadek i zostawić pustą metodę. Jeśli ją usuwasz, usuń też atrybut Click w XAML — inaczej projekt się nie skompiluje.
Budowa metody obsługi
private void Start_Click(object sender, RoutedEventArgs e)
// ↑ ↑ ↑ ↑ ↑
// │ │ │ │ └── szczegóły zdarzenia
// │ │ │ └── KTO wywołał zdarzenie (kliknięty przycisk)
// │ │ └── zwyczajowo: NazwaPrzycisku_Zdarzenie
// │ └── nic nie zwraca
// └── dostępna tylko w tej klasie
{
}
| Parametr | Typ | Co zawiera |
|---|---|---|
sender | object | Kontrolkę, która wysłała zdarzenie. Ma ogólny typ object, więc żeby użyć jej właściwości, trzeba ją zrzutować: (Button)sender. |
e | RoutedEventArgs | Szczegóły zdarzenia. Przy kliknięciu zwykle niepotrzebne. |
Plik MainWindow.xaml.cs nazywa się code-behind — to kod „za” oknem. W eksploratorze rozwiązań jest schowany pod plikiem .xaml.
Stan programu w polach klasy
Metoda obsługi zaczyna się i kończy przy każdym kliknięciu. Zmienna zadeklarowana w jej środku znika po zakończeniu metody i przy następnym kliknięciu powstaje od nowa:
private void Dodaj_Click(object sender, RoutedEventArgs e)
{
int licznik = 0; // ✗ przy każdym kliknięciu tworzona od nowa
licznik++;
Wynik.Text = $"Kliknięcia: {licznik}";
}
Wartość, którą program ma pamiętać między kliknięciami, trzymasz w polu klasy — zmiennej zadeklarowanej w klasie okna, poza metodami. Pole żyje tak długo jak okno:
public partial class MainWindow : Window
{
private int licznik = 0; // ✓ pole klasy
public MainWindow()
{
InitializeComponent();
}
private void Dodaj_Click(object sender, RoutedEventArgs e)
{
licznik++;
Wynik.Text = $"Kliknięcia: {licznik}";
}
}
Dane i ich obraz
Pole licznik to dane, napis Wynik.Text to ich obraz. Licz zawsze na polach, a napisy odświeżaj na ich podstawie. Gdy kilka przycisków zmienia dane, odświeżanie zamknij w jednej zwykłej metodzie i wołaj ją na końcu każdej metody Click.
Jedna metoda, wiele przycisków
Kilka przycisków może mieć tę samą metodę obsługi. Który kliknięto, mówi sender:
<StackPanel Margin="15">
<Button Content="Pizza" Tag="32" Click="Danie_Click" Margin="0,0,0,4"/>
<Button Content="Burger" Tag="27" Click="Danie_Click" Margin="0,0,0,4"/>
<Button Content="Sałatka" Tag="19" Click="Danie_Click"/>
<TextBlock x:Name="Wybor" Margin="0,10,0,0"/>
</StackPanel>
// na górze pliku: using System.Windows.Controls;
private void Danie_Click(object sender, RoutedEventArgs e)
{
Button przycisk = (Button)sender; // z object na Button
Wybor.Text = $"Wybrano: {przycisk.Content}, cena: {przycisk.Tag} zł";
}
| Sposób | Kiedy |
|---|---|
przycisk.Content | Potrzebujesz napisu z przycisku. |
przycisk.Tag | Potrzebujesz dodatkowej wartości, której nie widać — np. ceny. Tag ma każda kontrolka; to „kieszeń” na dowolne dane. |
if (sender == Pizza) | Jeden przycisk ma działać inaczej niż reszta (przycisk potrzebuje x:Name). |
Content i Tag mają typ object. W interpolacji $"{…}" zamieniają się na tekst same. Gdy potrzebujesz liczby, najpierw zamień na tekst, potem na liczbę: int cena = int.Parse($"{przycisk.Tag}"); — wartości w Tag wpisujesz sam, więc wiesz, że są poprawne.
Przyciski tworzone w kodzie
Przyciski można tworzyć w konstruktorze tak jak napisy w lekcji 03. Zdarzenie podpinasz wtedy w C# operatorem += — zapis Click="Danie_Click" w XAML robi pod spodem dokładnie to samo:
for (int i = 1; i <= 5; i++)
{
Button gwiazdka = new Button();
gwiazdka.Content = $"{i} ★";
gwiazdka.Tag = i;
gwiazdka.Width = 50;
gwiazdka.Click += Ocena_Click; // bez nawiasów — przekazujemy metodę, nie wywołujemy jej
Gwiazdki.Children.Add(gwiazdka); // Gwiazdki to poziomy StackPanel z XAML
}
Głosowanie klasowe
Program zlicza głosy w ankiecie „Czy wycieczka ma być w góry?”. Po każdym głosie pokazuje wynik i informację, która odpowiedź prowadzi. Wyzeruj zaczyna od nowa. Głosy pamiętają pola klasy, a wspólna metoda PokazWynik odświeża oba napisy.
stan po 23 głosach
<Window x:Class="Lekcja04.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Głosowanie" Height="320" Width="400"
WindowStartupLocation="CenterScreen">
<StackPanel Margin="20">
<TextBlock Text="Czy wycieczka klasowa ma być w góry?"
FontSize="18" FontWeight="Bold" TextWrapping="Wrap"/>
<StackPanel Orientation="Horizontal" Margin="0,15,0,0">
<Button Content="TAK" Width="100" Height="36" Margin="0,0,10,0" Click="Tak_Click"/>
<Button Content="NIE" Width="100" Height="36" Click="Nie_Click"/>
</StackPanel>
<TextBlock x:Name="Wynik" Text="TAK: 0 NIE: 0" FontSize="16" Margin="0,15,0,0"/>
<TextBlock x:Name="Prowadzi" Text="Brak głosów" FontSize="16" Margin="0,5,0,0"/>
<Button Content="Wyzeruj" Width="100" HorizontalAlignment="Left"
Margin="0,15,0,0" Click="Zeruj_Click"/>
</StackPanel>
</Window>
using System.Windows;
namespace Lekcja04;
public partial class MainWindow : Window
{
// Pola klasy — żyją tak długo jak okno, więc pamiętają głosy między kliknięciami
private int glosyTak = 0;
private int glosyNie = 0;
public MainWindow()
{
InitializeComponent();
}
private void Tak_Click(object sender, RoutedEventArgs e)
{
glosyTak++;
PokazWynik();
}
private void Nie_Click(object sender, RoutedEventArgs e)
{
glosyNie++;
PokazWynik();
}
private void Zeruj_Click(object sender, RoutedEventArgs e)
{
glosyTak = 0;
glosyNie = 0;
PokazWynik();
}
// Zwykła metoda (nie zdarzenie) — wołają ją wszystkie trzy przyciski
private void PokazWynik()
{
Wynik.Text = $"TAK: {glosyTak} NIE: {glosyNie}";
if (glosyTak == 0 && glosyNie == 0)
{
Prowadzi.Text = "Brak głosów";
}
else if (glosyTak > glosyNie)
{
Prowadzi.Text = "Prowadzi: TAK";
}
else if (glosyNie > glosyTak)
{
Prowadzi.Text = "Prowadzi: NIE";
}
else
{
Prowadzi.Text = "Remis";
}
}
}
Co dzieje się po kolei
- Przy otwarciu okna konstruktor wywołuje tylko
InitializeComponent(). Napisy pokazują stan początkowy wpisany w XAML: „TAK: 0 NIE: 0” i „Brak głosów”. - Kliknięcie TAK wywołuje
Tak_Click: poleglosyTakrośnie o 1, aPokazWynik()przepisuje oba napisy na podstawie pól. - Pola
glosyTakiglosyNiesą zadeklarowane w klasie, poza metodami — dlatego pamiętają wartości między kliknięciami. PokazWynikto zwykła metoda bez parametrówsenderie: nikt jej nie podpina w XAML, wywołujemy ją sami z trzech miejsc.- W
PokazWynikprzypadek „brak głosów” jest sprawdzany jako pierwszy. Gdyby był na końcu, przy 0 : 0 zadziałałaby wcześniej gałąź „Remis”. - Ostatnie
elseto remis — skoro nie ma zera głosów i nikt nie prowadzi, liczby są równe. - Przyciski TAK i NIE stoją obok siebie w poziomym panelu. Przycisk Wyzeruj ma
HorizontalAlignment="Left", żeby nie rozciągał się na całą szerokość.
Sprawdź trzy rzeczy
- Usuń z
Zeruj_ClickwywołaniePokazWynik();. Po kliknięciu pola się wyzerują, ale napisy pokażą stary wynik aż do następnego głosu. - Przenieś deklarację
int glosyTak = 0;do środkaTak_Click(usuńprivate). Wynik TAK nigdy nie przekroczy 1. - Zmień w XAML
Click="Nie_Click"naClick="Tak_Click". Oba przyciski będą dodawać głos na TAK — metoda nie wie, który przycisk ją wywołał, bo nie sprawdzasender.
Zestawienie elementów
| Element | Znaczenie | Uwagi |
|---|---|---|
Button | przycisk | Napis w Content. |
Click="Metoda" | podpięcie zdarzenia w XAML | Nazwa metody bez nawiasów. |
(object sender, RoutedEventArgs e) | parametry metody Click | Najlepiej generować z podpowiedzi edytora. |
(Button)sender | rzutowanie | Daje dostęp do Content i Tag klikniętego przycisku. |
Tag | dodatkowa wartość kontrolki | Typ object, niewidoczna na ekranie. |
IsDefault, IsCancel | przycisk pod Enter / Esc | W oknie najwyżej po jednym takim przycisku. |
| pole klasy | zmienna poza metodami | Pamięta wartość między kliknięciami. |
przycisk.Click += Metoda; | podpięcie zdarzenia w C# | Dla przycisków tworzonych w kodzie. |
int.Parse($"{przycisk.Tag}") | liczba z Tag | Tylko dla wartości, które sam wpisałeś. |
Zanim utkniesz
| Zapis lub sytuacja | Problem |
|---|---|
Click="Zapisz_Click", a w C# brak tej metody | CS1061: ’MainWindow’ does not contain a definition for 'Zapisz_Click’. Nazwy w XAML i C# muszą być identyczne, łącznie z wielkością liter. |
private void Tak_Click() | CS0123: metoda nie pasuje do zdarzenia — brakuje parametrów (object sender, RoutedEventArgs e). |
Click="Tak_Click()" | W XAML podajemy samą nazwę metody, bez nawiasów. |
<Button Text="OK"/> | Przycisk nie ma Text — napis to Content. |
| licznik jako zmienna w metodzie | Zeruje się przy każdym kliknięciu. |
Button p = sender; | CS0266 — sender ma typ object, potrzebne (Button)sender. |
zdarzenie w XAML i += w konstruktorze dla tego samego przycisku | Metoda wykonuje się dwa razy na jedno kliknięcie. |
nowy.Click += Ocena_Click(); | Z nawiasami metoda zostałaby wywołana, a nie przekazana — błąd kompilacji. |
| zmiana danych bez odświeżenia napisu | Na ekranie zostaje stary stan. Po każdej zmianie wołaj metodę odświeżającą. |
Zadania
Każde zadanie wykonaj w nowym projekcie Aplikacja WPF. Do dyspozycji masz okno, napisy, StackPanel i przyciski. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.
Pracownia mieści 16 osób. Okno ma duży napis „W sali: 0 / 16” i trzy przyciski: Wejście, Wyjście, Reset. Liczba osób nie może spaść poniżej 0 ani przekroczyć 16. Pod licznikiem drugi napis: „Wolne miejsca: 16”, a gdy sala jest pełna — „Sala pełna, nie wpuszczaj!”. Odświeżanie obu napisów zamknij w jednej metodzie.
pokaż wskazówkę
- Liczbę osób trzymaj w polu klasy, limit — w stałej.
- W metodach Wejście/Wyjście zmieniaj pole tylko wtedy, gdy nie przekroczy granicy.
Siedem przycisków z polskimi skrótami dni („pon” … „nd”) w jednym poziomym rzędzie. Kliknięcie dowolnego pokazuje w napisie pod spodem: „poniedziałek — Monday”. Wszystkie przyciski obsługuje jedna metoda, a angielska nazwa jest schowana w Tag. W komentarzu napisz, ile metod musiałbyś napisać bez sender i Tag.
pokaż wskazówkę
- Pełną polską nazwę też możesz zapisać w
Tag, np.Tag="poniedziałek — Monday". - W metodzie: rzutowanie
sendernaButtoni odczytTag.
Okno „Oceń film” z tytułem filmu i rzędem przycisków od 1 do 10 utworzonych pętlą w konstruktorze. Kliknięcie oceny pokazuje „Twoja ocena: 8/10 — świetny” (1–3: słaby, 4–6: przeciętny, 7–8: świetny, 9–10: arcydzieło). Program liczy też średnią ze wszystkich kliknięć: „Średnia z 5 ocen: 7,4”.
pokaż wskazówkę
- Każdemu przyciskowi daj w
Tagjego liczbę i podepnij metodę przez+=. - Do średniej potrzebujesz dwóch pól: sumy ocen i liczby ocen. Uważaj na dzielenie dwóch liczb
int.
Klasa zbiera 150 zł na bilety. Przyciski 1 zł, 2 zł, 5 zł i 10 zł wrzucają monetę lub banknot. Program pokazuje zebraną kwotę, liczbę wpłat i „Do celu brakuje: 37 zł”. Po osiągnięciu celu napis zmienia się na „Cel osiągnięty! Nadwyżka: 3 zł”. Jedna metoda obsługuje wszystkie cztery przyciski i nie porównuje nazw przycisków.
pokaż wskazówkę
- Nominał zapisz w
Tag, a w metodzie zamień go na liczbę. - Brakującą kwotę i nadwyżkę licz z tych samych pól — jedną z nich wybierz warunkiem.
Sejf ma trzy przyciski A, B, C i otwiera się po naciśnięciu ich w kolejności A → C → B. Każde poprawne naciśnięcie dokłada gwiazdkę w napisie postępu (*, **, ***). Błędny przycisk zeruje postęp i pokazuje „Błąd — zacznij od nowa”. Po otwarciu napis brzmi „Sejf otwarty!”, a dalsze klikanie A/B/C niczego nie zmienia, dopóki nie klikniesz Zamknij sejf. Jedna metoda obsługuje A, B i C. W komentarzu narysuj (tekstem) wszystkie stany programu i przejścia między nimi.
pokaż wskazówkę
- W polu klasy pamiętaj numer kroku: 0 — nic, 1 — wpisano A, 2 — wpisano A i C. Osobne pole
boolpowie, czy sejf jest otwarty. - Dla każdego kroku jest jeden poprawny przycisk — porównaj
Contentklikniętego przycisku z oczekiwaną literą. - Gwiazdki:
new string('*', liczba).
Tablica wyników meczu siatkówki: dwie drużyny z nazwami, punkty w secie i wygrane sety. Przyciski +1 Orły, +1 Sokoły i Nowy mecz. Set wygrywa drużyna, która ma co najmniej 25 punktów i 2 punkty przewagi (np. 25:23, 27:25) — wtedy jej licznik setów rośnie, punkty w secie się zerują, a w napisie pojawia się „Set dla: Orły (25:23)”. Mecz wygrywa drużyna, która pierwsza wygra 3 sety: napis „KONIEC MECZU — wygrały Orły 3:1”; po końcu meczu przyciski punktów nic nie robią. Pod tablicą wypisuj historię setów, np. „25:23, 21:25, 25:18”.
pokaż wskazówkę
- Warunek końca seta sprawdzaj po każdym punkcie:
punkty >= 25 && punkty - punktyRywala >= 2. - Historię możesz trzymać jako pole typu
stringi dopisywać do niego wynik każdego seta. - Pole
bool„mecz zakończony” zablokuje dalsze liczenie.
Co trzeba zapamiętać
- Zdarzenie to sygnał od kontrolki; metoda obsługi to Twoja reakcja. Łączy je atrybut
Click="NazwaMetody". - Napis przycisku to
Content.IsDefaultiIsCancelprzypisują przycisk do Enter i Esc. sender— kto wywołał zdarzenie (rzutuj naButton),e— szczegóły.- Wartości pamiętane między kliknięciami trzymaj w polach klasy, a napisy odświeżaj jedną metodą.
- Jedna metoda może obsługiwać wiele przycisków — z pomocą
Content,Tagalbo porównaniasender. - Przyciskom tworzonym w kodzie zdarzenie podpinasz operatorem
+=.
Dokumentacja: Microsoft Learn — klasa Button, zdarzenie Click.