C# · WPF · INF.04 · Kontrolki

TextBox – pole tekstowe

Poznasz TextBox – kontrolkę do wpisywania tekstu przez użytkownika. Nauczysz się ograniczać długość wpisywanego tekstu, tworzyć pola wieloliniowe, reagować na zmiany w czasie pisania i walidować dane.

TextBox TextChanged MaxLength Walidacja
1

Czym jest TextBox?

TextBox to pole tekstowe, w które użytkownik może wpisywać tekst – imię, adres email, opis, liczbę, dowolną treść. To podstawowa kontrolka każdego formularza.

TextBox to nie TextBlock!

TextBox = pole do wpisywania tekstu przez użytkownika.
TextBlock = wyświetla tekst, nie można go edytować.

Jeśli chcesz pokazać wynik obliczeń, opis, etykietę → TextBlock. Jeśli chcesz, żeby użytkownik coś wpisał → TextBox.

Najprostszy TextBox
<TextBox x:Name="txtImie"
         Width="200"
         Height="30" />
2

Text – podstawowa właściwość

Właściwość Text przechowuje to co użytkownik wpisał. Możesz ją odczytać, zmienić lub ustawić wartość domyślną.

Text – ustawienie domyślnej wartości
<!-- Tekst domyślny widoczny od razu po otwarciu okna -->
<TextBox x:Name="txtMiasto"
         Text="Lębork"
         Width="200" />
Odczyt i zapis Text z C#
// Odczyt tego co wpisał użytkownik
string imie = txtImie.Text;

// Ustawienie nowej wartości (np. po wczytaniu danych)
txtImie.Text = "Anna";

// Połączenie z innym tekstem
txtImie.Text = $"Witaj, {imie}!";
Text jest zawsze typu string

Nawet jeśli użytkownik wpisze same cyfry, Text zwraca je jako tekst (string), nie jako liczbę. Jeśli potrzebujesz liczby, musisz ją skonwertować – patrz sekcja o walidacji wejścia.

3

MaxLength – ograniczenie długości

MaxLength ogranicza ile znaków użytkownik może wpisać. Po osiągnięciu limitu pole po prostu nie przyjmuje więcej znaków – nie trzeba samodzielnie tego sprawdzać.

MaxLength – przykłady
<!-- Kod pocztowy – maksymalnie 6 znaków (00-000) -->
<TextBox x:Name="txtKodPocztowy"
         MaxLength="6"
         Width="80" />

<!-- Nazwa użytkownika – maksymalnie 20 znaków -->
<TextBox x:Name="txtNazwa"
         MaxLength="20"
         Width="200" />

<!-- Numer PESEL – zawsze 11 cyfr -->
<TextBox x:Name="txtPesel"
         MaxLength="11"
         Width="120" />
MaxLength nie waliduje treści – tylko liczbę znaków

MaxLength="11" nie sprawdza czy użytkownik wpisał same cyfry – tylko że nie wpisał więcej niż 11 znaków (mogą to być dowolne litery). Do sprawdzenia czy wpisane znaki są poprawne (np. tylko cyfry) potrzebujesz dodatkowej walidacji – patrz sekcja 7.

Wyświetlanie licznika znaków – typowy wzorzec
<!-- XAML: -->
<TextBox   x:Name="txtOpis"
           MaxLength="200"
           TextChanged="txtOpis_TextChanged" />
<TextBlock x:Name="lblLicznik" Text="0/200" />

// C#:
private void txtOpis_TextChanged(object sender, TextChangedEventArgs e)
{
    lblLicznik.Text = $"{txtOpis.Text.Length}/200";
}
4

IsReadOnly – pole tylko do odczytu

IsReadOnly="True" blokuje możliwość edycji tekstu, ale w przeciwieństwie do IsEnabled="False":

CechaIsReadOnly=TrueIsEnabled=False
Można edytować tekst❌ Nie❌ Nie
Wygląd✅ Normalny (czarny tekst)⚠️ Wyszarzony
Można zaznaczyć i skopiować tekst✅ Tak❌ Nie
Pole dostępne w nawigacji Tab✅ Tak❌ Nie
IsReadOnly – przykłady użycia
<!-- Pole z automatycznie wygenerowanym numerem zamówienia –
     użytkownik widzi i może skopiować, ale nie edytować -->
