Image — obrazy w oknie

Logo w nagłówku, zdjęcie produktu, ikona na przycisku, galeria — w zadaniach egzaminacyjnych obrazy pojawiają się bardzo często, a pliki dostajesz w folderze z arkuszem. Kontrolka Image wyświetla plik graficzny. Najwięcej problemów sprawia jednak nie sama kontrolka, tylko to, gdzie leży plik i jak program ma go znaleźć. Tym zajmiemy się dokładnie.

XAML C# Image Source Stretch BitmapImage 90 min
CEL LEKCJI

Czego się dziś nauczysz

  • Dodasz plik graficzny do projektu tak, żeby program zawsze go znalazł.
  • Wyświetlisz obraz w oknie i dobierzesz sposób dopasowania (Stretch).
  • Zmienisz wyświetlany obraz z kodu, tworząc BitmapImage z adresu pack://.
  • Umieścisz obraz na przycisku razem z napisem.
  • Rozpoznasz i naprawisz błąd „nie można znaleźć zasobu”.
TEORIA

Obraz w projekcie

Program musi wiedzieć, skąd wziąć plik. Najpewniejszy sposób to dołączenie obrazu do projektu jako zasobu — plik zostaje wkompilowany w program i podróżuje razem z nim.

  1. W Eksploratorze rozwiązań kliknij projekt prawym przyciskiem → Dodaj → Nowy folder i nazwij go obrazy.
  2. Przeciągnij pliki PNG lub JPG do tego folderu w Eksploratorze rozwiązań (albo Dodaj → Istniejący element).
  3. Zaznacz plik i w oknie Właściwości sprawdź Akcja kompilacji: ma być Resource (zasób). W projektach WPF obrazy zwykle dostają ją automatycznie.
Lekcja21/
├── obrazy/
│   ├── azor.pngakcja kompilacji: Resource
│   └── brak.png
├── App.xaml
├── MainWindow.xaml
└── Lekcja21.csproj

To samo można zapisać w pliku projektu — jedna linia obejmuje wszystkie obrazy w folderze:

Lekcja21.csproj (fragment)
<ItemGroup>
  <Resource Include="obrazy\*.png" />
</ItemGroup>
TEORIA

Image w XAML

MainWindow.xaml
<StackPanel Margin="10">
    <Image x:Name="Logo" Source="obrazy/logo.png" Height="80"
           HorizontalAlignment="Left"/>
    <TextBlock Text="Zespół Szkół Mechaniczno-Informatycznych" FontSize="16"/>
</StackPanel>

Source to ścieżka pliku względem folderu projektu, z ukośnikiem /. Bez Width i Height obraz zajmie tyle miejsca, ile mu da panel — w StackPanel rozciągnie się na całą szerokość. Zwykle podajesz jeden wymiar, a drugi wyliczy się z proporcji obrazu.

TEORIA

Stretch — dopasowanie do miejsca

Gdy proporcje miejsca różnią się od proporcji obrazu, Stretch decyduje, co zrobić:

Uniform
cały obraz, proporcje zachowane, puste pasy
UniformToFill
wypełnia całość, proporcje zachowane, brzegi ucięte
Fill
wypełnia całość, obraz zniekształcony
None
rozmiar oryginalny, nadmiar ucięty

Domyślna wartość to Uniform — najbezpieczniejsza, bo nigdy nie zniekształca ani nie ucina. UniformToFill dobrze sprawdza się w miniaturach galerii, gdzie wszystkie kafelki mają mieć ten sam kształt.

TEORIA

Zmiana obrazu z kodu

W kodzie Source nie przyjmuje napisu — trzeba utworzyć obiekt obrazu BitmapImage z adresem Uri. Dla pliku zapisanego jako zasób adres ma specjalną postać pack://application:,,,/ + ścieżka w projekcie:

MainWindow.xaml.cs
using System.Windows.Media.Imaging;      // BitmapImage

