ToolBar i StatusBar
Menu z lekcji 35 zawiera wszystkie polecenia, ale najczęściej używane powinny być pod ręką — na pasku narzędzi pod menu. Z kolei na dole okna program może na bieżąco mówić, co się dzieje: pozycja kursora, liczba znaków, powiększenie. Tak wygląda Notatnik, Word czy Visual Studio. W tej lekcji zbudujesz edytor ogłoszeń z oboma paskami.
Czego się dziś nauczysz
- Dodasz pasek narzędzi z przyciskami, separatorami i listą rozwijaną.
- Ułożysz kilka pasków narzędzi obok siebie.
- Użyjesz przycisku, który pozostaje wciśnięty (
ToggleButton). - Zbudujesz pasek stanu z kilkoma polami, także dosuniętymi do prawej.
- Obsłużysz schowek i odczytasz pozycję kursora w polu tekstowym.
ToolBar i ToolBarTray
ToolBar to poziomy pasek, do którego wkłada się zwykłe kontrolki: przyciski, listy rozwijane, pola tekstowe. Pasek nadaje im płaski wygląd — przyciski nie mają ramki, dopóki nie najedziesz na nie myszą. Kilka pasków umieszcza się w ToolBarTray („tacy”), który ustawia je obok siebie:
<DockPanel>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Content="📄" ToolTip="Nowy" Click="Nowy_Click"/>
<Button Content="💾" ToolTip="Zapisz" Click="Zapisz_Click"/>
<Separator/>
<Button Content="↶" ToolTip="Cofnij" Click="Cofnij_Click"/>
</ToolBar>
<ToolBar>
<TextBlock Text="Klasa:" VerticalAlignment="Center"/>
<ComboBox x:Name="Klasa" Width="70" SelectedIndex="0">
<ComboBoxItem Content="1TI"/>
<ComboBoxItem Content="2TI"/>
</ComboBox>
</ToolBar>
</ToolBarTray>
<TextBox x:Name="Tekst" AcceptsReturn="True"/>
</DockPanel>
dwa paski w jednej tacy; przycisk pod kursorem myszy dostaje ramkę, kropki z lewej to uchwyty do przesuwania
Paski w ToolBarTray można przeciągać za uchwyt i zmieniać ich kolejność. Gdy okno jest za wąskie, kontrolki, które się nie mieszczą, trafiają do rozwijanego menu ze strzałką na końcu paska.
Przyciski z samym znakiem są małe, ale bez podpisu niejasne — dlatego każdy ma ToolTip (dymek z lekcji 5). Kliknięcie obsługuje się zwykłym Click, często tą samą metodą co odpowiednia pozycja menu.
ToggleButton
ToggleButton wygląda jak przycisk, ale działa jak CheckBox: kliknięcie go wciska, kolejne — zwalnia. Ma te same IsChecked (typu bool?) oraz zdarzenia Click, Checked i Unchecked. To typowy przycisk „pogrubienie” albo „tryb nocny” na pasku narzędzi:
<ToggleButton x:Name="Pogrubienie" Content="B" FontWeight="Bold" Width="24"
ToolTip="Pogrubienie" Click="Pogrubienie_Click"/>
private void Pogrubienie_Click(object sender, RoutedEventArgs e)
{
Tekst.FontWeight = Pogrubienie.IsChecked == true ? FontWeights.Bold : FontWeights.Normal;
}
Zwykły TextBox ma jedną czcionkę dla całego tekstu, więc pogrubienie dotyczy całego ogłoszenia. Formatowanie pojedynczych słów wymaga innej kontrolki (RichTextBox), która wykracza poza ten kurs.
StatusBar
StatusBar to pasek na dole okna. Każde pole to StatusBarItem — ma jedno dziecko (tekst, panel, pasek postępu…), a Separator rysuje między polami pionową kreskę:
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem>
<TextBlock x:Name="Stan" Text="Gotowe"/>
</StatusBarItem>
<Separator/>
<StatusBarItem x:Name="Uczniow" Content="Uczniów: 0"/>
<!-- ostatnie pole przy prawej krawędzi -->
<StatusBarItem HorizontalAlignment="Right">
<ProgressBar x:Name="Postep" Width="100" Height="12"/>
</StatusBarItem>
</StatusBar>
trzy pola: tekst, liczba uczniów i pasek postępu dosunięty do prawej
W środku StatusBar działa jak DockPanel z lekcji 13: pola ustawiają się od lewej, a ostatnie zajmuje resztę miejsca. Dlatego HorizontalAlignment="Right" na ostatnim polu dosuwa je do prawej krawędzi.
Kolejność w DockPanel okna
ToolBarTray (Top) i StatusBar (Bottom) muszą być w XAML przed główną zawartością — ostatni element DockPanel wypełnia środek.
Schowek i kursor w TextBox
Tekst.Cut(); // wytnij zaznaczenie do schowka
Tekst.Copy(); // kopiuj zaznaczenie
Tekst.Paste(); // wklej w miejscu kursora
Tekst.Undo(); // cofnij ostatnią zmianę
Tekst.SelectAll(); // zaznacz wszystko
// numer wiersza (od 0), w którym stoi kursor, i początek tego wiersza
int wiersz = Tekst.GetLineIndexFromCharacterIndex(Tekst.CaretIndex);
int poczatek = Tekst.GetCharacterIndexFromLineIndex(wiersz);
int kolumna = Tekst.CaretIndex - poczatek;
int zaznaczono = Tekst.SelectionLength; // ile znaków jest zaznaczonych
Pole tekstowe ma zdarzenie SelectionChanged, które zachodzi przy każdej zmianie zaznaczenia — także przy zwykłym przesunięciu kursora strzałką lub kliknięciem. To dobre miejsce na odświeżenie pozycji kursora w pasku stanu.
Przy TextWrapping="Wrap" „wiersz” oznacza wiersz widoczny na ekranie, więc długi akapit zawinięty na trzy linijki to trzy wiersze. Zanim pole zostanie pierwszy raz wyświetlone, GetLineIndexFromCharacterIndex zwraca -1 — trzeba to sprawdzić.
Ogłoszenie na tablicę szkolną
Edytor krótkich ogłoszeń wydrukowanych na kolorowej kartce. Pasek narzędzi ma dwie części: plik i schowek oraz wygląd (pogrubienie, kursywa, kolor kartki). Pasek stanu pokazuje pozycję kursora, liczbę znaków z paskiem limitu 300 znaków, ostrzeżenie o zbliżaniu się do limitu i — przy prawej krawędzi — suwak rozmiaru tekstu.
Spotkanie w czwartek o 15:00 w sali 14.
Zapraszamy!
wciśnięte „B” (pogrubienie), zielona kartka, kursor na końcu ostatniego wiersza
<Window x:Class="Lekcja36.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Ogłoszenie na tablicę szkolną" Width="620" Height="440"
MinWidth="520" MinHeight="300" WindowStartupLocation="CenterScreen">
<DockPanel>
<!-- dwa paski narzędzi obok siebie w jednej „tacce” -->
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Content="📄" ToolTip="Nowe ogłoszenie" Click="Nowe_Click"/>
<Separator/>
<Button Content="✂" ToolTip="Wytnij" Click="Wytnij_Click"/>
<Button Content="⧉" ToolTip="Kopiuj" Click="Kopiuj_Click"/>
<Button Content="📋" ToolTip="Wklej" Click="Wklej_Click"/>
</ToolBar>
<ToolBar>
<ToggleButton x:Name="Pogrubienie" Content="B" FontWeight="Bold" Width="24"
ToolTip="Pogrubienie" Click="Format_Click"/>
<ToggleButton x:Name="Kursywa" Content="I" FontStyle="Italic" Width="24"
ToolTip="Kursywa" Click="Format_Click"/>
<Separator/>
<TextBlock Text="Kartka:" VerticalAlignment="Center" Margin="4,0"/>
<ComboBox x:Name="Kartka" Width="100" SelectedIndex="0"
SelectionChanged="Kartka_SelectionChanged">
<ComboBoxItem Content="żółta"/>
<ComboBoxItem Content="zielona"/>
<ComboBoxItem Content="różowa"/>
<ComboBoxItem Content="niebieska"/>
</ComboBox>
</ToolBar>
</ToolBarTray>
<!-- pasek stanu na dole okna -->
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem>
<TextBlock x:Name="Pozycja" Text="Wiersz 1, kolumna 1"/>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="Znaki" Width="110"/>
<ProgressBar x:Name="Limit" Width="80" Height="10" Maximum="300"/>
</StackPanel>
</StatusBarItem>
<Separator/>
<StatusBarItem x:Name="Uwaga"/>
<!-- ostatni element dosunięty do prawej -->
<StatusBarItem HorizontalAlignment="Right">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="RozmiarOpis" Width="40"/>
<Slider x:Name="Rozmiar" Width="100" Minimum="12" Maximum="32" Value="18"
IsSnapToTickEnabled="True" TickFrequency="2"
ValueChanged="Rozmiar_ValueChanged"/>
</StackPanel>
</StatusBarItem>
</StatusBar>
<!-- kartka z ogłoszeniem -->
<Border x:Name="Tlo" Margin="20" Padding="12" Background="#FFFFF59D"
BorderBrush="#33000000" BorderThickness="1">
<TextBox x:Name="Tekst" AcceptsReturn="True" TextWrapping="Wrap"
Background="Transparent" BorderThickness="0" FontSize="18"
VerticalScrollBarVisibility="Auto" MaxLength="300"
TextChanged="Tekst_TextChanged" SelectionChanged="Tekst_SelectionChanged"/>
</Border>
</DockPanel>
</Window>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Lekcja36;
public partial class MainWindow : Window
{
// kolory kartek w tej samej kolejności co pozycje listy „Kartka”
private Color[] kolory =
{
Color.FromRgb(255, 245, 157), // żółta
Color.FromRgb(197, 225, 165), // zielona
Color.FromRgb(248, 187, 208), // różowa
Color.FromRgb(179, 229, 252) // niebieska
};
public MainWindow()
{
InitializeComponent();
RozmiarOpis.Text = $"{Rozmiar.Value} pt";
Tekst.Text = "Szkolne koło informatyczne\n\nSpotkanie w czwartek o 15:00 w sali 14.\nZapraszamy!";
Tekst.Focus();
}
// ---------- pasek narzędzi ----------
private void Nowe_Click(object sender, RoutedEventArgs e)
{
if (Tekst.Text != "")
{
MessageBoxResult odp = MessageBox.Show("Wyczyścić ogłoszenie?", "Nowe",
MessageBoxButton.YesNo, MessageBoxImage.Question);
if (odp != MessageBoxResult.Yes)
{
return;
}
}
Tekst.Clear();
Pogrubienie.IsChecked = false;
Kursywa.IsChecked = false;
Format_Click(sender, e);
Tekst.Focus();
}
// TextBox ma gotowe metody schowka
private void Wytnij_Click(object sender, RoutedEventArgs e)
{
Tekst.Cut();
Tekst.Focus();
}
private void Kopiuj_Click(object sender, RoutedEventArgs e)
{
Tekst.Copy();
Tekst.Focus();
}
private void Wklej_Click(object sender, RoutedEventArgs e)
{
Tekst.Paste();
Tekst.Focus();
}
// ToggleButton pamięta stan wciśnięcia — jak CheckBox
private void Format_Click(object sender, RoutedEventArgs e)
{
Tekst.FontWeight = Pogrubienie.IsChecked == true ? FontWeights.Bold : FontWeights.Normal;
Tekst.FontStyle = Kursywa.IsChecked == true ? FontStyles.Italic : FontStyles.Normal;
Tekst.Focus();
}
private void Kartka_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (Tlo != null && Kartka.SelectedIndex >= 0)
{
Tlo.Background = new SolidColorBrush(kolory[Kartka.SelectedIndex]);
}
}
// ---------- pasek stanu ----------
private void Tekst_TextChanged(object sender, TextChangedEventArgs e)
{
int ile = Tekst.Text.Length;
Znaki.Text = $"Znaki: {ile}/{Tekst.MaxLength}";
Limit.Value = ile;
if (ile >= Tekst.MaxLength)
{
Uwaga.Content = "Osiągnięto limit znaków";
Uwaga.Foreground = Brushes.Firebrick;
}
else if (ile > Tekst.MaxLength - 30)
{
Uwaga.Content = $"Zostało {Tekst.MaxLength - ile} znaków";
Uwaga.Foreground = Brushes.DarkOrange;
}
else
{
Uwaga.Content = "";
}
}
// SelectionChanged pola tekstowego zachodzi także przy każdym ruchu kursora
private void Tekst_SelectionChanged(object sender, RoutedEventArgs e)
{
int wiersz = Tekst.GetLineIndexFromCharacterIndex(Tekst.CaretIndex);
if (wiersz < 0)
{
return; // pole nie jest jeszcze wyświetlone
}
int poczatekWiersza = Tekst.GetCharacterIndexFromLineIndex(wiersz);
int kolumna = Tekst.CaretIndex - poczatekWiersza;
Pozycja.Text = $"Wiersz {wiersz + 1}, kolumna {kolumna + 1}";
if (Tekst.SelectionLength > 0)
{
Pozycja.Text += $" (zaznaczono {Tekst.SelectionLength})";
}
}
private void Rozmiar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (Tekst == null)
{
return; // zdarzenie z czasu wczytywania XAML
}
Tekst.FontSize = Rozmiar.Value;
RozmiarOpis.Text = $"{Rozmiar.Value} pt";
}
}
Co dzieje się po kolei
- Okno to
DockPanel: taca z paskami u góry, pasek stanu na dole, kartka (Borderz polem tekstowym) w środku. - Przyciski schowka wywołują gotowe metody pola tekstowego i oddają mu fokus — po kliknięciu paska narzędzi można od razu pisać dalej.
- Oba przyciski
ToggleButtonobsługuje jedna metodaFormat_Click, która ustawia czcionkę na podstawie stanu obu przycisków. - Kolory kartek są w tablicy
kolory, w tej samej kolejności co pozycje listy — numer wybranej pozycji jest numerem koloru. Tekst_TextChangedaktualizuje licznik, pasek limitu i ostrzeżenie. Samo ograniczenie do 300 znaków zapewniaMaxLengthpola.Tekst_SelectionChangedwylicza wiersz i kolumnę kursora oraz dopisuje liczbę zaznaczonych znaków.- Suwak w ostatnim polu paska stanu zmienia rozmiar czcionki; osłona
Tekst == nullchroni przed zdarzeniem z czasu wczytywania XAML.
Sprawdź trzy rzeczy
- Zwężaj okno. Gdy kontrolki przestaną się mieścić, na końcu paska narzędzi pojawi się strzałka z ukrytymi przyciskami.
- Przeciągnij drugi pasek narzędzi za kropkowany uchwyt przed pierwszy.
- Przenieś w XAML
StatusBarzaBorder. Pasek stanu zajmie środek okna, a kartka ułoży się z lewej.
Zestawienie elementów
| Element | Typ | Co robi |
|---|---|---|
ToolBar | kontrolka | Pasek narzędzi z kontrolkami w płaskim stylu. |
ToolBarTray | kontrolka | Taca na kilka pasków; pozwala je przesuwać. |
ToggleButton | kontrolka | Przycisk wciskany i zwalniany; IsChecked typu bool?. |
StatusBar, StatusBarItem | kontrolki | Pasek stanu i jego pola. |
Separator | kontrolka | Kreska między grupami przycisków lub polami stanu. |
Cut, Copy, Paste, Undo, SelectAll | metody TextBox | Schowek, cofanie, zaznaczenie. |
GetLineIndexFromCharacterIndex | metoda | Numer wiersza (od 0) dla pozycji znaku; -1 przed wyświetleniem. |
GetCharacterIndexFromLineIndex | metoda | Pozycja pierwszego znaku wiersza. |
SelectionLength | int | Liczba zaznaczonych znaków. |
TextBox.SelectionChanged | zdarzenie | Zmiana zaznaczenia lub ruch kursora. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
StatusBar w XAML po głównej zawartości DockPanel | Pasek stanu wypełnia środek. Elementy z Dock muszą być przed ostatnim. |
GetCharacterIndexFromLineIndex(-1) | ArgumentOutOfRangeException przy starcie — sprawdź, czy wiersz nie jest ujemny. |
przycisk paska bez ToolTip | Sam znak bywa niezrozumiały; egzaminator też tego nie zgadnie. |
if (Pogrubienie.IsChecked) | CS0266 — ToggleButton.IsChecked to bool?, porównaj z true. |
| po kliknięciu przycisku paska nie da się pisać | Fokus został na przycisku — wywołaj Tekst.Focus(). |
Zadania
Każde zadanie wykonaj w nowym projekcie Aplikacja WPF. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.
Wielowierszowe pole i pasek stanu z polami: liczba słów, liczba znaków (ze spacjami i bez), liczba akapitów, szacowany czas czytania (200 słów na minutę). Przy prawej krawędzi pole „Cel: 250 słów” z paskiem postępu, który robi się zielony po osiągnięciu celu.
pokaż wskazówkę
- Słowa:
Splitpo spacjach i znakach nowej linii zStringSplitOptions.RemoveEmptyEntries.
Rozbuduj rysowanie myszą z lekcji 25 albo 29 o pasek narzędzi: przyciski kolorów (kwadraty w kolorze, z dymkiem z nazwą), dwa ToggleButtony „pióro” i „gumka” (wciśnięty może być tylko jeden), lista grubości i przycisk „wyczyść”. Pasek stanu: współrzędne kursora i aktualne narzędzie.
pokaż wskazówkę
- Kliknięcie jednego
ToggleButtonustawia drugiemuIsChecked = false.
Okno z obrazem (kilka obrazów w zasobach projektu). Pasek narzędzi: ◀ ▶ (poprzedni/następny, wyłączone na końcach), „pokaz slajdów” jako ToggleButton (co 3 s następne zdjęcie, DispatcherTimer), lista sposobu dopasowania (Stretch). Pasek stanu: nazwa pliku, „zdjęcie 3 z 8”, a przy prawej krawędzi pasek postępu do kolejnego slajdu.
pokaż wskazówkę
- Timer co 100 ms: zwiększa pasek postępu i po 30 krokach przechodzi do następnego zdjęcia.
Siatka przycisków produktów (drożdżówka, woda, kanapka…) i lista paragonu. Pasek narzędzi: „nowy paragon”, „usuń pozycję”, ToggleButton „zniżka 10%” dla nauczycieli, pole „wpłacono” z przyciskiem „reszta”. Pasek stanu: liczba pozycji, suma, godzina (zegar na DispatcherTimer) i — przy prawej — liczba paragonów od początku dnia.
pokaż wskazówkę
- Produkty zapisz w
Listobiektów klasyPozycja(nazwa, cena) — suma to pętla po liście.
Dwa pola: tekst jawny i zaszyfrowany. Pasek narzędzi: lista szyfrów (Cezar, ROT13, odwrócenie tekstu), pole przesunięcia dla Cezara (aktywne tylko dla niego), przyciski „szyfruj ↓” i „odszyfruj ↑”, „kopiuj wynik”, „zamień pola”. Pasek stanu: liczba znaków w obu polach i komunikat ostatniej operacji („Zaszyfrowano 42 znaki szyfrem Cezara, przesunięcie 3”).
pokaż wskazówkę
- Polskie litery zostaw bez zmian — szyfruj tylko A–Z i a–z.
Połącz lekcje 35 i 36: edytor notatek z menu (Plik, Edycja, Widok), paskiem narzędzi i paskiem stanu. Te same polecenia są w menu i na pasku i wywołują te same metody. W menu „Widok” pozycje zaznaczane „Pasek narzędzi” i „Pasek stanu” ukrywają i pokazują paski. Przycisk „pogrubienie” na pasku i pozycja „Pogrubienie” w menu muszą zawsze pokazywać ten sam stan. Pasek stanu: wiersz i kolumna, liczba słów, informacja „zmieniono” po każdej zmianie od ostatniego „Nowy”.
pokaż wskazówkę
- Jedna metoda
UstawPogrubienie(bool)zmienia czcionkę orazIsCheckedprzycisku i pozycji menu — wywołują ją oba miejsca.
Co trzeba zapamiętać
ToolBarwToolBarTrayu góry,StatusBarna dole — oba przed główną zawartościąDockPanel.- Przyciski paska narzędzi potrzebują
ToolTip; po kliknięciu oddaj fokus polu roboczemu. ToggleButtonto przycisk zIsCheckedjak wCheckBox.- Ostatnie pole paska stanu z
HorizontalAlignment="Right"trafia do prawej krawędzi. TextBoxma metody schowka,SelectionChangedi metody do wyliczenia wiersza kursora.
Dokumentacja: Microsoft Learn — ToolBar, StatusBar.