<TextBox x:Name="txtNumerZamowienia"
         Text="ZAM/2024/00142"
         IsReadOnly="True"
         Background="#f0f0f0"
         Width="150" />
Kiedy IsReadOnly, a kiedy IsEnabled?

Użyj IsReadOnly, gdy chcesz, żeby użytkownik mógł przeczytać i skopiować wartość (np. wygenerowany kod, ID, wynik). Użyj IsEnabled=”False”, gdy pole jest tymczasowo niedostępne i nie powinno przyciągać uwagi (np. zależne od innego wyboru).

5

Pole wieloliniowe

Domyślnie TextBox to pole jednoliniowe – naciśnięcie Enter nic nie robi (lub aktywuje przycisk z IsDefault="True", jeśli taki jest w oknie). Żeby zrobić pole wieloliniowe (jak w notatniku), potrzebujesz dwóch właściwości.

AcceptsReturn – zezwolenie na Enter

AcceptsReturn="True" sprawia, że naciśnięcie Enter wstawia nową linię w polu, zamiast aktywować przycisk domyślny.

AcceptsReturn
<TextBox x:Name="txtTresc"
         AcceptsReturn="True"
         Height="150"
         Width="300" />

TextWrapping – zawijanie długich linii

Sam AcceptsReturn pozwala na Enter, ale długie linie nadal wychodzą poza pole w poziomie. TextWrapping="Wrap" sprawia, że linie zawijają się automatycznie.

Kompletne pole wieloliniowe
<TextBox x:Name="txtOpis"
         AcceptsReturn="True"
         TextWrapping="Wrap"
         Height="150"
         Width="300" />
<!-- Teraz: Enter tworzy nową linię,
     a długie linie zawijają się automatycznie -->

Scrollbary – pasek przewijania

Gdy treść jest dłuższa niż wysokość pola, potrzebny jest scrollbar. Włączasz go przez VerticalScrollBarVisibility.

WartośćZachowanie
Disabled (domyślne)Brak scrollbara – treść poza polem jest niewidoczna
AutoScrollbar pojawia się tylko gdy potrzebny
VisibleScrollbar zawsze widoczny
Pole tekstowe jak w notatniku – kompletny przykład
<TextBox x:Name="txtNotatnik"
         AcceptsReturn="True"
         TextWrapping="Wrap"
         VerticalScrollBarVisibility="Auto"
         FontFamily="Consolas"
         FontSize="14"
         Padding="8"
         AcceptsTab="True" />
<!-- AcceptsTab – Tab wpisuje znak tabulacji
     zamiast przejść do następnej kontrolki -->
6

Zdarzenie TextChanged

TextChanged odpala się za każdym razem gdy zmieni się zawartość pola – po każdym wpisanym lub usuniętym znaku. Dzięki temu możesz reagować na pisanie w czasie rzeczywistym, bez przycisku.

XAML – podpięcie zdarzenia
<TextBox x:Name="txtImie"
         TextChanged="txtImie_TextChanged" />
C# – typowe zastosowania TextChanged
private void txtImie_TextChanged(object sender, TextChangedEventArgs e)
{
    // 1. Aktualizacja podglądu na żywo
    lblPodglad.Text = $"Witaj, {txtImie.Text}!";

    // 2. Włączanie/wyłączanie przycisku w zależności od treści
    btnDalej.IsEnabled = !string.IsNullOrWhiteSpace(txtImie.Text);

    // 3. Licznik znaków
    lblLicznik.Text = $"{txtImie.Text.Length} znaków";
}
TextChanged odpala się też przy ustawianiu Text z kodu!

Jeśli w Window_Loaded ustawisz txtImie.Text = "Jan";, zdarzenie TextChanged odpali się natychmiast. Jeśli metoda obsługująca to zdarzenie odwołuje się do innych kontrolek, które mogą jeszcze nie być gotowe, dodaj zabezpieczenie: if (lblPodglad == null) return; na początku metody.

7

Walidacja wejścia

Walidacja to sprawdzanie, czy wpisane dane są poprawne. Są trzy podejścia – każde ma swoje zastosowanie.

Walidacja przy kliknięciu przycisku

Najprostsze podejście – sprawdzamy wszystko na raz, gdy użytkownik kliknie przycisk „Zapisz” lub „Dalej”.

