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#.
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.
<Image Source="/Images/logo.png" Width="150" Height="150" />
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.
<!-- Ś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" />
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.
<!-- 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.
<Image Source="https://school-it.pl/images/logo.png" Width="150" /> <!-- Wymaga połączenia z internetem podczas uruchomienia aplikacji -->
| Źródło | Zaleta | Wada |
|---|---|---|
| Zasób projektu | Zawsze dostępny, działa bez internetu | Powiększa rozmiar aplikacji |
| Plik z dysku | Można zmieniać bez przebudowy aplikacji | Plik musi istnieć na komputerze użytkownika |
| URL | Łatwa aktualizacja po stronie serwera | Wymaga internetu, wolniejsze ładowanie |
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ść | Zachowanie | Czy proporcje zachowane? |
|---|---|---|
None | Obraz 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 |
Uniform | Obraz skalowany proporcjonalnie, cały widoczny, może zostawić wolne miejsce z boków | ✅ Tak |
UniformToFill | Obraz skalowany proporcjonalnie, wypełnia cały obszar, fragmenty mogą być przycięte | ✅ Tak |
<!-- 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" />
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.
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
// 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
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)
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");
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.
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.
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; } }
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ę.
Zmiana obrazu kliknięciem
Galeria – przyciski Następny / Poprzedni
<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>
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(); }
(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
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)); } }
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.
<Image x:Name="imgAwatar" Source="/Images/avatar-default.png" Width="80" Height="80" Cursor="Hand" MouseLeftButtonDown="imgAwatar_Click" />
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 }
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.
Praktyczny przykład – karta profilu ze zdjęciem
<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>
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}"); } }
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.
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" />
Zadania do wykonania
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).
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.
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).
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.