1/38
Tematy zajęć: 1. Wprowadzenie do WPF i XAML. Tworzenie interfejsu użytkownika.
2. Posługiwanie się podstawowymi kontrolkami. 3. Własności i zdarzenia w WPF. 4. Zadania aplikacji. Okna. 5. Polecenia. Zasoby. 6. Wiązanie danych. 7. Konwersja, walidacja, szablony, widoki. 8. Style, drzewa, menu. 9. Dokumenty i drukowanie. 10. Kształty, transformacje, pędzle, geometria, rysowanie. 11. Animacje. 12. Szablony kontrolek. Kontrolki użytkownika. 13. Grafika 3d. 14. Interfejs oparty na stronach.
3/38
Literatura i oprogramowanie: 1. Matthew MacDonald, Pro WPF in C# 2010: Windows Presentation
Foundation in .NET 4
2. Adam Nathan, WPF 4 Unleashed
3. Andrew Troelsen, Język C# I platform .NET 4. Stephen C. Perry, C# I .NET 5. Jesse Liberty, C#. Programowanie 6. Charles Petzold, Applications = Code + Markup, a guide to the Microsoft Windows
Presentation Foundation 7. Matthew A. Stoecker, Microsoft .NET Framework 3.5 – Windows Presentation
Foundation 8. http://msdn.com (wszystko na temat programowania w C# i nie tylko) 9. http://codeguru.pl, http://codeproject.com (artykuły na temat programowania w C#
i nie tylko) 10. Visual Studio 2008 lub 2010 w dowolnej wersji (wraz z dostępnym z niego
systemem pomocy MSDN)
4/38
Co to jest programowanie zdarzeniowe? Jak powinna wyglądać aplikacja okienkowa?
Krok pierwszy – tworzenie interfejsu użytkownika (wizualne lub w kodzie).
5/38
Co to jest programowanie zdarzeniowe? Jak powinna wyglądać aplikacja okienkowa?
Krok drugi – obsługa zdarzeń. (chcemy, aby akcja użytkownika spowodowała
wykonanie naszego kodu)
void Oblicz(...) { ... ... ... }
6/38
Co to jest programowanie zdarzeniowe? Jak powinna wyglądać aplikacja okienkowa?
Krok trzeci – interakcja z kontrolkami. (chcemy odczytać wprowadzone przez
użytkownika dane, wykonać obliczenia
i wyświetlić wynik)
void Oblicz(...) { ... ... ... }
7/38
WPF - Windows Presentation Foundation
API wyższego poziomu
Nowy system graficzny i API (interfejs programowania aplikacji) Windowsów. Najbardziej radykalna zmiana interfejsu od czasów Win95. Wcześniej API opierało się na User32 (wygląd i zachowanie okien i kontrolek) oraz GDI/GDI+ (odpowiadało za rysowanie). Różnorodne frameworki (np. Window Forms, MFC) zapewniały dodatkową abstrakcję, pomagając redukować złożoność, dodając nowe możliwości; ale pod tym zawsze kryło się User32 i GDI/GDI+ ze swymi ograniczeniami.
8/38
WPF
Wykorzystuje DirectX do rysowania okien i zawartości. Niezależność od rozdzielczości (oparcie się na „jednostkach
logicznych”=1/96 cala) – umożliwia łatwe skalowanie i dopasowanie do rozdzielczości ekranu.
Ułożenie kontrolek: dynamiczne, oparte na zawartości (dopasowują się do swojej
zawartości oraz dostępnego miejsca). Obiektowy model rysowania oparty na grafice wektorowej. Wsparcie dla mediów i grafiki 3D. Deklaratywne tworzenie animacji. Style i szablony – pozwalają dopasowywać formatowanie i sposób renderowania
elementów interfejsu. Deklaratywne*) tworzenie interfejsu użytkownika (XAML) – pozwala oddzielić
wygląd interfejsu od kodu. *) Deklaratywne – czyli nie opisujemy kroków prowadzących do rozwiązania (algorytmu), a
jedynie samo rozwiązanie. Nie mówmy jak ma być coś zrobione, ale co ma być zrobione.
9/38
XAML – Extensible Application Markup Language
oparty na XMLu*) (deklaratywny) język do tworzenia interfejsu definiuje ułożenie (oraz inne cechy) kontrolek w oknie pozwala na podział pracy pomiędzy programistów i grafików (twórców interfejsu) XAML nie jest obowiązkowy – to samo można zrobić w kodzie, ale wymaga to
większego nakładu pracy można korzystać z narzędzi wizualnych do generowania plików XAML (np.
Microsoft Expression Blend) warto znać XAMLa aby móc w pełni wykorzystywać możliwości WPFa
cel – oddzielenie tworzenia interfejsu od programowania (mechanizmów) np. w Windows Forms narzędzia wizualne tworzą kod c# – nie ma łatwej współpracy
(a logika i tak zawsze po stronie programisty (animacje, etc.))
*) XML – uniwersalny język znaczników do strukturalnego reprezentowania danych
10/38
XML – wprowadzenie
prawidłowy plik XML <!-- komentarz --> <lista> <osoba> Kowalski </osoba> <osoba imię="Piotr" nazwisko="Nowak"> <telefon numer="0123456789"/> </osoba> </lista>
znacznik zamykający
atrybuty
znacznik pusty
znacznik otwierający
11/38
XML – wprowadzenie
nieprawidłowy plik XML <lista> <osoba>Nowak</Osoba> <osoba> <adres> </osoba> </adres> </lista> <osoba> Kowalski </osoba>
12/38
XAML
każdy znacznik odpowiada określonemu elementowi interfejsu (i konkretnej klasie
.NET – powoduje utworzenie obiektu danej klasy) możliwe jest zagnieżdżanie – używane do określenia zawierania np. jednych
elementów w innych ustawianie właściwości przez atrybuty
<Window ... Title="Okienko" Height="200" Width="300" FontSize="14"> <Grid> <Button Margin="30"> Nie dotykać </Button> </Grid> </Window>
13/38
Element <Window> – okno aplikacji
<Window x:Class="WindowsApplication1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Okienko" Height="200" Width="300" FontSize="14"> <Grid> <Button Margin="30"> Nie dotykać </Button> </Grid> </Window> Przykładowe atrybuty okna:
Title – tekst na pasku tytułowym Height – wysokość (rozmiar w jednostkach logicznych) Width – szerokość FontSize – rozmiar czcionki używanej przez wszystkie kontrolki w oknie
Każdy atrybut odpowiada konkretnej własności klasy Window uwaga: tylko jeden element korzenia (nadrzędny)
14/38
Element <Window> – okno aplikacji
<Window x:Class="WindowsApplication1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" ...>... </Window> Przestrzenie nazw – określają, skąd pochodzą klasy (znaczniki), którymi się posługujemy. (uwaga: te deklaracje można umieścić gdziekolwiek (jako atrybuty), ale dobra praktyka
każe umieszczać je w korzeniu)
xmlns="…" – zawiera wszystkie klasy WPFa, kontrolki, wszystko do tworzenia elementów interfejsu deklarowana bez prefiksów, jest domyślna dla dokumentu xmlns:x="…" – XAMLowa przestrzeń nazw zawiera różne przydatne narzędzia i elementy XAMLa; zmapowana tu do prefiksu „x” - czyli wymaga podania <x:nazwaElementu> gdy chcemy coś z niej wziąć te przestrzenie nazw nie odpowiadają dokładnie przestrzeniom z .Net, gdyż dla ułatwienia i
uproszczenia zawarto większość w zbiorczej - a definiowane są przez URI aby różni
dostawcy nie wchodzili sobie w drogę
15/38
Element <Window> – okno aplikacji
<Window x:Class="WpfApplication1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" ...>... </Window>
atrybut x:Class w oknie pozwala połączyć klasę z tworzonym interfejsem (prefiks x oznacza, że ten atrybut to element przestrzeni nazw XAMLa a nie WPF) // zawartość pliku Window1.xaml.cs: namespace WpfApplication1 { public partial class Window1 : Window { public Window1() { InitializeComponent(); } } }
16/38
nazywanie elementów
ważne, aby posłużyć się obiektem, np. <Button Name="button1" ...> ... </Button> odpowiada to: Button button1; i pozwala na: button1.Content = „Dzień dobry”;
uwaga: element nie musi mieć nazwy
poste właściwości (i konwersja typów)
<TextBox Name="pole" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" FontFamily="Verdana" FontSize="24" Foreground="Green" ... > atrybuty ustawiają właściwości obiektu
17/38
Układy zawartości
<Window ... Title="Okienko" Height="200" Width="300" FontSize="14"> <Grid> <Button Margin="30"> Nie dotykać </Button> </Grid> </Window> Okno może zawierać tylko jeden element. Aby umieścić ich więcej musimy wykorzystać
specjalny element, który posłuży za kontener dla innych kontrolek. jest on odpowiedzialny za ułożenie kontrolek w oknie; dba o dopasowanie
kontrolek do zawartości oraz do dostępnego miejsca elementy nie powinny mieć ustalonego rozmiaru ani położenia (współrzędnych) – o
te aspekty będzie dbał kontener różne rodzaje (typy) kontenerów będą kierować się różną logiką rozmieszczania
elementów Wszystkie kontenery dziedziczą z (abstrakcyjnej) klasy Panel. Dodaje ona
właściwość Children – jest to kolekcja elementów zawartych w panelu (pierwszy poziom zagnieżdżenia).
18/38
<StackPanel>
<Window ...> <StackPanel> <Button>jeden</Button> <Button>dwa</Button> <Button>trzy</Button> <Button>cztery</Button> </StackPanel> </Window>
19/38
<StackPanel>
<Window ...> <StackPanel Orientation="Horizontal"> <Button>jeden</Button> <Button>dwa</Button> <Button>trzy</Button> <Button>cztery</Button> </StackPanel>
</Window>
20/38
Przy pomocy atrybutów możemy sterować ułożeniem kontrolek:
<Window ...> <StackPanel Orientation="Horizontal"> <Button VerticalAlignment="Top">jeden</Button> <Button VerticalAlignment="Center">dwa</Button> <Button VerticalAlignment="Bottom">trzy</Button> <Button VerticalAlignment="Stretch">cztery</Button> </StackPanel> </Window>
VerticalAlignment HorizontalAlignment
21/38
Margin – dodaje odstęp od krawędzi kontenera i sąsiednich elementów:
<Window ...> <StackPanel> <Button HorizontalAlignment="Left" Margin="5"> jeden</Button> <Button HorizontalAlignment="Right" Margin="5"> dwa</Button> <Button Margin="5">trzy</Button> <Button Margin="15, 5">cztery</Button> <Button Margin="30, 5, 15, 0">pięć</Button> </StackPanel> </Window>
marginesy sąsiadujących
kontrolek sumują się!
22/38
Podobnie sterujemy ułożeniem zawartości wewnątrz kontrolki: <Window ...> <StackPanel> <Button HorizontalContentAlignment="Left"> jeden</Button> <Button HorizontalContentAlignment="Right"> dwa</Button> <Button HorizontalContentAlignment="Center"> trzy</Button> <Button>cztery</Button> </StackPanel> </Window>
VerticalContentAlignment HorizontalContentAlignment
23/38
Podobnie sterujemy ułożeniem zawartości wewnątrz kontrolki: <Window ...> <StackPanel> <Button HorizontalAlignment="Center"> jeden</Button> <Button HorizontalAlignment="Center" Padding="5"> dwa</Button> <Button HorizontalAlignment="Center" Padding="15, 5"> trzy</Button> <Button HorizontalAlignment="Center" Padding="30, 0, 15, 5">cztery</Button> </StackPanel> </Window>
Padding steruje odstępem od zawartości kontrolki
24/38
<WrapPanel>
<Window ...> <WrapPanel> <Button Margin="5">jeden</Button> <Button Margin="5">dwa</Button> <Button Margin="5">trzy</Button> <Button Margin="5">cztery</Button> <Button Margin="5">pięć</Button> <Button Margin="5">sześć</Button> </WrapPanel> </Window>
25/38
<DockPanel>
<Window ...> <DockPanel> <Button DockPanel.Dock="Top">jeden</Button> <Button DockPanel.Dock="Left">dwa</Button> <Button DockPanel.Dock="Right">trzy</Button> <Button DockPanel.Dock="Bottom">cztery</Button> <Button DockPanel.Dock="Top">pięć</Button> <Button DockPanel.Dock="Right">sześć</Button> <Button>siedem</Button> </DockPanel> </Window>
kolejność elementów
ma znaczenie!
Dołączone właściwości
26/38
uwaga: dołączone właściwości
są to właściwości, które mogą dotyczyć jakiejś kontrolki, ale są zdefiniowane w
innej klasie każda kontrolka ma swoje własne właściwości, ale też jakaś inna kontrolka czy
element interfejsu (np. kontener) może chcieć udekorować ją własnymi
specyficznymi właściwościami kontrolka umieszczona w kontenerze zyskuje dodatkowe właściwości
określa się: TypDenifiujący.NazwaWłaściwości działa to jako: DockPanel.SetDockPanel(button, Dock.Top); co faktycznie tłumaczy się na: button.SetValue(DockPanel.DockProperty, Dock.Top) jest to właściwość potrzebna DockPanelowi, ale faktycznie przechowywana jest w
Buttonie
27/38
<Grid>
<Window ...> <Grid ShowGridLines="True"> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> ... ... </Grid> </Window>
elementy umieszczamy w polach siatki
krok pierwszy – zdefiniowanie siatki
Tylko do testu
Złożone właściwości
28/38
co to jest? złożona właściwość – gdy prosta to za mało; wtedy zamiast: <Grid Name="grid1" RowDefinitions = "..."> ... </Grid> dajemy: <Grid Name="grid1"> <Grid.RowDefinitions> ... </Grid.RowDefinitions > ... </Grid>
29/38
<Grid> <Window ...> <Grid> <Grid.RowDefinitions>...</Grid.RowDefinitions> <Grid.ColumnDefinitions>...</Grid.ColumnDefinitions> <Button>jeden</Button> <Button Grid.Row="1">dwa</Button> <Button Grid.Column="2">trzy</Button> <Button Grid.Row="1" Grid.Column="1">cztery</Button> </Grid> </Window>
krok drugi – rozmieszczenie elementów
30/38
<Grid> <Window ...> <Grid> <Grid.RowDefinitions>...</Grid.RowDefinitions> <Grid.ColumnDefinitions>...</Grid.ColumnDefinitions> <Button Grid.ColumnSpan="2">jeden</Button> <Button Grid.Row="1">dwa</Button> <Button Grid.Column="2" Grid.RowSpan="2">trzy</Button> <Button Grid.Row="1" Grid.Column="1">cztery</Button> </Grid> </Window>
element może rozciągać się
na więcej niż jedno pole siatki
31/38
<Grid>
<Window ...> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="70"/> </Grid.ColumnDefinitions> ... ... </Grid> </Window>
Width dla kolumn Height dla wierszy
32/38
Kontenery można dowolnie zagnieżdżać:
<Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="auto"/> <ColumnDefinition/> </Grid.ColumnDefinitions> <StackPanel> ... </StackPanel> <StackPanel Grid.Column="1" Orientation="Horizontal"> ... </StackPanel> <WrapPanel Grid.Column="2"> ... </WrapPanel> </Grid>
33/38
Ciekawe cechy Grida:
GridSplitter – pozwala na dynamiczną (przez użytkownika) zmianę rozmiaru kolumn/
wierszy Grida
SharedSizeGroup – pozwala dopasować rozmiar kolumny lub wiersza do innej kolumny
lub wiersza (również w innym Gridzie)
Inne ciekawe rodzaje kontenerów:
UniformGrid – podobne do Grida, lecz wymusza jednakowy rozmiar komórek tabeli
Canvas – pozwala wyłącznie na rozmieszczanie bezwzględne (w ustalonych
współrzędnych)
InkCanvas – podobne do Canvas, ale pozwala też na przyjmowanie wejścia ze stylusa
(rysika), pozwala np. na rysowanie na powierzchni okna lub rozpoznawanie gestów
34/38
A jak zaprojektujemy nasze okienko?
<Window ... Title="Obliczenia" Height="200" Width="300" FontSize="14"> ... </Window>
35/38
<Window ...> <Grid> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> </Grid> </Window>
cztery wiersze, dwie kolumny wysokość wierszy dopasowuje się
do zawartości szerokość pierwszej kolumny
dopasowuje się do zawartości szerokość kolumny z polem
tekstowym – rozciągnięta
36/38
<Window ...> <Grid> <Grid.RowDefinitions>...</Grid.RowDefinitions> <Grid.ColumnDefinitions>...</Grid.ColumnDefinitions> <Label Margin="5">Pierwsza liczba:</Label> <Label Margin="5" Grid.Row="1">Druga liczba:</Label> <Label Margin="5" Grid.Row="2">Wynik:</Label> <TextBox Margin="5" Grid.Column="1"/> <TextBox Margin="5" Grid.Row="1" Grid.Column="1"/> <TextBox Margin="5" Grid.Row="2" Grid.Column="1" IsReadOnly="True"/> <Button Grid.Row="3" Grid.ColumnSpan="2" Margin="5" Padding="10,3" HorizontalAlignment="Right"> Oblicz</Button> </Grid> </Window>
Label – etykieta TextBox – pole tekstowe IsReadOnly – tylko do odczytu przycisk zajmuje cały wiersz
37/38
Jak dodać obsługę zdarzeń?
również przy pomocy atrybutów:
<Button Click="Oblicz" ...>Oblicz</Button> // zawartość pliku Window1.xaml.cs: namespace WpfApplication1 { public partial class Window1 : Window { public Window1() { InitializeComponent(); } private void Oblicz(object sender, RoutedEventArgs e) { } } }
Ta funkcja zostanie wywołana, gdy użytkownik naciśnie przycisk „Oblicz”
38/38
Jak odczytać dane z okna w kodzie programu?
najpierw musimy nazwać elementy, do których chcemy mieć dostęp: <TextBox Name="pierwsze" .../> <TextBox Name="drugie" .../> <TextBox Name="wynik" ... IsReadOnly="True"/>
następnie w pliku *.cs: private void Oblicz(object sender, RoutedEventArgs e) { int a = int.Parse(pierwsze.Text); int b = int.Parse(drugie.Text); int c = a + b; wynik.Text = c.ToString(); }
pierwsze.Text – zawartość pola tekstowego
int.Parse(...) – konwersja tekstu na liczbę
c.ToString() – konwersja zmiennej na tekst