C# · WPF · INF.04 · Kontrolki

Image – wyświetlanie obrazów

Poznasz kontrolkę Image – sposób wyświetlania zdjęć, ikon i grafik w WPF. Nauczysz się wczytywać obrazy z różnych źródeł, skalować je i dynamicznie podmieniać z kodu C#.

Image Source Stretch BitmapImage
1

Czym jest Image?

Image to kontrolka WPF, która wyświetla obraz – zdjęcie, logo, ikonę, miniaturkę. Obraz może pochodzić z pliku dodanego do projektu, z dysku użytkownika, albo z internetu.

Najprostszy Image
<Image Source="/Images/logo.png"
       Width="150"
       Height="150" />
2

Source – skąd brać obraz

Właściwość Source może wskazywać na obraz z trzech różnych źródeł. Każde ma swoje zastosowanie.

Plik dodany do projektu (zasób)

Najczęstszy sposób – dodajesz plik graficzny do folderu projektu (np. Images) i ustawiasz jego właściwość „Build Action” na „Resource” w Visual Studio (kliknięcie prawym przyciskiem na plik → Właściwości). Taki obraz jest wbudowany w aplikację – nie trzeba dystrybuować osobnych plików graficznych.

Obraz jako zasób projektu
<!-- Ścieżka zaczyna się od / – folder Images musi istnieć w projekcie -->
<Image Source="/Images/logo.png"
       Width="100" />

<!-- Jeśli plik jest w podfolderze -->
<Image Source="/Images/Icons/save.png"
       Width="24" />
Build Action musi być Resource!

Samo dodanie pliku do folderu projektu nie wystarczy. W Eksploratorze rozwiązań kliknij plik prawym przyciskiem → Właściwości → ustaw Build Action na Resource. Bez tego WPF nie znajdzie obrazu podczas działania programu, nawet jeśli plik fizycznie istnieje.

Plik z dysku (ścieżka absolutna)

Gdy obraz nie jest częścią projektu, ale leży gdzieś na dysku użytkownika (np. wybrany przez OpenFileDialog), używasz pełnej ścieżki.

Ścieżka absolutna z dysku
<!-- W XAML rzadko używane na sztywno – ścieżka różni się komputer od komputera -->
<Image Source="C:\Users\Uczen\Pictures\zdjecie.jpg" />

<!-- W praktyce ścieżkę z dysku ustawia się dynamicznie z C# (sekcja 4) -->

Obraz z internetu (URL)

Image może też wczytać obraz prosto z adresu internetowego – WPF samodzielnie go pobierze.

Obraz z URL
<Image Source="https://school-it.pl/images/logo.png"
       Width="150" />
<!-- Wymaga połączenia z internetem podczas uruchomienia aplikacji -->
ŹródłoZaletaWada
Zasób projektuZawsze dostępny, działa bez internetuPowiększa rozmiar aplikacji
Plik z dyskuMożna zmieniać bez przebudowy aplikacjiPlik musi istnieć na komputerze użytkownika
URLŁatwa aktualizacja po stronie serweraWymaga internetu, wolniejsze ładowanie
3

Stretch – skalowanie obrazu

Gdy rozmiar kontrolki Image różni się od rozmiaru oryginalnego obrazu, właściwość Stretch decyduje jak obraz wypełni dostępne miejsce.

WartośćZachowanieCzy proporcje zachowane?
NoneObraz w oryginalnym rozmiarze (może wystawać poza kontrolkę lub zostawić wolne miejsce)✅ Tak
Fill (domyślne)Obraz rozciąga się na cały dostępny obszar❌ Nie – może zniekształcić obraz
UniformObraz skalowany proporcjonalnie, cały widoczny, może zostawić wolne miejsce z boków✅ Tak
UniformToFillObraz skalowany proporcjonalnie, wypełnia cały obszar, fragmenty mogą być przycięte✅ Tak
Porównanie wartości Stretch
<!-- Fill (domyślne) – może zniekształcić obraz jeśli proporcje się nie zgadzają -->
<Image Source="/Images/zdjecie.jpg"
       Width="300" Height="150"
       Stretch="Fill" />

<!-- Uniform – cały obraz widoczny, zachowane proporcje, może być "okno" wolnego miejsca -->
<Image Source="/Images/zdjecie.jpg"
       Width="300" Height="150"
       Stretch="Uniform" />

<!-- UniformToFill – cały obszar wypełniony, fragmenty obrazu przycięte -->
<Image Source="/Images/zdjecie.jpg"
       Width="300" Height="150"
       Stretch="UniformToFill" />
Praktyczna zasada