Walidacja po kliknięciu
private void btnZapisz_Click(object sender, RoutedEventArgs e)
{
    // Sprawdzenie 1: pole nie może być puste
    if (string.IsNullOrWhiteSpace(txtImie.Text))
    {
        MessageBox.Show("Podaj imię!", "Błąd", MessageBoxButton.OK, MessageBoxImage.Warning);
        txtImie.Focus();   // ustawiamy kursor na polu z błędem
        return;
    }

    // Sprawdzenie 2: czy wiek jest liczbą
    if (!int.TryParse(txtWiek.Text, out int wiek))
    {
        MessageBox.Show("Wiek musi być liczbą!", "Błąd", MessageBoxButton.OK, MessageBoxImage.Warning);
        txtWiek.Focus();
        return;
    }

    // Sprawdzenie 3: zakres wartości
    if (wiek < 0 || wiek > 120)
    {
        MessageBox.Show("Wiek musi być w zakresie 0-120!", "Błąd", MessageBoxButton.OK, MessageBoxImage.Warning);
        return;
    }

    // Wszystko OK – kontynuujemy
    MessageBox.Show($"Zapisano: {txtImie.Text}, {wiek} lat");
}

Walidacja na żywo (podczas pisania)

Drugie podejście – pokazujemy błąd natychmiast, gdy użytkownik pisze, zamiast czekać na kliknięcie przycisku.

Walidacja email na żywo
private void txtEmail_TextChanged(object sender, TextChangedEventArgs e)
{
    string email = txtEmail.Text;

    bool czyPoprawny = email.Contains("@") && email.Contains(".") && email.Length > 5;

    if (czyPoprawny)
    {
        txtEmail.BorderBrush = Brushes.LimeGreen;
        lblBladEmail.Visibility = Visibility.Collapsed;
    }
    else
    {
        txtEmail.BorderBrush = Brushes.Tomato;
        lblBladEmail.Text       = "Nieprawidłowy adres email";
        lblBladEmail.Visibility = Visibility.Visible;
    }
}

Blokowanie złych znaków podczas pisania

Trzecie podejście – nie pozwalamy wpisać złych znaków wcale. Używamy do tego zdarzenia PreviewTextInput, które odpala się przed dodaniem znaku do pola.

Pole tylko na cyfry
<!-- XAML: -->
<TextBox x:Name="txtWiek"
         PreviewTextInput="txtWiek_PreviewTextInput" />

// C#:
private void txtWiek_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
    // e.Text to znak, który użytkownik właśnie próbuje wpisać
    foreach (char znak in e.Text)
    {
        if (!char.IsDigit(znak))
        {
            e.Handled = true;   // blokujemy wpisanie znaku
            return;
        }
    }
}
e.Handled = true – blokowanie znaku

Ustawienie e.Handled = true mówi WPF: „zignoruj to zdarzenie, nie wpisuj znaku do pola”. To ta sama mechanika, którą poznałeś przy zdarzeniach myszy w Canvas.

PodejścieKiedy używać
Walidacja po kliknięciuFormularze, gdzie sprawdzasz wszystko na raz przed zapisem
Walidacja na żywoPokazywanie błędu od razu, np. format email, siła hasła
Blokowanie znakówPola tylko-cyfry, tylko-litery (PESEL, telefon, kod)
8

Czyszczenie pola

Sposoby czyszczenia TextBox
// Sposób 1: ustawienie pustego tekstu
txtImie.Text = "";

// Sposób 2: metoda Clear() – działa identycznie
txtImie.Clear();

// Czyszczenie kilku pól na raz
private void WyczyscFormularz()
{
    txtImie.Clear();
    txtNazwisko.Clear();
    txtEmail.Clear();
    txtWiek.Clear();
    txtImie.Focus();   // ustawiamy kursor na pierwszym polu
}

// Przycisk "Wyczyść"
private void btnWyczysc_Click(object sender, RoutedEventArgs e)
{
    WyczyscFormularz();
}
Czyszczenie po zapisaniu – typowy wzorzec
private void btnDodaj_Click(object sender, RoutedEventArgs e)
{
    if (string.IsNullOrWhiteSpace(txtNazwa.Text))
    {
        MessageBox.Show("Podaj nazwę produktu!");
        return;
    }

    // Dodajemy do listy
    lstProdukty.Items.Add(txtNazwa.Text);

    // Czyścimy pole i ustawiamy focus, żeby można dodawać kolejne
    txtNazwa.Clear();
    txtNazwa.Focus();
}
9