// obraz z zasobów programu
Logo.Source = new BitmapImage(new Uri("pack://application:,,,/obrazy/logo2.png"));

// obraz z dowolnego pliku na dysku (np. wybranego przez użytkownika)
Logo.Source = new BitmapImage(new Uri(@"C:\Zdjecia\wycieczka.jpg"));

// usunięcie obrazu
Logo.Source = null;

Trzy przecinki w ,,, to nie pomyłka — taki jest zapis adresu zasobów aplikacji. @ przed napisem ze ścieżką Windows sprawia, że \ jest zwykłym znakiem. Do plików z dysku wrócimy w lekcji 38, przy oknie wyboru pliku.

TEORIA

Obraz na przycisku

Content przycisku może być dowolną kontrolką, także panelem z obrazem i napisem:

MainWindow.xaml
<Button Click="Drukuj_Click" Padding="8,4" HorizontalAlignment="Left">
    <StackPanel Orientation="Horizontal">
        <Image Source="obrazy/drukarka.png" Width="20" Height="20" Margin="0,0,6,0"/>
        <TextBlock Text="Drukuj" VerticalAlignment="Center"/>
    </StackPanel>
</Button>
PRZYKŁAD

Zwierzęta do adopcji

Program schroniska pokazuje zwierzęta czekające na dom: lista imion po lewej, zdjęcie na środku, opis i strzałki na dole. Zwierzę bez zdjęcia dostaje obraz zastępczy. Przyciski opcji pozwalają porównać tryby Stretch na tym samym zdjęciu. Projekt zawiera folder obrazy z pięcioma plikami PNG.

Schronisko „Azorek” — do adopcji–□✕
Czekają na dom
Azor
Mruczek
Burek
Luna
Reksio
Dopasowanie zdjęcia
Uniform
UniformToFill
Fill
None
🐾
MRUCZEK
kot, 2 lata
◀
Mruczek — kot, 2 lata. Spokojny, idealny do mieszkania.
▶

zdjęcie z Stretch="Uniform" — nad i pod obrazem widać puste pasy tła

MainWindow.xaml — pełny plik
<Window x:Class="Lekcja21.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Schronisko „Azorek” — do adopcji" Height="480" Width="720"
        MinWidth="560" MinHeight="400" WindowStartupLocation="CenterScreen">

    <DockPanel Margin="12">
        <!-- lista zwierząt po lewej -->
        <StackPanel DockPanel.Dock="Left" Width="160" Margin="0,0,12,0">
            <TextBlock Text="Czekają na dom" FontWeight="Bold" Margin="0,0,0,6"/>
            <ListBox x:Name="Lista" Height="200" SelectionChanged="Lista_SelectionChanged"/>

            <TextBlock Text="Dopasowanie zdjęcia" FontWeight="Bold" Margin="0,14,0,4"/>
            <StackPanel x:Name="Tryby">
                <RadioButton Content="Uniform" Tag="Uniform" Checked="Tryb_Checked"/>
                <RadioButton Content="UniformToFill" Tag="UniformToFill" Checked="Tryb_Checked"/>
                <RadioButton Content="Fill" Tag="Fill" Checked="Tryb_Checked"/>
                <RadioButton Content="None" Tag="None" Checked="Tryb_Checked"/>
            </StackPanel>
        </StackPanel>

        <!-- dół: opis i przyciski przewijania -->
        <DockPanel DockPanel.Dock="Bottom" Margin="0,10,0,0">
            <Button DockPanel.Dock="Left" Content="◀" Width="40" Click="Poprzedni_Click"/>
            <Button DockPanel.Dock="Right" Content="▶" Width="40" Click="Nastepny_Click"/>
            <TextBlock x:Name="Opis" TextWrapping="Wrap" TextAlignment="Center"
                       VerticalAlignment="Center" Margin="10,0"/>
        </DockPanel>

        <!-- zdjęcie wypełnia resztę okna -->
        <Border BorderBrush="LightGray" BorderThickness="1" Background="#FFF3F4F6">
            <Image x:Name="Zdjecie" Source="obrazy/brak.png" Stretch="Uniform"/>
        </Border>
    </DockPanel>