Dla zdjęć i grafik, gdzie liczy się dokładny wygląd (logo, ikony, awatary) – używaj Uniform, żeby nie zniekształcić proporcji.
Dla tła czy miniatur, gdzie ważniejsze jest wypełnienie całego obszaru – użyj UniformToFill.
Domyślne Fill rzadko jest dobrym wyborem, bo zniekształca proporcje obrazu.

4

BitmapImage w C#

Gdy ustawiasz obraz z kodu C# (a nie z XAML), nie podajesz po prostu ścieżki tekstem – tworzysz obiekt BitmapImage, który WPF używa jako źródło dla kontrolki Image.

Wczytywanie z pliku

BitmapImage z lokalnego pliku
// XAML: <Image x:Name="imgZdjecie" Width="200" Height="200" />

private void WczytajObraz(string sciezka)
{
    BitmapImage bitmap = new BitmapImage();
    bitmap.BeginInit();
    bitmap.UriSource = new Uri(sciezka, UriKind.RelativeOrAbsolute);
    bitmap.EndInit();

    imgZdjecie.Source = bitmap;
}

// Użycie:
WczytajObraz(@"C:\Users\Uczen\Pictures\zdjecie.jpg");
WczytajObraz("/Images/logo.png");  // zasób projektu
BeginInit() i EndInit() – co to jest?

Te dwie metody „opakowują” ustawianie właściwości BitmapImage. Mówią WPF: „zaraz zacznę konfigurować ten obraz, poczekaj z jego wczytaniem do momentu EndInit()”. Dzięki temu możesz ustawić kilka właściwości naraz (np. UriSource, DecodePixelWidth), a obraz wczyta się tylko raz, z wszystkimi ustawieniami.

Wczytywanie z URL (internet)

BitmapImage z internetu
private void WczytajZInternetu(string url)
{
    BitmapImage bitmap = new BitmapImage();
    bitmap.BeginInit();
    bitmap.UriSource = new Uri(url, UriKind.Absolute);
    bitmap.CacheOption = BitmapCacheOption.OnLoad;  // wczytaj całość do pamięci
    bitmap.EndInit();

    imgZdjecie.Source = bitmap;
}

WczytajZInternetu("https://school-it.pl/images/logo.png");
CacheOption.OnLoad przy obrazach z internetu

Bez CacheOption = BitmapCacheOption.OnLoad, plik z internetu może zostać „zablokowany” przez WPF (trzymane jest do niego połączenie), co czasem powoduje problemy przy ponownym wczytywaniu tego samego adresu. Ustawienie OnLoad wczytuje cały obraz do pamięci i zamyka połączenie.

5

Obsługa błędu ładowania

Co się stanie, jeśli plik nie istnieje, jest uszkodzony, albo adres URL nie odpowiada? Trzeba to obsłużyć, żeby aplikacja nie „wyłożyła się” na nieoczekiwanym wyjątku.

Bezpieczne wczytywanie obrazu z try-catch
private bool WczytajObrazBezpiecznie(string sciezka)
{
    try
    {
        BitmapImage bitmap = new BitmapImage();
        bitmap.BeginInit();
        bitmap.UriSource = new Uri(sciezka, UriKind.RelativeOrAbsolute);
        bitmap.CacheOption = BitmapCacheOption.OnLoad;
        bitmap.EndInit();

        imgZdjecie.Source = bitmap;
        return true;
    }
    catch (Exception ex)
    {
        // Plik nie istnieje, uszkodzony, brak internetu, złe uprawnienia...
        MessageBox.Show($"Nie udało się wczytać obrazu: {ex.Message}",
                        "Błąd", MessageBoxButton.OK, MessageBoxImage.Warning);

        // Pokazujemy obraz zastępczy ("placeholder")
        imgZdjecie.Source = new BitmapImage(new Uri("/Images/brak-zdjecia.png", UriKind.Relative));
        return false;
    }
}
Zawsze miej obraz zastępczy (placeholder)

Dobra praktyka to dodanie do projektu prostego obrazu „brak-zdjecia.png” lub „placeholder.png”, który pokazujesz gdy właściwy obraz nie może się wczytać. Lepiej pokazać informację „brak obrazu” niż puste, dziwnie wyglądające miejsce lub wyjątek, który wyłoży aplikację.

6

Zmiana obrazu kliknięciem

Galeria – przyciski Następny / Poprzedni

XAML – galeria zdjęć
<StackPanel>
    <Image x:Name="imgGaleria"
           Width="300"
           Height="200"
           Stretch="Uniform" />

    <StackPanel Orientation="Horizontal"
                HorizontalAlignment="Center"
                Margin="0,10,0,0">
        <Button Content="⬅ Poprzedni" Click="btnPoprzedni_Click" Margin="0,0,8,0" />
        <Button Content="Następny ➡" Click="btnNastepny_Click" />
    </StackPanel>

    <TextBlock x:Name="lblNumer"
               Text="1 / 5"
               HorizontalAlignment="Center"
               Margin="0,8,0,0" />
