Przejdź do treści
School IT / Przestrzeń do nauki

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ą.

C# XAML Button Click sender pola klasy Tag 90 min
CEL LEKCJI

Czego się dziś nauczysz

  • Wyjaśnisz, czym jest zdarzenie i metoda jego obsługi.
  • Dodasz przycisk, podepniesz zdarzenie Click w XAML i utworzysz metodę w Visual Studio.
  • Wyjaśnisz znaczenie parametrów sender i e.
  • Przechowasz stan programu w polu klasy i odświeżysz napisy jedną metodą.
  • Obsłużysz kilka przycisków jedną metodą, korzystając z Content i Tag.
  • Utworzysz przyciski w kodzie i podepniesz im zdarzenie operatorem +=.
TEORIA

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ę wykonujePrzykład
konstruktorzeraz, przy otwarciu oknaustawienie daty w tytule (lekcja 01)
metodzie obsługi Clickprzy każdym kliknięciu przyciskudodanie głosu, przeliczenie wyniku
TEORIA

Przycisk i zdarzenie Click

MainWindow.xaml
<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>
MainWindow.xaml.cs
private void Start_Click(object sender, RoutedEventArgs e)
{
    Komunikat.Text = "Gra rozpoczęta!";
    Title = "Gra — trwa";
}
Właściwość przyciskuCo robi
ContentNapis na przycisku. Uwaga: to Content, a nie Text jak w napisie.
Width, HeightRozmiar. W pionowym StackPanel przycisk bez Width rozciąga się na całą szerokość.
ClickZdarzenie: 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.

TEORIA

Budowa metody obsługi

MainWindow.xaml.cs
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
{
}
ParametrTypCo zawiera
senderobjectKontrolkę, 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.
eRoutedEventArgsSzczegół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.

TEORIA

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:

błąd — licznik zawsze pokazuje 1
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:

poprawnie
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.

TEORIA

Jedna metoda, wiele przycisków

Kilka przycisków może mieć tę samą metodę obsługi. Który kliknięto, mówi sender:

MainWindow.xaml
<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>
MainWindow.xaml.cs
// 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óbKiedy
przycisk.ContentPotrzebujesz napisu z przycisku.
przycisk.TagPotrzebujesz 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.

TEORIA

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:

MainWindow.xaml.cs
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
}
PRZYKŁAD

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.

Głosowanie–□✕
Czy wycieczka klasowa ma być w góry?
TAK
NIE
TAK: 14   NIE: 9
Prowadzi: TAK
Wyzeruj

stan po 23 głosach

MainWindow.xaml — pełny plik
<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>
MainWindow.xaml.cs — pełny plik
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: pole glosyTak rośnie o 1, a PokazWynik() przepisuje oba napisy na podstawie pól.
  • Pola glosyTak i glosyNie są zadeklarowane w klasie, poza metodami — dlatego pamiętają wartości między kliknięciami.
  • PokazWynik to zwykła metoda bez parametrów sender i e: nikt jej nie podpina w XAML, wywołujemy ją sami z trzech miejsc.
  • W PokazWynik przypadek „brak głosów” jest sprawdzany jako pierwszy. Gdyby był na końcu, przy 0 : 0 zadziałałaby wcześniej gałąź „Remis”.
  • Ostatnie else to 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

  1. Usuń z Zeruj_Click wywołanie PokazWynik();. Po kliknięciu pola się wyzerują, ale napisy pokażą stary wynik aż do następnego głosu.
  2. Przenieś deklarację int glosyTak = 0; do środka Tak_Click (usuń private). Wynik TAK nigdy nie przekroczy 1.
  3. Zmień w XAML Click="Nie_Click" na Click="Tak_Click". Oba przyciski będą dodawać głos na TAK — metoda nie wie, który przycisk ją wywołał, bo nie sprawdza sender.
ELEMENTY WBUDOWANE

Zestawienie elementów

ElementZnaczenieUwagi
ButtonprzyciskNapis w Content.
Click="Metoda"podpięcie zdarzenia w XAMLNazwa metody bez nawiasów.
(object sender, RoutedEventArgs e)parametry metody ClickNajlepiej generować z podpowiedzi edytora.
(Button)senderrzutowanieDaje dostęp do Content i Tag klikniętego przycisku.
Tagdodatkowa wartość kontrolkiTyp object, niewidoczna na ekranie.
IsDefault, IsCancelprzycisk pod Enter / EscW oknie najwyżej po jednym takim przycisku.
pole klasyzmienna poza metodamiPamię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 TagTylko dla wartości, które sam wpisałeś.
CZĘSTE BŁĘDY

Zanim utkniesz

Zapis lub sytuacjaProblem
Click="Zapisz_Click", a w C# brak tej metodyCS1061: ’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 metodzieZeruje 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 przyciskuMetoda 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 napisuNa ekranie zostaje stary stan. Po każdej zmianie wołaj metodę odświeżającą.
ZADANIA

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.

ZAD 1Licznik osób w pracowni★☆☆

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.

Pracownia 21–□✕
W sali: 16 / 16
Sala pełna, nie wpuszczaj!
Wejście
Wyjście
Reset
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.
ZAD 2Słowniczek dni tygodnia★☆☆

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 sender na Button i odczyt Tag.
ZAD 3Ocena filmu★★☆

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 Tag jego 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.
ZAD 4Skarbonka na wycieczkę★★☆

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.
ZAD 5Sejf z kodem★★★

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 bool powie, czy sejf jest otwarty.
  • Dla każdego kroku jest jeden poprawny przycisk — porównaj Content klikniętego przycisku z oczekiwaną literą.
  • Gwiazdki: new string('*', liczba).
ZAD 6Sędzia meczu siatkówki★★★

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 string i dopisywać do niego wynik każdego seta.
  • Pole bool „mecz zakończony” zablokuje dalsze liczenie.
PODSUMOWANIE

Co trzeba zapamiętać

  • Zdarzenie to sygnał od kontrolki; metoda obsługi to Twoja reakcja. Łączy je atrybut Click="NazwaMetody".
  • Napis przycisku to Content. IsDefault i IsCancel przypisują przycisk do Enter i Esc.
  • sender — kto wywołał zdarzenie (rzutuj na Button), 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, Tag albo porównania sender.
  • Przyciskom tworzonym w kodzie zdarzenie podpinasz operatorem +=.

Dokumentacja: Microsoft Learn — klasa Button, zdarzenie Click.

Postęp zapisuje się w tej przeglądarce.