</Window>
MainWindow.xaml.cs — pełny plik
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Lekcja21;

public partial class MainWindow : Window
{
    private readonly string[] imiona = { "Azor", "Mruczek", "Burek", "Luna", "Reksio" };
    private readonly string[] pliki = { "azor.png", "mruczek.png", "burek.png", "luna.png", "" };
    private readonly string[] opisy =
    {
        "Azor — pies, 4 lata. Lubi długie spacery i dzieci.",
        "Mruczek — kot, 2 lata. Spokojny, idealny do mieszkania.",
        "Burek — pies, 9 lat. Senior szuka cichego domu.",
        "Luna — kotka, 1 rok. Ciekawska i bardzo towarzyska.",
        "Reksio — pies, 3 lata. Zdjęcie wkrótce."
    };

    public MainWindow()
    {
        InitializeComponent();

        foreach (string imie in imiona)
        {
            Lista.Items.Add(imie);
        }

        ((RadioButton)Tryby.Children[0]).IsChecked = true;   // Uniform
        Lista.SelectedIndex = 0;
    }

    private void Lista_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        int i = Lista.SelectedIndex;
        if (i < 0)
        {
            return;
        }

        string plik = pliki[i] == "" ? "brak.png" : pliki[i];

        // obraz z zasobów programu: adres pack://application:,,,/folder/plik
        Zdjecie.Source = new BitmapImage(new Uri($"pack://application:,,,/obrazy/{plik}"));
        Opis.Text = opisy[i];
    }

    private void Tryb_Checked(object sender, RoutedEventArgs e)
    {
        RadioButton tryb = (RadioButton)sender;

        // napis z Tag zamieniamy na wartość wyliczenia Stretch
        Zdjecie.Stretch = Enum.Parse<Stretch>($"{tryb.Tag}");
    }

    private void Poprzedni_Click(object sender, RoutedEventArgs e)
    {
        // z pierwszego przechodzimy na ostatni
        Lista.SelectedIndex = (Lista.SelectedIndex - 1 + imiona.Length) % imiona.Length;
        Lista.ScrollIntoView(Lista.SelectedItem);
    }

    private void Nastepny_Click(object sender, RoutedEventArgs e)
    {
        Lista.SelectedIndex = (Lista.SelectedIndex + 1) % imiona.Length;
        Lista.ScrollIntoView(Lista.SelectedItem);
    }
}

Co dzieje się po kolei

  • Układ to DockPanel: panel listy po lewej, pasek opisu na dole, a ramka ze zdjęciem — jako ostatnie dziecko — wypełnia resztę okna. Zdjęcie rośnie razem z oknem.
  • W XAML Image ma na start obraz zastępczy obrazy/brak.png — okno nigdy nie jest puste.
  • Imiona, nazwy plików i opisy to trzy tablice równoległe. Zwierzę bez zdjęcia ma pusty napis zamiast nazwy pliku, a kod podstawia wtedy brak.png.
  • Lista_SelectionChanged tworzy BitmapImage z adresu pack://application:,,,/obrazy/… i wstawia go do Source.
  • Przyciski opcji trybu mają nazwę trybu w Tag. Enum.Parse<Stretch>(…) zamienia napis „UniformToFill” na wartość Stretch.UniformToFill — nie trzeba czterech if.
  • Strzałki zmieniają SelectedIndex „w kółko”. Przy cofaniu dodajemy długość tablicy przed %, bo −1 % 5 w C# daje −1, a nie 4.
  • Pliki obrazów są w projekcie jako Resource (wpis w .csproj), więc program działa po skopiowaniu samego pliku .exe.