Inne przydatne właściwości

WłaściwośćCo robi
TextAlignmentWyrównanie tekstu w polu (Left/Center/Right)
CharacterCasingWymusza wielkość liter: Upper, Lower, Normal
SelectionStart / SelectionLengthZaznaczenie fragmentu tekstu z kodu
CaretIndexPozycja kursora w tekście
SpellCheck.IsEnabledSprawdzanie pisowni (czerwone podkreślenie błędów)
UndoLimitIle kroków Ctrl+Z można wykonać
CharacterCasing – automatyczne wielkie litery
<!-- Kod promocyjny – zawsze wielkimi literami -->
<TextBox x:Name="txtKodPromocyjny"
         CharacterCasing="Upper"
         MaxLength="10" />
<!-- Użytkownik wpisuje "lato2025", pole pokazuje "LATO2025" -->
Zaznaczanie tekstu z C#
// Zaznacz cały tekst w polu (przydaje się np. po Focus())
txtImie.SelectAll();

// Ustaw kursor na końcu tekstu
txtImie.CaretIndex = txtImie.Text.Length;

// Zaznacz konkretny fragment (od znaku 0, długość 5)
txtImie.Select(0, 5);
10

Praktyczny przykład – formularz rejestracji

MainWindow.xaml
<Grid Margin="20">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="110" />
        <ColumnDefinition Width="*"   />
    </Grid.ColumnDefinitions>

    <TextBlock Grid.Row="0" Grid.Column="0" Text="Nazwa użytkownika:" VerticalAlignment="Center" Margin="0,0,0,8" />
    <TextBox   Grid.Row="0" Grid.Column="1" x:Name="txtNazwa"
               MaxLength="20"
               Margin="0,0,0,8"
               TextChanged="txtNazwa_TextChanged" />

    <TextBlock Grid.Row="1" Grid.Column="0" Text="Email:" VerticalAlignment="Center" Margin="0,0,0,8" />
    <TextBox   Grid.Row="1" Grid.Column="1" x:Name="txtEmail"
               Margin="0,0,0,8" />

    <TextBlock Grid.Row="2" Grid.Column="0" Text="Wiek:" VerticalAlignment="Center" Margin="0,0,0,8" />
    <TextBox   Grid.Row="2" Grid.Column="1" x:Name="txtWiek"
               MaxLength="3"
               PreviewTextInput="txtWiek_PreviewTextInput"
               Margin="0,0,0,8"
               Width="60"
               HorizontalAlignment="Left" />

    <TextBlock Grid.Row="3" Grid.Column="0"
               Grid.ColumnSpan="2"
               x:Name="lblBlad"
               Foreground="Tomato"
               Margin="0,0,0,8"
               Visibility="Collapsed" />

    <StackPanel Grid.Row="4" Grid.Column="1"
                Orientation="Horizontal"
                HorizontalAlignment="Right">
        <Button Content="Wyczyść" Width="80" Margin="0,0,8,0" Click="btnWyczysc_Click" />
        <Button x:Name="btnZarejestruj" Content="Zarejestruj" Width="100"
                IsEnabled="False"
                Click="btnZarejestruj_Click" />
    </StackPanel>
</Grid>
MainWindow.xaml.cs
private void txtNazwa_TextChanged(object sender, TextChangedEventArgs e)
{
    // Przycisk aktywny tylko gdy nazwa ma min. 3 znaki
    btnZarejestruj.IsEnabled = txtNazwa.Text.Length >= 3;
}

private void txtWiek_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
    // Tylko cyfry w polu wieku
    foreach (char znak in e.Text)
    {
        if (!char.IsDigit(znak))
        {
            e.Handled = true;
            return;
        }
    }
}

private void btnZarejestruj_Click(object sender, RoutedEventArgs e)
{
    // Walidacja emaila
    if (!txtEmail.Text.Contains("@") || !txtEmail.Text.Contains("."))
    {
        PokazBlad("Podaj prawidłowy adres email");
        txtEmail.Focus();
        return;
    }

    // Walidacja wieku
    if (!int.TryParse(txtWiek.Text, out int wiek) || wiek < 13)
    {
        PokazBlad("Wiek musi być liczbą i wynosić minimum 13 lat");
        txtWiek.Focus();
        return;
    }

    // Sukces
    lblBlad.Visibility = Visibility.Collapsed;
    MessageBox.Show($"Zarejestrowano: {txtNazwa.Text} ({wiek} lat)",
                    "Sukces", MessageBoxButton.OK, MessageBoxImage.Information);
    WyczyscFormularz();
}