</StackPanel>
C# – logika galerii
string[] _zdjecia = {
    "/Images/zdjecie1.jpg",
    "/Images/zdjecie2.jpg",
    "/Images/zdjecie3.jpg",
    "/Images/zdjecie4.jpg",
    "/Images/zdjecie5.jpg"
};
int _aktualnyIndeks = 0;

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    PokazAktualneZdjecie();
}

private void PokazAktualneZdjecie()
{
    imgGaleria.Source = new BitmapImage(
        new Uri(_zdjecia[_aktualnyIndeks], UriKind.RelativeOrAbsolute));

    lblNumer.Text = $"{_aktualnyIndeks + 1} / {_zdjecia.Length}";
}

private void btnNastepny_Click(object sender, RoutedEventArgs e)
{
    // Operator % (modulo) zawija indeks z powrotem do 0 po ostatnim zdjęciu
    _aktualnyIndeks = (_aktualnyIndeks + 1) % _zdjecia.Length;
    PokazAktualneZdjecie();
}

private void btnPoprzedni_Click(object sender, RoutedEventArgs e)
{
    // Dodajemy Length przed modulo, żeby uniknąć ujemnego indeksu
    _aktualnyIndeks = (_aktualnyIndeks - 1 + _zdjecia.Length) % _zdjecia.Length;
    PokazAktualneZdjecie();
}
Operator % (modulo) do zawijania indeksu

(indeks + 1) % długość to popularny trik: gdy indeks dochodzi do ostatniej pozycji, modulo „zawija” go z powrotem do 0. Dla 5 zdjęć (indeksy 0-4): po indeksie 4, (4+1) % 5 = 0 – wracamy na początek.

Wybór pliku przez OpenFileDialog

Wybór obrazu z dysku przez przycisk
private void btnWybierzZdjecie_Click(object sender, RoutedEventArgs e)
{
    Microsoft.Win32.OpenFileDialog dialog = new Microsoft.Win32.OpenFileDialog();
    dialog.Filter = "Obrazy (*.jpg;*.png;*.bmp)|*.jpg;*.png;*.bmp|Wszystkie pliki (*.*)|*.*";
    dialog.Title = "Wybierz zdjęcie profilowe";

    if (dialog.ShowDialog() == true)
    {
        imgZdjecie.Source = new BitmapImage(new Uri(dialog.FileName));
    }
}
7

Image jako przycisk – obsługa kliknięcia

Image nie ma wbudowanego zdarzenia Click (w przeciwieństwie do Button). Żeby zareagować na kliknięcie obrazu, używasz zdarzenia myszy MouseLeftButtonDown.

Klikalny obraz
<Image x:Name="imgAwatar"
       Source="/Images/avatar-default.png"
       Width="80"
       Height="80"
       Cursor="Hand"
       MouseLeftButtonDown="imgAwatar_Click" />
C# – obsługa kliknięcia
private void imgAwatar_Click(object sender, MouseButtonEventArgs e)
{
    MessageBox.Show("Kliknięto avatar – otwieram wybór zdjęcia");
    // Tu np. wywołanie OpenFileDialog z sekcji 6b
}
Alternatywa: Button z obrazem w środku

Jeśli potrzebujesz „prawdziwego” przycisku z efektem najechania i wszystkimi właściwościami Button (IsEnabled, ToolTip itd.), rozważ umieszczenie Image wewnątrz Button jako jego Content – tak jak pokazano w temacie o kontrolce Button. To często lepsze rozwiązanie niż obsługa kliknięcia na samym Image.

8

Praktyczny przykład – karta profilu ze zdjęciem

MainWindow.xaml
<Border Background="#252535"
        CornerRadius="10"
        Padding="20"
        Width="250">
    <StackPanel HorizontalAlignment="Center">

        <Border CornerRadius="60"
                Width="120" Height="120"
                ClipToBounds="True">
            <Image x:Name="imgAvatar"
                   Source="/Images/avatar-default.png"
                   Stretch="UniformToFill"
                   Cursor="Hand"
                   MouseLeftButtonDown="imgAvatar_Click" />
        </Border>

        <TextBlock Text="Kliknij, aby zmienić zdjęcie"
                   FontSize="11"
                   Foreground="#888"
                   HorizontalAlignment="Center"
                   Margin="0,8,0,16" />

        <TextBlock Text="Jan Kowalski"
                   FontSize="18"
                   FontWeight="Bold"
                   Foreground="White"
                   HorizontalAlignment="Center" />

    </StackPanel>