Sprawdź trzy rzeczy

  1. Przełączaj tryby dopasowania i zmieniaj rozmiar okna — przy oknie szerokim i niskim różnice są najlepiej widoczne.
  2. Zmień w tablicy "luna.png" na "lunaa.png" i wybierz Lunę. Program zakończy się wyjątkiem IOException z informacją, że nie można znaleźć zasobu.
  3. Usuń z pliku projektu wpis <Resource Include=…/> i sprawdź w Właściwościach pliku PNG, jaką akcję kompilacji dostał.
ELEMENTY WBUDOWANE

Zestawienie elementów

ElementTypCo robi
Image.SourceImageSource?Wyświetlany obraz. W XAML ścieżka, w kodzie BitmapImage.
StretchNone, Fill, Uniform, UniformToFillDopasowanie do dostępnego miejsca (domyślnie Uniform).
new BitmapImage(new Uri(…))obiektObraz wczytany z adresu; przestrzeń nazw System.Windows.Media.Imaging.
pack://application:,,,/folder/plik.pngadresPlik z zasobów programu (akcja kompilacji Resource).
<Resource Include="obrazy\*.png"/>.csprojDołącza obrazy do programu.
Enum.Parse<Typ>(napis)metodaZamienia nazwę na wartość wyliczenia.
CZĘSTE BŁĘDY

Zanim utkniesz

ZapisProblem
obraz w Source, ale okno puste / błąd „nie można zlokalizować zasobu”Plik nie jest w projekcie, ma inną nazwę (wielkość liter!) albo akcję kompilacji inną niż Resource.
Zdjecie.Source = "obrazy/azor.png";CS0029 — w kodzie potrzebny BitmapImage.
new Uri("obrazy\azor.png")\a to znak specjalny w C#, a adres jest niepełny. Użyj pack://application:,,,/obrazy/azor.png.
ścieżka C:\Users\…\Pulpit\azor.png w programieNa innym komputerze plik nie istnieje. Dołącz obraz do projektu.
obraz bez wymiarów w StackPanelRozciąga się na całą szerokość okna i może być ogromny.
brak using System.Windows.Media.Imaging;CS0246 — nie znaleziono typu BitmapImage.
plik ze spacją lub polskimi znakami w nazwieDziała, ale łatwo o literówkę w adresie. Nazywaj pliki małymi literami bez spacji.
ZADANIA

Zadania

Każde zadanie wykonaj w nowym projekcie Aplikacja WPF. Obrazy przygotuj sam (np. narysuj w Paincie, zrób zdjęcie telefonem albo użyj grafik na wolnej licencji) i dołącz je do projektu w folderze obrazy. Wskazówkę otwórz dopiero wtedy, gdy utkniesz.

ZAD 1Strona startowa szkoły★☆☆

Okno z nagłówkiem: logo szkoły (wysokość 60) i obok nazwa szkoły. Pod nagłówkiem duże zdjęcie budynku, które wypełnia resztę okna bez zniekształceń i bez pustych pasów (brzegi mogą zostać ucięte). Na dole trzy przyciski z ikoną i napisem: Plan lekcji, Kontakt, Rekrutacja — kliknięcie pokazuje krótką informację w MessageBox.

pokaż wskazówkę
  • „Bez pustych pasów, brzegi ucięte” to jeden z trybów Stretch.
  • Przycisk z ikoną: w środku poziomy StackPanel z Image i TextBlock.
ZAD 2Flagi Europy★☆☆

Lista rozwijana z sześcioma państwami. Po wyborze pod listą pojawia się flaga (szerokość 200, z cienką ramką), stolica i liczba ludności. Na start wybrana jest Polska. Przycisk Następne przechodzi do kolejnego państwa w kółko.

pokaż wskazówkę
  • Nazwij pliki tak jak kody państw (pl.png, de.png…) — adres złożysz w interpolacji.
  • Ramka wokół obrazu to Border z Image w środku i HorizontalAlignment="Left".
ZAD 3Galeria z miniaturami★★☆

