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.
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
BitmapImagez adresupack://. - Umieścisz obraz na przycisku razem z napisem.
- Rozpoznasz i naprawisz błąd „nie można znaleźć zasobu”.
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.
- W Eksploratorze rozwiązań kliknij projekt prawym przyciskiem → Dodaj → Nowy folder i nazwij go
obrazy. - Przeciągnij pliki PNG lub JPG do tego folderu w Eksploratorze rozwiązań (albo Dodaj → Istniejący element).
- 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.
├── 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:
<ItemGroup>
<Resource Include="obrazy\*.png" />
</ItemGroup>
Image w 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.
Stretch — dopasowanie do miejsca
Gdy proporcje miejsca różnią się od proporcji obrazu, Stretch decyduje, co zrobić:
Uniformcały obraz, proporcje zachowane, puste pasy
UniformToFillwypełnia całość, proporcje zachowane, brzegi ucięte
Fillwypełnia całość, obraz zniekształcony
Nonerozmiar 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.
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:
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.
Obraz na przycisku
Content przycisku może być dowolną kontrolką, także panelem z obrazem i napisem:
<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>
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.
zdjęcie z Stretch="Uniform" — nad i pod obrazem widać puste pasy tła
<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>
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
Imagema na start obraz zastępczyobrazy/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_SelectionChangedtworzyBitmapImagez adresupack://application:,,,/obrazy/…i wstawia go doSource.- Przyciski opcji trybu mają nazwę trybu w
Tag.Enum.Parse<Stretch>(…)zamienia napis „UniformToFill” na wartośćStretch.UniformToFill— nie trzeba czterechif. - 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
- Przełączaj tryby dopasowania i zmieniaj rozmiar okna — przy oknie szerokim i niskim różnice są najlepiej widoczne.
- Zmień w tablicy
"luna.png"na"lunaa.png"i wybierz Lunę. Program zakończy się wyjątkiemIOExceptionz informacją, że nie można znaleźć zasobu. - Usuń z pliku projektu wpis
<Resource Include=…/>i sprawdź w Właściwościach pliku PNG, jaką akcję kompilacji dostał.
Zestawienie elementów
| Element | Typ | Co robi |
|---|---|---|
Image.Source | ImageSource? | Wyświetlany obraz. W XAML ścieżka, w kodzie BitmapImage. |
Stretch | None, Fill, Uniform, UniformToFill | Dopasowanie do dostępnego miejsca (domyślnie Uniform). |
new BitmapImage(new Uri(…)) | obiekt | Obraz wczytany z adresu; przestrzeń nazw System.Windows.Media.Imaging. |
pack://application:,,,/folder/plik.png | adres | Plik z zasobów programu (akcja kompilacji Resource). |
<Resource Include="obrazy\*.png"/> | .csproj | Dołącza obrazy do programu. |
Enum.Parse<Typ>(napis) | metoda | Zamienia nazwę na wartość wyliczenia. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
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 programie | Na innym komputerze plik nie istnieje. Dołącz obraz do projektu. |
obraz bez wymiarów w StackPanel | Rozcią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 nazwie | Działa, ale łatwo o literówkę w adresie. Nazywaj pliki małymi literami bez spacji. |
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.
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
StackPanelzImageiTextBlock.
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
BorderzImagew środku iHorizontalAlignment="Left".
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
Contenti numerem zdjęcia wTag; miniatury mająStretch="UniformToFill". - Ramkę ustaw przyciskom przez
BorderBrushiBorderThicknessw jednej metodzie „pokaż zdjęcie nr i”.
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.
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. ObiektBitmapImagemożesz za to utworzyć raz i używać w wieluImage. - Kto zajął pole, zapisz w
Tagprzycisku — porównywanie obrazów byłoby kłopotliwe.
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 przezArray.IndexOf. - Gdy po filtrowaniu nic nie jest zaznaczone, pokaż obraz zastępczy i wyczyść opis.
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")). Contentprzycisku może zawierać obraz i napis w panelu.
Dokumentacja: Microsoft Learn — klasa Image, identyfikatory pack URI.