Zdarzenia myszy
Do tej pory mysz służyła tylko do klikania przycisków. Każda kontrolka — także zwykła ramka czy napis — potrafi jednak zareagować na najechanie kursorem, opuszczenie, wciśnięcie lewego lub prawego przycisku, ruch i kółko. Dzięki temu zrobisz podświetlane kafelki, ocenę gwiazdkami, mapę sali czy prosty program do rysowania. To pierwszy krok do gier, które zbudujesz pod koniec bloku.
Czego się dziś nauczysz
- Zareagujesz na najechanie kursorem na kontrolkę i na jej opuszczenie.
- Rozróżnisz lewy i prawy przycisk myszy oraz podwójne kliknięcie.
- Odczytasz pozycję kursora względem wybranej kontrolki.
- Obsłużysz kółko myszy i zmienisz wygląd kursora.
- Podepniesz zdarzenia myszy do kontrolek tworzonych w kodzie.
- Wyjaśnisz, dlaczego panel bez tła nie reaguje na mysz.
Zdarzenia myszy
| Zdarzenie | Parametr e | Kiedy |
|---|---|---|
MouseEnter | MouseEventArgs | kursor wjechał nad kontrolkę |
MouseLeave | MouseEventArgs | kursor opuścił kontrolkę |
MouseMove | MouseEventArgs | każdy ruch kursora nad kontrolką |
MouseLeftButtonDown / Up | MouseButtonEventArgs | wciśnięcie / puszczenie lewego przycisku |
MouseRightButtonDown / Up | MouseButtonEventArgs | to samo dla prawego przycisku |
MouseDown | MouseButtonEventArgs | dowolny przycisk; który — mówi e.ChangedButton |
MouseWheel | MouseWheelEventArgs | obrót kółka; kierunek w e.Delta |
Typy parametrów i klasa Cursors są w przestrzeni nazw System.Windows.Input — dopisz using System.Windows.Input;. Visual Studio dodaje je automatycznie, gdy tworzysz metodę obsługi z XAML.
Najechanie i opuszczenie
<Border x:Name="Kafel" Width="160" Height="80" Background="LightGray" CornerRadius="6"
MouseEnter="Kafel_MouseEnter" MouseLeave="Kafel_MouseLeave">
<TextBlock Text="Najedź na mnie" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
private void Kafel_MouseEnter(object sender, MouseEventArgs e)
{
Kafel.Background = Brushes.Gold;
}
private void Kafel_MouseLeave(object sender, MouseEventArgs e)
{
Kafel.Background = Brushes.LightGray;
}
MouseEnter i MouseLeave zawsze występują w parze: co włączysz przy wejściu, wyłączasz przy wyjściu. Czy kursor jest teraz nad kontrolką, sprawdzisz też właściwością IsMouseOver.
Przyciski myszy
// w XAML: MouseDown="Kafel_MouseDown"
private void Kafel_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
Info.Text = "lewy";
}
else if (e.ChangedButton == MouseButton.Right)
{
Info.Text = "prawy";
}
else if (e.ChangedButton == MouseButton.Middle)
{
Info.Text = "środkowy (kółko)";
}
}
Dla jednego przycisku wygodniej podpiąć MouseLeftButtonDown albo MouseRightButtonDown. Czy przycisk jest trzymany w trakcie ruchu, sprawdzisz w MouseMove albo MouseEnter: e.LeftButton == MouseButtonState.Pressed — to podstawa rysowania „przeciąganiem”.
Przycisk „zjada” lewy przycisk myszy
Button sam obsługuje lewy przycisk, żeby wywołać Click, i jego MouseLeftButtonDown nie dociera do Twojej metody. Dla przycisku używaj Click, a jeśli koniecznie potrzebujesz wciśnięcia — zdarzenia PreviewMouseLeftButtonDown. Ramki, napisy i panele tego problemu nie mają.
Pozycja kursora
e.GetPosition(kontrolka) zwraca punkt (Point) z polami X i Y — współrzędnymi kursora liczonymi od lewego górnego rogu podanej kontrolki. Oś Y rośnie w dół.
private void Obszar_MouseMove(object sender, MouseEventArgs e)
{
Point p = e.GetPosition(Obszar); // względem obszaru
Point w = e.GetPosition(this); // względem całego okna
Info.Text = $"w obszarze: {p.X:F0}, {p.Y:F0} w oknie: {w.X:F0}, {w.Y:F0}";
}
x rośnie w prawo, y — w dół
Szerokość i wysokość kontrolki po ułożeniu w oknie podają ActualWidth i ActualHeight — przydają się np. do sprawdzenia, czy kursor jest w lewej, czy prawej połowie.
Kółko, dwuklik i kursor
// kółko: Delta > 0 — od siebie, Delta < 0 — do siebie (zwykle ±120 na „ząbek”)
private void Podglad_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta > 0 && Podglad.FontSize < 72) Podglad.FontSize += 2;
if (e.Delta < 0 && Podglad.FontSize > 8) Podglad.FontSize -= 2;
}
// podwójne kliknięcie: liczba kliknięć w serii
private void Kafel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 2)
{
Info.Text = "Dwuklik!";
}
}
Wygląd kursora nad kontrolką ustawisz właściwością Cursor: w XAML Cursor="Hand", w kodzie Cursors.Hand. Inne przydatne: Cross (celownik), Wait (klepsydra), No (zakaz), SizeAll (przesuwanie).
Tło łapie mysz
WPF przekazuje zdarzenie myszy kontrolce, która jest narysowana pod kursorem. Panel bez Background jest przezroczysty „na wylot” — kliknięcie w puste miejsce panelu go nie dotyczy, a MouseMove działa tylko nad jego dziećmi. Rozwiązanie: nadaj panelowi tło, choćby Background="Transparent" — przezroczyste, ale „klikalne”.
Stan stanowisk w pracowni
Mapa pracowni z 16 stanowiskami. Najechanie na kafelek podświetla go niebieską ramką i pokazuje informacje po prawej. Lewy przycisk zmienia stan stanowiska w kółko (wolne → zajęte → awaria), prawy przywraca „wolne”. Pasek stanu pokazuje liczby stanowisk w każdym stanie i współrzędne kursora nad salą.
Rząd 2, miejsce 2
kursor nad stanowiskiem PC-06 — niebieska ramka i informacje po prawej
<Window x:Class="Lekcja25.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Pracownia 204 — stan stanowisk" Height="460" Width="620"
MinWidth="500" MinHeight="400" WindowStartupLocation="CenterScreen">
<DockPanel Margin="12">
<!-- legenda u góry -->
<TextBlock DockPanel.Dock="Top" Margin="0,0,0,8" TextWrapping="Wrap"
Text="Lewy przycisk: zmień stan (wolne → zajęte → awaria). Prawy przycisk: przywróć „wolne”."/>
<!-- pasek stanu na dole -->
<Border DockPanel.Dock="Bottom" Background="#FFE5E7EB" Padding="8,4" Margin="0,8,0,0">
<DockPanel>
<TextBlock x:Name="Pozycja" DockPanel.Dock="Right" Foreground="DimGray"/>
<TextBlock x:Name="Podsumowanie"/>
</DockPanel>
</Border>
<!-- panel informacji po prawej -->
<Border DockPanel.Dock="Right" Width="170" Margin="10,0,0,0" Padding="10"
BorderBrush="LightGray" BorderThickness="1" CornerRadius="6">
<StackPanel>
<TextBlock x:Name="InfoNazwa" FontSize="18" FontWeight="Bold" Text="—"/>
<TextBlock x:Name="InfoStan" Margin="0,6,0,0" TextWrapping="Wrap"
Text="Najedź myszą na stanowisko."/>
</StackPanel>
</Border>
<!-- sala: 16 kafelków tworzonych w kodzie -->
<UniformGrid x:Name="Sala" Rows="4" Columns="4" Background="White"
MouseMove="Sala_MouseMove" MouseLeave="Sala_MouseLeave"/>
</DockPanel>
</Window>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace Lekcja25;
public partial class MainWindow : Window
{
private readonly string[] stany = new string[16]; // "wolne", "zajęte" albo "awaria"
public MainWindow()
{
InitializeComponent();
for (int i = 0; i < stany.Length; i++)
{
stany[i] = "wolne";
Border kafel = new Border
{
Margin = new Thickness(4),
CornerRadius = new CornerRadius(6),
BorderBrush = Brushes.Gray,
BorderThickness = new Thickness(1),
Cursor = Cursors.Hand, // kursor „łapka” nad kafelkiem
Tag = i, // numer stanowiska
Child = new TextBlock
{
Text = $"PC-{i + 1:D2}",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
FontWeight = FontWeights.Bold
}
};
// zdarzenia myszy podpinamy w kodzie, bo kafelki powstają w kodzie
kafel.MouseEnter += Kafel_MouseEnter;
kafel.MouseLeave += Kafel_MouseLeave;
kafel.MouseLeftButtonDown += Kafel_MouseLeftButtonDown;
kafel.MouseRightButtonDown += Kafel_MouseRightButtonDown;
Sala.Children.Add(kafel);
Pokoloruj(kafel);
}
PokazPodsumowanie();
}
// Kursor wjechał nad kafelek
private void Kafel_MouseEnter(object sender, MouseEventArgs e)
{
Border kafel = (Border)sender;
kafel.BorderBrush = Brushes.RoyalBlue;
kafel.BorderThickness = new Thickness(3);
PokazInformacje(kafel);
}
// Kursor opuścił kafelek
private void Kafel_MouseLeave(object sender, MouseEventArgs e)
{
Border kafel = (Border)sender;
kafel.BorderBrush = Brushes.Gray;
kafel.BorderThickness = new Thickness(1);
}
// Lewy przycisk: wolne → zajęte → awaria → wolne
private void Kafel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Border kafel = (Border)sender;
int nr = (int)kafel.Tag;
if (stany[nr] == "wolne") stany[nr] = "zajęte";
else if (stany[nr] == "zajęte") stany[nr] = "awaria";
else stany[nr] = "wolne";
Pokoloruj(kafel);
PokazInformacje(kafel);
PokazPodsumowanie();
}
// Prawy przycisk: szybki powrót do „wolne”
private void Kafel_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
Border kafel = (Border)sender;
stany[(int)kafel.Tag] = "wolne";
Pokoloruj(kafel);
PokazInformacje(kafel);
PokazPodsumowanie();
}
// Pozycja kursora względem sali, przy każdym ruchu myszy
private void Sala_MouseMove(object sender, MouseEventArgs e)
{
Point p = e.GetPosition(Sala);
Pozycja.Text = $"x = {p.X:F0}, y = {p.Y:F0}";
}
private void Sala_MouseLeave(object sender, MouseEventArgs e)
{
Pozycja.Text = "";
}
private void Pokoloruj(Border kafel)
{
string stan = stany[(int)kafel.Tag];
if (stan == "wolne") kafel.Background = Brushes.Honeydew;
else if (stan == "zajęte") kafel.Background = Brushes.LightSkyBlue;
else kafel.Background = Brushes.LightCoral;
}
private void PokazInformacje(Border kafel)
{
int nr = (int)kafel.Tag;
InfoNazwa.Text = $"PC-{nr + 1:D2}";
InfoStan.Text = $"Stan: {stany[nr]}\nRząd {nr / 4 + 1}, miejsce {nr % 4 + 1}";
}
private void PokazPodsumowanie()
{
int wolne = 0, zajete = 0, awarie = 0;
foreach (string s in stany)
{
if (s == "wolne") wolne++;
else if (s == "zajęte") zajete++;
else awarie++;
}
Podsumowanie.Text = $"Wolne: {wolne} Zajęte: {zajete} Awaria: {awarie}";
}
}
Co dzieje się po kolei
- Konstruktor tworzy 16 kafelków (
Borderz napisem) i wkłada je doUniformGrid. Numer stanowiska jest wTag, a stan — w tablicystanypod tym samym numerem. - Zdarzenia myszy kafelków są podpinane w kodzie przez
+=, tak jakClickw lekcji 04. Wszystkie kafelki mają te same cztery metody, a który kafelek wywołał zdarzenie, mówisender. Cursor = Cursors.Handpokazuje „łapkę” — użytkownik widzi, że kafelek jest klikalny.MouseEnterpogrubia ramkę i wypisuje informacje,MouseLeaveprzywraca zwykłą ramkę. Panel informacji zostaje z danymi ostatniego kafelka.- Lewy przycisk przesuwa stan o jeden dalej w kółko, prawy ustawia „wolne”. Po każdej zmianie odświeżamy kolor, informacje i podsumowanie.
{i + 1:D2}formatuje liczbę na dwóch cyfrach z zerem z przodu: 1 → „01”.SalamaBackground="White", więcMouseMovedziała także w szczelinach między kafelkami.e.GetPosition(Sala)daje współrzędne względem lewego górnego rogu sali.- Rząd i miejsce wynikają z numeru:
nr / 4inr % 4(jak w grze „Zgaś światła” z lekcji 12).
Sprawdź trzy rzeczy
- Usuń z
UniformGridatrybutBackground="White". Współrzędne zmieniają się tylko nad kafelkami — w szczelinach zastygają. - Usuń podpięcie
MouseLeave. Najechane kafelki zostaną z niebieską ramką na zawsze. - Zmień w
Sala_MouseMoveGetPosition(Sala)naGetPosition(this). Współrzędne w lewym górnym rogu sali nie będą już równe (0, 0).
Zestawienie elementów
| Element | Typ | Co robi |
|---|---|---|
e.GetPosition(kontrolka) | Point | Pozycja kursora względem kontrolki (X, Y). |
e.ChangedButton | MouseButton | Left, Right, Middle. |
e.ClickCount | int | 1 — kliknięcie, 2 — dwuklik. |
e.LeftButton | MouseButtonState | Pressed, gdy lewy przycisk jest trzymany. |
e.Delta | int | Kierunek obrotu kółka (> 0 od siebie). |
IsMouseOver | bool | Czy kursor jest nad kontrolką. |
Cursor, Cursors.Hand | kursor | Wygląd kursora nad kontrolką. |
ActualWidth, ActualHeight | double | Rzeczywisty rozmiar kontrolki w oknie. |
{x:D2} | formatowanie | Liczba całkowita na co najmniej 2 cyfrach. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
MouseLeftButtonDown na Button | Metoda się nie wywołuje — przycisk obsługuje lewy przycisk sam. Użyj Click. |
panel bez Background | Kliknięcia i ruch w pustych miejscach nie są zgłaszane. Dodaj Background="Transparent". |
MouseEnter z parametrem MouseButtonEventArgs | Błąd: sygnatura nie pasuje — parametr to MouseEventArgs. |
brak using System.Windows.Input; | CS0246 — nie znaleziono MouseEventArgs, Cursors. |
podświetlenie w MouseEnter, bez MouseLeave | Kontrolka zostaje podświetlona po odjechaniu kursora. |
e.GetPosition(this), gdy chodzi o pozycję w obszarze | Współrzędne liczone od rogu okna, nie obszaru. |
ciężkie obliczenia w MouseMove | Zdarzenie przychodzi dziesiątki razy na sekundę — program zaczyna „przycinać”. |
Zadania
Każde zadanie wykonaj w nowym projekcie Aplikacja WPF. Kontrolki reagujące na mysz zrób z ramek, napisów i paneli — nie z przycisków. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.
Pionowe menu z pięciu kafelków (Start, Plan lekcji, Oceny, Wiadomości, Ustawienia). Najechanie zmienia tło kafelka na jasnoniebieskie i kursor na „łapkę”, a pod menu wyświetla krótki opis sekcji. Kliknięcie wybiera sekcję: wybrany kafelek ma ciemnoniebieskie tło i biały tekst także po odjechaniu kursora, a nagłówek okna pokazuje jej nazwę.
pokaż wskazówkę
- Wybrany kafelek zapamiętaj w polu klasy typu
Border?. WMouseLeavenie zmieniaj tła wybranego kafelka.
Duży kafelek z liczbą na środku. Lewy przycisk: +1, prawy: −1, kółko od siebie: +10, do siebie: −10, podwójne kliknięcie lewym: zerowanie. Liczba jest ograniczona do zakresu −100…100, a jej kolor zależy od znaku (zielona, czerwona, czarna dla zera). Pod kafelkiem opis ostatniej akcji: „kółko ↑ (+10)”.
pokaż wskazówkę
- Dwuklik to dwa zdarzenia
MouseLeftButtonDown: przy pierwszymClickCount= 1 (doda 1), przy drugim = 2. Przy zerowaniu nie przejmuj się tym pierwszym dodaniem — i tak zerujesz.
Obszar 400 × 300 z cienką ramką. Środek obszaru to punkt (0, 0) „matematycznego” układu, w którym oś Y rośnie w górę. Podczas ruchu myszy program pokazuje współrzędne w tym układzie (np. „x = −120, y = 45”), numer ćwiartki (I–IV, „na osi”, gdy x albo y wynosi 0) i koloruje tło obszaru inaczej dla każdej ćwiartki. Po wyjechaniu poza obszar napis „Poza obszarem”, tło białe.
pokaż wskazówkę
- Przeliczenie:
x = p.X − szerokość / 2,y = wysokość / 2 − p.Y(minus odwraca oś).
Pięć dużych gwiazdek (napisy „★”, czcionka 40) w poziomym rzędzie. Najechanie na gwiazdkę pokazuje podgląd: gwiazdki do tej włącznie są złote, dalsze szare, a pod spodem opis („3 — dobry”). Po odjechaniu z całego rzędu wraca wybrana ocena. Kliknięcie ustala ocenę. Przycisk Wyślij opinię jest aktywny dopiero po wyborze oceny.
pokaż wskazówkę
- Numer gwiazdki trzymaj w
Tag. Jedna metoda „pokoloruj do n” wystarczy dla podglądu i dla wybranej oceny. MouseLeavepodepnij do całego panelu z gwiazdkami — inaczej przejście między gwiazdkami będzie „mrugać”.
Siatka 8 × 8 pól z numerami 1–4 (wzór zapisz w tablicy, np. prosty domek albo serce). Po lewej paleta czterech kolorów z numerami. Wybrany kolor ma pogrubioną ramkę. Kliknięcie pola maluje je wybranym kolorem; prawy przycisk czyści pole. Pod siatką „Poprawnie pomalowane: 41 / 64”, a po ukończeniu — „Obrazek gotowy!” i znikają numery.
pokaż wskazówkę
- Wzór:
int[] wzorz 64 liczbami; pomalowany kolor każdego pola trzymaj w drugiej tablicy (0 = puste).
Siatka 16 × 16 kwadratowych pól i paleta 8 kolorów. Rysowanie „przeciąganiem”: wciśnięcie lewego przycisku i ruch nad polami maluje je wybranym kolorem; prawy przycisk wymazuje. Kółko myszy nad siatką zmienia wybrany kolor na następny/poprzedni w palecie. Pole wyboru Siatka pokazuje/ukrywa linie między polami. Przycisk Wyczyść (z potwierdzeniem) i napis z liczbą pomalowanych pól. Kursor nad siatką to celownik.
pokaż wskazówkę
- Malowanie przeciąganiem: w
MouseEnterpola sprawdźe.LeftButton == MouseButtonState.Pressed. Samo wciśnięcie na pierwszym polu obsłuż wMouseLeftButtonDown. - Linie siatki to
BorderThicknesspól — ustawiasz 0 albo 0,5 w pętli.
Co trzeba zapamiętać
MouseEnteriMouseLeavew parze — podświetlenie i jego wyłączenie.MouseLeftButtonDown,MouseRightButtonDownalboMouseDownze.ChangedButton; dwuklik —e.ClickCount == 2.e.GetPosition(kontrolka)— pozycja od lewego górnego rogu kontrolki, y w dół.MouseWheelie.Delta— kierunek kółka;Cursor— wygląd kursora.- Panel reaguje na mysz w pustych miejscach tylko wtedy, gdy ma tło.
- Przycisk sam obsługuje lewy przycisk myszy — dla niego zostaje
Click.
Dokumentacja: Microsoft Learn — dane wejściowe myszy.