</Border>
MainWindow.xaml.cs
private void imgAvatar_Click(object sender, MouseButtonEventArgs e)
{
    Microsoft.Win32.OpenFileDialog dialog = new Microsoft.Win32.OpenFileDialog();
    dialog.Filter = "Obrazy (*.jpg;*.jpeg;*.png)|*.jpg;*.jpeg;*.png";
    dialog.Title = "Wybierz zdjęcie profilowe";

    if (dialog.ShowDialog() != true) return;   // użytkownik kliknął Anuluj

    try
    {
        BitmapImage bitmap = new BitmapImage();
        bitmap.BeginInit();
        bitmap.UriSource = new Uri(dialog.FileName);
        bitmap.CacheOption = BitmapCacheOption.OnLoad;
        bitmap.EndInit();

        imgAvatar.Source = bitmap;
    }
    catch (Exception ex)
    {
        MessageBox.Show($"Nie udało się wczytać obrazu: {ex.Message}");
    }
}
ClipToBounds + CornerRadius – okrągłe zdjęcie profilowe

Trik na okrągły avatar: umieszczamy Image wewnątrz Border z dużym CornerRadius (połowa szerokości/wysokości daje idealny okrąg) i ClipToBounds="True", które „przycina” zawartość do zaokrąglonych krawędzi Bordera.

9

Częste błędy

❌ Błąd 1: Zapomniany Build Action = Resource

❌ Obraz nie wyświetla się

<Image Source="/Images/logo.png" />
<!-- Plik dodany do folderu,
     ale Build Action = "None"
     (domyślne) – nie zostanie
     wbudowany w aplikację! -->

✅ Ustaw Build Action na Resource

Kliknij plik w Eksploratorze rozwiązań → Właściwości → Build Action: Resource

❌ Błąd 2: Brak try-catch przy wczytywaniu z dysku

❌ Aplikacja wyłoży się przy nieprawidłowym pliku

imgZdjecie.Source = new BitmapImage(
    new Uri(dialog.FileName));
// Jeśli plik jest uszkodzony
// lub usunięty po wybraniu –
// nieobsłużony wyjątek!

✅ Zabezpiecz try-catch

try
{
    imgZdjecie.Source = new BitmapImage(
        new Uri(dialog.FileName));
}
catch (Exception ex)
{
    MessageBox.Show("Błąd: " + ex.Message);
}

❌ Błąd 3: Stretch=Fill zniekształca zdjęcia

❌ Zdjęcie wygląda rozciągnięte

<Image Source="/Images/zdjecie.jpg"
       Width="300" Height="100" />
<!-- Stretch="Fill" domyślne –
     kwadratowe zdjęcie staje się
     prostokątem, ludzie wyglądają
     "ściśnięci" -->

✅ Uniform zachowuje proporcje

<Image Source="/Images/zdjecie.jpg"
       Width="300" Height="100"
       Stretch="Uniform" />

❌ Błąd 4: Próba użycia Click na Image

❌ Błąd – Image nie ma zdarzenia Click

<Image Source="/Images/icon.png"
       Click="img_Click" />
<!-- Błąd kompilacji!
     Image nie ma Click,
     tylko Button -->

✅ MouseLeftButtonDown

<Image Source="/Images/icon.png"
       MouseLeftButtonDown="img_Click" />
10

Zadania do wykonania

Zadanie 1 łatwe

Dodaj do projektu 3 dowolne obrazy jako zasoby (Build Action: Resource). Stwórz okno z jednym Image i trzema przyciskami pod nim, każdy podmienia wyświetlany obraz na inny. Porównaj wizualnie działanie Stretch="Fill" i Stretch="Uniform" na tym samym oknie (dwa Image obok siebie z tym samym zdjęciem).

Zadanie 2 łatwe

Stwórz przycisk „Wybierz zdjęcie”, który otwiera OpenFileDialog z filtrem na pliki graficzne i wyświetla wybrany obraz w kontrolce Image (Width=300, Stretch=Uniform). Zabezpiecz wczytywanie przed błędami (try-catch) i wyświetl MessageBox w razie problemu.

Zadanie 3 średnie

Zbuduj kompletną galerię zdjęć (jak w sekcji 6a) z minimum 5 obrazami, przyciskami „Poprzedni”/„Następny” i licznikiem „X / Y”. Dodaj też klawiszowe sterowanie strzałkami lewo/prawo (zdarzenie KeyDown na oknie).

Zadanie 4 średnie

Stwórz kartę profilu użytkownika z okrągłym zdjęciem (Border + CornerRadius + ClipToBounds), klikalnym (zmiana zdjęcia przez OpenFileDialog), imieniem i nazwiskiem w TextBox do edycji. Jeśli użytkownik nie wybrał jeszcze zdjęcia, pokaż domyślny obraz-placeholder. Dodaj przycisk „Resetuj zdjęcie”, który przywraca placeholder.