private void PokazBlad(string komunikat)
{
    lblBlad.Text       = komunikat;
    lblBlad.Visibility = Visibility.Visible;
}

private void WyczyscFormularz()
{
    txtNazwa.Clear();
    txtEmail.Clear();
    txtWiek.Clear();
    txtNazwa.Focus();
}

private void btnWyczysc_Click(object sender, RoutedEventArgs e)
{
    WyczyscFormularz();
    lblBlad.Visibility = Visibility.Collapsed;
}
11

Częste błędy

❌ Błąd 1: Konwersja Text na liczbę bez TryParse

❌ Wyjątek przy nieprawidłowych danych

int wiek = int.Parse(txtWiek.Text);
// Jeśli użytkownik wpisze "abc" –
// program się wyłoży (wyjątek)!

✅ TryParse – bezpieczna konwersja

if (!int.TryParse(txtWiek.Text, out int wiek))
{
    MessageBox.Show("Wiek musi być liczbą!");
    return;
}
// Tutaj wiek jest już bezpieczną liczbą

❌ Błąd 2: Pole wieloliniowe bez AcceptsReturn

❌ Enter nie działa

<TextBox Height="150" />
<!-- Enter nic nie robi,
     tekst nie zawija się -->

✅ AcceptsReturn + TextWrapping

<TextBox Height="150"
         AcceptsReturn="True"
         TextWrapping="Wrap" />

❌ Błąd 3: Sprawdzanie tylko IsNullOrEmpty (nie wykrywa spacji)

❌ Same spacje przechodzą walidację

if (string.IsNullOrEmpty(txtImie.Text))
{
    MessageBox.Show("Podaj imię!");
}
// Użytkownik wpisał same spacje "   " –
// walidacja nie wykryje problemu!

✅ IsNullOrWhiteSpace

if (string.IsNullOrWhiteSpace(txtImie.Text))
{
    MessageBox.Show("Podaj imię!");
}
// Wykrywa też same spacje

❌ Błąd 4: TextChanged odpalający się przy starcie programu

❌ Wyjątek null reference przy starcie

private void txt_TextChanged(...)
{
    // lblInfo może być jeszcze null
    // gdy ten kod wykona się podczas
    // budowania okna!
    lblInfo.Text = txt.Text;
}

✅ Zabezpieczenie przed null

private void txt_TextChanged(...)
{
    if (lblInfo == null) return;
    lblInfo.Text = txt.Text;
}
12

Zadania do wykonania

Zadanie 1 łatwe

Stwórz pole TextBox z MaxLength="50" i TextBlock pokazujący licznik znaków w formacie „15/50″, aktualizowany na żywo (TextChanged). Gdy zostanie wykorzystane więcej niż 80% limitu, licznik ma zmienić kolor na pomarańczowy.

Zadanie 2 łatwe

Stwórz pole na numer telefonu, które przyjmuje tylko cyfry (użyj PreviewTextInput) i ma MaxLength="9". Dodaj TextBlock, który informuje czy numer jest kompletny (9 cyfr) czy nie.

Zadanie 3 średnie

Zbuduj prosty notatnik: wieloliniowe pole TextBox (AcceptsReturn, TextWrapping, scrollbar), przycisk „Wyczyść” oraz pasek stanu pokazujący liczbę znaków i liczbę linii (txt.Text.Split('\n').Length), aktualizowany przy każdej zmianie tekstu.

Zadanie 4 średnie

Stwórz kompletny formularz rejestracyjny z polami: nazwa użytkownika (3-20 znaków), email (musi zawierać @ i .), wiek (tylko cyfry, 13-120), hasło (min. 6 znaków – użyj PasswordBox). Waliduj wszystko po kliknięciu „Zarejestruj”, wyświetlaj błędy w czerwonym TextBlock, a przycisk „Zarejestruj” ma być nieaktywny dopóki nazwa użytkownika ma mniej niż 3 znaki. Po sukcesie wyczyść formularz i pokaż MessageBox z podsumowaniem.