U góry duży podgląd zdjęcia z podpisem, pod nim pasek miniatur 100 × 70 w WrapPanel, utworzonych w pętli z tablicy nazw plików (co najmniej 6 zdjęć z wycieczki klasowej). Kliknięcie miniatury pokazuje zdjęcie w podglądzie, a wybrana miniatura ma niebieską ramkę grubości 3. Strzałki ◀ ▶ obok podglądu przewijają zdjęcia. Podpis: „Zdjęcie 3 z 8 — molo w Sopocie”.

pokaż wskazówkę
  • Miniatura to przycisk z obrazem w Content i numerem zdjęcia w Tag; miniatury mają Stretch="UniformToFill".
  • Ramkę ustaw przyciskom przez BorderBrush i BorderThickness w jednej metodzie „pokaż zdjęcie nr i”.
ZAD 4Ikona pogody★★☆

Pole temperatury, lista rozwijana zachmurzenia (bezchmurnie, częściowe, pełne) i pole wyboru Opady. Program na żywo wybiera ikonę: słońce, słońce z chmurą, chmura, deszcz albo śnieg (opady przy temperaturze ≤ 0 °C to śnieg) i pokazuje pod nią opis „Pochmurno, deszcz, 7 °C”. Gdy temperatura jest błędna, wyświetla ikonę znaku zapytania.

pokaż wskazówkę
  • Najpierw ustal nazwę pliku w łańcuchu if, a obraz utwórz raz, na końcu metody.
  • Opady mają pierwszeństwo przed zachmurzeniem.
ZAD 5Kółko i krzyżyk z obrazkami★★☆

Wróć do planszy 3 × 3 z lekcji 09. Zamiast liter X i O pola pokazują obrazki (np. krzyżyk i kółko narysowane w Paincie albo dwie ikony zwierząt). Nad planszą obrazek gracza, który ma ruch. Pole jest wolne, gdy jego Content jest null. Przycisk Nowa gra czyści planszę.

pokaż wskazówkę
  • Każde pole potrzebuje nowego obiektu Image — jednej kontrolki nie da się wstawić w dwa miejsca naraz. Obiekt BitmapImage możesz za to utworzyć raz i używać w wielu Image.
  • Kto zajął pole, zapisz w Tag przycisku — porównywanie obrazów byłoby kłopotliwe.
ZAD 6Atlas grzybów★★★

Atlas ośmiu grzybów: lista nazw polskich, przyciski opcji filtra Wszystkie / Jadalne / Trujące i pole wyszukiwania. Zaznaczenie grzyba pokazuje zdjęcie (zielona ramka dla jadalnych, czerwona dla trujących), nazwę łacińską kursywą i napis „JADALNY” albo „TRUJĄCY — NIE ZBIERAĆ!”. Grzyb bez zdjęcia pokazuje obraz zastępczy. Filtr i wyszukiwanie działają razem, a pod listą widać „Pokazano 3 z 8”. Wyczyszczenie listy przy filtrowaniu nie może zostawić na ekranie zdjęcia grzyba, którego nie ma na liście.

pokaż wskazówkę
  • Dane w tablicach: nazwa, łacińska nazwa, plik, bool[] jadalny. Na liście tylko nazwy — numer w tablicach znajdziesz przez Array.IndexOf.
  • Gdy po filtrowaniu nic nie jest zaznaczone, pokaż obraz zastępczy i wyczyść opis.
PODSUMOWANIE

Co trzeba zapamiętać

  • Obrazy dołączaj do projektu (folder obrazy, akcja kompilacji Resource) — wtedy działają na każdym komputerze.
  • W XAML: <Image Source="obrazy/plik.png"/>; podaj jeden wymiar, drugi wynika z proporcji.
  • Stretch: Uniform (domyślny, cały obraz), UniformToFill (wypełnia, ucina), Fill (zniekształca), None (oryginał).
  • W kodzie: new BitmapImage(new Uri("pack://application:,,,/obrazy/plik.png")).
  • Content przycisku może zawierać obraz i napis w panelu.

Dokumentacja: Microsoft Learn — klasa Image, identyfikatory pack URI.